Functions
Functions are the building blocks of Move programs. They allow you to encapsulate logic, reuse code, and create modular, maintainable programs. Move provides several types of functions with different visibility levels and purposes.
Function Types and Visibility
Public Functions
Public functions can be called from outside the module.
module my_module::functions {
/// Public functions can be called from outside the module.
public fun add(x: u64, y: u64): u64 {
x + y
}
/// Public functions can also be entry functions.
public entry fun public_entry(account: &signer, value: u64) {
let addr = signer::address_of(account);
// Process the transaction
}
}
Private Functions
Private functions can only be called from within the same module. Functions are private by default.
module my_module::private_functions {
/// Private functions can only be called from within the module.
fun multiply(x: u64, y: u64): u64 {
x * y
}
/// Public function that uses private function
public fun calculate_area(width: u64, height: u64): u64 {
multiply(width, height) // Call private function
}
/// Private helper function
fun validate_input(value: u64): bool {
value > 0 && value < 1000
}
public fun process_data(input: u64): u64 {
if (validate_input(input)) { // Call private function
input * 2
} else {
0
}
}
}
Friend Functions
Friend functions can be called from other modules that declare this module as a friend.
module my_module::friend_example {
/// Friend functions can be called from other modules that declare this module as a friend.
friend fun sum_vector(vec: vector<u64>): u64 {
let sum = 0u64;
let i = 0;
let len = vector::length(&vec);
while (i < len) {
sum = sum + *vector::borrow(&vec, i);
i = i + 1;
};
sum
}
/// Alternative syntax for friend functions
public(friend) fun do_something_friend(x: u64): u64 {
x + 1
}
}
/// Module that declares my_module::friend_example as a friend
module my_module::friend_caller {
friend my_module::friend_example;
public fun call_friend_function(): u64 {
let numbers = vector[1, 2, 3, 4, 5];
my_module::friend_example::sum_vector(numbers)
}
}
Package Functions
Package functions can be called from within the package but not from outside.
module my_module::package_functions {
/// Package functions can be called from within the package, but not from outside.
package fun square(x: u64): u64 {
x * x
}
/// Alternative syntax for package functions
public(package) fun cube(x: u64): u64 {
x * x * x
}
/// Public function that uses package function
public fun calculate_power(base: u64, power: u8): u64 {
if (power == 2) {
square(base) // Call package function
} else if (power == 3) {
cube(base) // Call package function
} else {
base
}
}
}
Entry Functions
Entry functions are special functions that can be called as standalone transactions from outside the module.
module my_module::entry_functions {
use std::signer;
/// Entry functions can be called as standalone transactions.
entry fun create_account(account: &signer) {
let addr = signer::address_of(account);
// Initialize account data
}
/// Entry functions can also be public, friend, or package.
public entry fun transfer_funds(
from: &signer,
to: address,
amount: u64
) {
let from_addr = signer::address_of(from);
// Transfer logic here
}
/// Entry functions with multiple parameters
entry fun update_profile(
account: &signer,
name: String,
age: u8
) {
let addr = signer::address_of(account);
// Update profile logic
}
/// Entry functions can return values (though they're typically void)
entry fun calculate_and_return(x: u64, y: u64): u64 {
x + y
}
}
Entry Function Best Practices
module my_module::entry_best_practices {
use std::signer;
use std::error;
const EINSUFFICIENT_FUNDS: u64 = 1;
const EINVALID_AMOUNT: u64 = 2;
/// Good: Entry function with proper validation
entry fun safe_transfer(
from: &signer,
to: address,
amount: u64
) {
// Validate input
assert!(amount > 0, error::invalid_argument(EINVALID_AMOUNT));
assert!(to != @0x0, error::invalid_argument(3));
let from_addr = signer::address_of(from);
// Check balance (simplified)
// let balance = get_balance(from_addr);
// assert!(balance >= amount, error::invalid_state(EINSUFFICIENT_FUNDS));
// Perform transfer
// transfer_funds(from_addr, to, amount);
}
/// Good: Entry function with clear parameter names
entry fun create_user_profile(
account: &signer,
user_name: String,
user_age: u8,
user_email: String
) {
let user_address = signer::address_of(account);
// Validate input
assert!(user_age >= 13, error::invalid_argument(4));
assert!(string::length(&user_name) > 0, error::invalid_argument(5));
// Create profile
// create_profile(user_address, user_name, user_age, user_email);
}
}
View Functions
View functions are read-only functions that can be called without a transaction. They are marked with the #[view]
attribute.
module my_module::view_functions {
use std::signer;
struct UserProfile has key, store {
name: String,
age: u8,
email: String,
}
/// View function - can be called without a transaction
#[view]
public fun get_user_name(user_addr: address): String {
assert!(exists<UserProfile>(user_addr), 0);
borrow_global<UserProfile>(user_addr).name
}
/// View function that returns multiple values
#[view]
public fun get_user_profile(user_addr: address): (String, u8, String) {
assert!(exists<UserProfile>(user_addr), 0);
let profile = borrow_global<UserProfile>(user_addr);
(profile.name, profile.age, profile.email)
}
/// View function with complex logic
#[view]
public fun is_user_adult(user_addr: address): bool {
if (!exists<UserProfile>(user_addr)) {
return false
};
let profile = borrow_global<UserProfile>(user_addr);
profile.age >= 18
}
/// View function that doesn't access global storage
#[view]
public fun calculate_discount(price: u64, discount_percent: u8): u64 {
let discount = (price * (discount_percent as u64)) / 100;
price - discount
}
}
View Function Best Practices
module my_module::view_best_practices {
/// Good: View function with proper error handling
#[view]
public fun get_user_balance(user_addr: address): u64 {
if (!exists<UserBalance>(user_addr)) {
return 0
};
borrow_global<UserBalance>(user_addr).amount
}
/// Good: View function that validates input
#[view]
public fun calculate_compound_interest(
principal: u64,
rate: u64,
time: u64
): u64 {
// Validate input parameters
assert!(principal > 0, 0);
assert!(rate <= 100, 0); // Rate as percentage
assert!(time > 0, 0);
// Calculate compound interest
let rate_decimal = rate / 100;
let amount = principal * ((1 + rate_decimal) ^ time);
amount - principal
}
}
Test-Only Functions
Test-only functions are only available during testing and are marked with the #[test_only]
attribute.
module my_module::test_functions {
use std::signer;
/// Test-only functions are only available during testing.
#[test_only]
fun create_test_user(account: &signer, name: String): address {
let addr = signer::address_of(account);
// Create test user profile
// move_to(account, UserProfile { name, age: 25, email: string::utf8(b"test@example.com") });
addr
}
/// Test-only functions can be public
#[test_only]
public fun setup_test_environment(): vector<u64> {
vector[1, 2, 3, 4, 5]
}
/// Test function that tests other functions
#[test_only]
public fun test_add_function() {
let result = add(2, 3);
assert!(result == 5, 0);
}
/// Test function with test account
#[test(account = @0x1)]
public fun test_with_account(account: signer) {
let addr = signer::address_of(&account);
// Test logic here
}
/// Private function used by tests
fun add(x: u64, y: u64): u64 {
x + y
}
}
Test Function Best Practices
module my_module::test_best_practices {
use std::signer;
use std::error;
const EINVALID_INPUT: u64 = 1;
/// Good: Comprehensive test function
#[test_only]
public fun test_validation_function() {
// Test valid input
let result1 = validate_input(50);
assert!(result1 == true, 0);
// Test invalid input
let result2 = validate_input(0);
assert!(result2 == false, 0);
let result3 = validate_input(1001);
assert!(result3 == false, 0);
}
/// Good: Test function with multiple test cases
#[test_only]
public fun test_calculation_function() {
// Test case 1: Normal calculation
let result1 = calculate_discount(100, 10);
assert!(result1 == 90, 0);
// Test case 2: No discount
let result2 = calculate_discount(100, 0);
assert!(result2 == 100, 0);
// Test case 3: Full discount
let result3 = calculate_discount(100, 100);
assert!(result3 == 0, 0);
}
/// Good: Test function with test account
#[test(account = @0x1)]
public fun test_account_operations(account: signer) {
let addr = signer::address_of(&account);
// Test account creation
// create_account(&account);
// assert!(exists<UserProfile>(addr), 0);
// Test account operations
// update_profile(&account, string::utf8(b"Test User"), 25);
// let profile = get_user_profile(addr);
// assert!(profile.0 == string::utf8(b"Test User"), 0);
}
/// Helper functions for tests
fun validate_input(value: u64): bool {
value > 0 && value <= 1000
}
fun calculate_discount(price: u64, discount_percent: u8): u64 {
let discount = (price * (discount_percent as u64)) / 100;
price - discount
}
}
Inline Functions
Inline functions are small functions that can be inlined by the compiler for better performance.
module my_module::inline_functions {
/// Inline function - compiler may inline this for better performance
#[inline]
public fun max(a: u64, b: u64): u64 {
if (a > b) a else b
}
/// Inline function with multiple parameters
#[inline]
public fun clamp(value: u64, min: u64, max: u64): u64 {
if (value < min) {
min
} else if (value > max) {
max
} else {
value
}
}
/// Inline function used in calculations
#[inline]
public fun is_even(n: u64): bool {
n % 2 == 0
}
/// Public function that uses inline functions
public fun process_numbers(a: u64, b: u64, c: u64): u64 {
let max_value = max(a, b); // Inline function call
let clamped = clamp(c, 0, 100); // Inline function call
if (is_even(max_value)) { // Inline function call
max_value + clamped
} else {
max_value - clamped
}
}
}
Inline Function Best Practices
module my_module::inline_best_practices {
/// Good: Small, simple inline functions
#[inline]
public fun square(x: u64): u64 {
x * x
}
/// Good: Inline function for frequently used operations
#[inline]
public fun is_positive(n: u64): bool {
n > 0
}
/// Good: Inline function for type conversions
#[inline]
public fun u8_to_u64(value: u8): u64 {
(value as u64)
}
/// Avoid: Large inline functions (compiler may ignore inline hint)
#[inline]
public fun complex_calculation(a: u64, b: u64, c: u64): u64 {
let temp1 = a * b;
let temp2 = b * c;
let temp3 = c * a;
let result = temp1 + temp2 + temp3;
result / 3
}
}
Function Overloading and Generics
Move doesn't support function overloading, but you can use generics to create flexible functions.
module my_module::generic_functions {
/// Generic function that works with any type that has copy, drop abilities
public fun identity<T>(x: T): T {
x
}
/// Generic function with constraints
public fun add_generic<T: copy + drop>(a: T, b: T): T {
// Note: This is a simplified example
// In practice, you'd need to implement specific logic for each type
a
}
/// Generic function for vector operations
public fun get_first<T>(vec: &vector<T>): T {
assert!(vector::length(vec) > 0, 0);
*vector::borrow(vec, 0)
}
/// Generic function with multiple type parameters
public fun create_pair<T, U>(first: T, second: U): (T, U) {
(first, second)
}
}
Function Abilities and Constraints
Functions can have ability constraints that determine what types they can work with.
module my_module::function_abilities {
/// Function that requires copy ability
public fun duplicate<T: copy>(x: T): (T, T) {
(x, x)
}
/// Function that requires drop ability
public fun consume<T: drop>(x: T) {
// x is automatically dropped at the end
}
/// Function that requires store ability
public fun store_value<T: store>(x: T) {
// x can be stored in global storage
}
/// Function with multiple ability constraints
public fun process_value<T: copy + drop + store>(x: T): T {
// x can be copied, dropped, and stored
x
}
}
Best Practices Summary
Function Visibility
- Start with private: Make functions private by default
- Use public sparingly: Only expose what's necessary
- Use friend for related modules: When modules need to share functionality
- Use package for internal APIs: For functions used within the package
Entry Functions
- Validate input: Always validate parameters in entry functions
- Use clear names: Make function names descriptive
- Handle errors gracefully: Use proper error handling
- Keep them simple: Delegate complex logic to private functions
View Functions
- Mark with
#[view]
: Always mark read-only functions with#[view]
- Don't modify state: View functions should be pure
- Handle missing data: Return sensible defaults for missing data
- Optimize for queries: Make view functions efficient
Test Functions
- Use
#[test_only]
: Mark test functions appropriately - Test edge cases: Include boundary condition tests
- Use descriptive names: Make test names clear
- Test error conditions: Include tests for error cases
Inline Functions
- Keep them small: Inline functions should be simple
- Use for hot paths: Inline frequently called functions
- Don't overuse: Let the compiler decide when to inline
- Profile first: Measure performance before optimizing
By understanding and using these different function types effectively, you can create well-structured, maintainable, and efficient Move programs.