Functions
Learn about functions in Move, which are used to perform operations.
Functions
Functions are the fundamental building blocks of Move programs, enabling code organization, reusability, and modularity. Move supports both module functions (reusable across transactions) and script functions (single-use transaction entry points).
Key characteristics:
- Statically typed: All parameters and return types must be explicitly declared
- Module-scoped: Functions belong to specific modules and follow visibility rules
- Resource-aware: Special annotations for global storage access
Function Declaration
Functions use the fun keyword followed by a structured declaration syntax that ensures type safety and clear interfaces:
Syntax structure:
fun <identifier><[type_parameters: constraint],*>([identifier: type],*): <return_type> <acquires [identifier],*> <function_body>Complete example:
fun process_transaction<T: store>(amount: u64, data: T): (bool, T) acquires Account {
// function body
(true, data)
}Visibility and Access Control
Move enforces strict module-level encapsulation through visibility modifiers, ensuring controlled access to functionality:
Visibility levels:
- Private (default): Only callable within the defining module
- Public: Callable from any module or script
- Public(friend): Callable only from explicitly trusted modules
- Entry: Can serve as transaction entry points
module 0x42::defi_pool {
fun calculate_fees(): u64 { 100 }
fun get_pool_fees(): u64 { calculate_fees() } // valid - same module
}
module 0x42::trading_bot {
fun estimate_costs(): u64 {
0x42::defi_pool::calculate_fees() // ERROR!
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 'calculate_fees' is private to '0x42::defi_pool'
}
}script {
fun calls_m_foo(): u64 {
0x42::defi_pool::calculate_fees() // ERROR!
// ^^^^^^^^^^^^ 'foo' is internal to '0x42::m'
}
}To allow access from other modules or from scripts, the function must be declared public or public(friend).
Public Functions
Public functions allow access to the function from any module or script. As shown in the following example, a public function can be called by:
- other functions defined in the same module,
- functions defined in another module, or
- the function defined in a script.
module 0x42::defi_pool {
public fun get_pool_balance(): u64 { 1000000 }
fun internal_calculation(): u64 { get_pool_balance() } // valid - same module
}
module 0x42::trading_bot {
fun check_liquidity(): u64 {
0x42::defi_pool::get_pool_balance() // valid - public access
}
}script {
fun calls_m_foo(): u64 {
0x42::m::foo() // valid
}
}Public(friend) Visibility
The public(friend) visibility modifier provides controlled access between trusted modules. It's more restrictive than public but more permissive than private visibility.
Access rules for public(friend) functions:
- Functions within the same module can call them
- Functions in explicitly declared friend modules can call them
- Functions in non-friend modules cannot call them
- Script functions cannot call them (scripts cannot be declared as friends)
module 0x42::vault_core {
friend 0x42::vault_manager; // friend declaration
public(friend) fun access_vault_funds(): u64 { 50000 }
fun internal_operation(): u64 { access_vault_funds() } // valid - same module
}
module 0x42::vault_manager {
fun manage_funds(): u64 {
0x42::vault_core::access_vault_funds() // valid - trusted friend
}
}
module 0x42::external_user {
fun try_access(): u64 {
0x42::vault_core::access_vault_funds() // ERROR!
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 'access_vault_funds' can only be called from a 'friend' of module '0x42::vault_core'
}
}script {
fun calls_m_foo(): u64 {
0x42::m::foo() // ERROR!
// ^^^^^^^^^^^^ 'foo' can only be called from a 'friend' of module '0x42::m'
}
}Entry Functions
Entry functions serve as transaction entry points, enabling direct invocation from external clients while maintaining Move's safety guarantees:
Entry function characteristics:
- Transaction entry points: Can be called directly by transactions
- Client interfaces: Primary way external applications interact with modules
- Flexible visibility: Can be public, private, or friend-restricted
- Still callable internally: Other Move functions can invoke them
Design benefits:
- Clear interfaces: Explicitly marks functions intended for external use
- Security boundaries: Helps identify transaction entry points
- Composability: Entry functions can call other functions normally
module 0x42::nft_marketplace {
public entry fun create_listing(price: u64): u64 { price }
fun internal_setup(): u64 { create_listing(100) } // valid - internal call
}
module 0x42::auction_house {
fun start_auction(): u64 {
0x42::nft_marketplace::create_listing(500) // valid - cross-module call
}
}
module 0x42::trading_platform {
public entry fun launch_sale(): u64 {
0x42::nft_marketplace::create_listing(1000) // valid - entry calling entry
}
}script {
fun calls_m_foo(): u64 {
0x42::m::foo() // valid!
}
}Private entry functions provide transaction entry points while maintaining module privacy:
module 0x42::admin_panel {
entry fun system_initialize(): u64 { 42 } // private entry - transaction accessible but not cross-module
}
module 0x42::user_interface {
fun setup_system(): u64 {
0x42::admin_panel::system_initialize() // ERROR!
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 'system_initialize' is private to '0x42::admin_panel'
}
}Key insight: Entry functions can be private, allowing transaction access while preventing cross-module calls.
Function Naming
Move enforces specific naming conventions for functions to ensure consistency and readability:
Naming rules:
- First character: Letters (a-z, A-Z) only
- Subsequent characters: Letters, digits (0-9), underscores (_)
- Convention: snake_case is strongly recommended
- Restrictions: Cannot start with underscore or digit
// Valid function names
fun process_payment() {}
fun calculateFees() {} // valid but not recommended (prefer snake_case)
fun handle_nft_transfer() {}
fun get_balance_v2() {}
// Invalid function names
fun _private_func() {} // ERROR: cannot start with underscore
fun 2nd_attempt() {} // ERROR: cannot start with digit
fun process-payment() {} // ERROR: hyphens not allowedBest practices:
- Use descriptive, action-oriented names
- Follow snake_case convention consistently
- Avoid abbreviations that reduce clarity
- Consider the function's purpose in naming
Generic Functions
Generic functions enable code reuse across different types while maintaining type safety through parameterized types:
Generic syntax:
- Type parameters: Declared in angle brackets
<T, U> - Constraints: Optional ability requirements
<T: copy + drop> - Usage: Type parameters can be used in parameters, return types, and function body
// Simple generic function
fun swap<T>(x: T, y: T): (T, T) { (y, x) }
// Generic with constraints
fun duplicate_and_store<T: copy + store>(item: T): (T, T) {
(copy item, item)
}
// Multiple type parameters
fun process_pair<Asset: store, Metadata: copy + drop>(
asset: Asset,
meta: Metadata
): (Asset, Metadata) {
(asset, copy meta)
}Function Parameters
Parameters define the inputs a function accepts, with explicit type annotations ensuring type safety:
Parameter syntax:
- Format:
name: type - Multiple parameters: Comma-separated list
- Type annotations: Always required (no type inference)
- Ownership: Parameters transfer ownership unless borrowed
// Single parameter
fun calculate_fee(amount: u64): u64 { amount / 100 }
// Multiple parameters with different types
fun create_trade(trader: address, amount: u64, active: bool): Trade {
Trade { trader, amount, active }
}
// Reference parameters (borrowing)
fun read_balance(account: &Account): u64 {
account.balance
}
// Mutable reference parameters
fun update_balance(account: &mut Account, new_balance: u64) {
account.balance = new_balance;
}Parameter-less functions are common for constructors and getters:
module 0x42::token_factory {
struct TokenConfig { decimals: u8, max_supply: u64 }
// Constructor with no parameters
fun default_config(): TokenConfig {
TokenConfig { decimals: 8, max_supply: 1000000 }
}
// Getter with no parameters
fun get_current_timestamp(): u64 {
// implementation
1234567890
}
}Global Storage Access
Acquires annotations ensure safe access to global storage by explicitly declaring which resources a function will access:
Global storage operations requiring acquires:
move_from<T>(address): Moving resource from global storageborrow_global<T>(address): Immutable borrow from global storageborrow_global_mut<T>(address): Mutable borrow from global storage
module 0x42::liquidity_pool {
struct Pool has key { reserves: u64, fees_collected: u64 }
public fun initialize_pool(admin: &signer, initial_reserves: u64) {
move_to(admin, Pool { reserves: initial_reserves, fees_collected: 0 })
}
public fun withdraw_reserves(pool_address: address): u64 acquires Pool {
let Pool { reserves, fees_collected: _ } = move_from(pool_address);
reserves
}
public fun get_pool_info(pool_address: address): (u64, u64) acquires Pool {
let pool = borrow_global<Pool>(pool_address);
(pool.reserves, pool.fees_collected)
}
public fun add_fees(pool_address: address, fee_amount: u64) acquires Pool {
let pool = borrow_global_mut<Pool>(pool_address);
pool.fees_collected = pool.fees_collected + fee_amount;
}
}Transitive Acquires
Transitive acquires occur when a function calls another function that accesses global storage. The calling function must also declare the acquires:
Key rules:
- Same module: Must declare acquires for transitive calls within the module
- Cross-module: No acquires needed when calling functions from other modules
- Reason: Cross-module resource access is impossible, so no reference safety issues
module 0x42::staking_pool {
struct StakeInfo has key { amount: u64, rewards: u64 }
public fun create_stake(staker: &signer, amount: u64) {
move_to(staker, StakeInfo { amount, rewards: 0 })
}
public fun claim_rewards(staker_addr: address): u64 acquires StakeInfo {
let stake = borrow_global_mut<StakeInfo>(staker_addr);
let rewards = stake.rewards;
stake.rewards = 0;
rewards
}
// Transitive acquires - must declare StakeInfo because it calls claim_rewards
public fun compound_rewards(staker_addr: address) acquires StakeInfo {
let rewards = claim_rewards(staker_addr); // calls function that acquires StakeInfo
let stake = borrow_global_mut<StakeInfo>(staker_addr);
stake.amount = stake.amount + rewards;
}
}module 0x42::rewards_distributor {
fun distribute_rewards(staker_addr: address): u64 {
0x42::staking_pool::claim_rewards(staker_addr) // no acquires needed - cross-module
}
}Multiple Acquires
Multiple acquires allow functions to access several different resource types from global storage:
Syntax: List all acquired resources separated by commas Use cases: Functions that coordinate between multiple resource types
module 0x42::defi_protocol {
use std::vector;
struct UserAccount has key { balance: u64, locked: u64 }
struct RewardPool has key { total_rewards: u64, participants: vector<address> }
struct GovernanceVotes has key { votes: u64, proposals: vector<u64> }
// Function accessing multiple resource types
public fun participate_in_governance<T: store>(
user_addr: address,
vote_weight: u64,
proposal_id: u64,
) acquires UserAccount, RewardPool, GovernanceVotes {
// Access user account
let account = borrow_global_mut<UserAccount>(user_addr);
assert!(account.balance >= vote_weight, 1);
account.locked = account.locked + vote_weight;
// Update reward pool
let pool = borrow_global_mut<RewardPool>(user_addr);
if (!vector::contains(&pool.participants, &user_addr)) {
vector::push_back(&mut pool.participants, user_addr);
};
// Record governance vote
let votes = borrow_global_mut<GovernanceVotes>(user_addr);
votes.votes = votes.votes + vote_weight;
vector::push_back(&mut votes.proposals, proposal_id);
}
}Return Types
Return types specify what values a function produces, ensuring type safety and clear interfaces:
Basic return type syntax:
fun calculate_interest(): u64 { 500 }
fun get_user_name(): vector<u8> { b"Alice" }
fun is_valid(): bool { true }Multiple Return Values
Tuple returns enable functions to produce multiple values simultaneously:
// Trading pair information
fun get_trading_pair(): (u64, u64, bool) {
(1000, 2000, true) // (price, volume, active)
}
// User account details
fun get_account_info(addr: address): (vector<u8>, u64, bool) {
(b"trader_123", 50000, true) // (username, balance, verified)
}
// Coordinate pair
fun get_position(): (u64, u64) {
(100, 200) // (x, y)
}Destructuring multiple returns:
fun use_multiple_returns() {
let (price, volume, active) = get_trading_pair();
let (name, balance, _) = get_account_info(@0x123); // ignore verified status
}Unit Return Type
Unit type () represents "no meaningful return value" - used for functions that perform actions rather than compute values:
fun just_unit(): () { () }
fun just_unit() { () }
fun just_unit() { }Script Function Return Type
Script functions must have a return type of unit ():
script {
fun transfer_tokens() {
// transaction logic
// must return () - no other return type allowed
}
fun initialize_account() {
// setup logic
// implicitly returns ()
}
}As mentioned in the tuples section, these tuple "values" are virtual and do not exist at runtime. So for a function that returns unit (), it will not be returning any value at all during execution.
Function Body
Function bodies contain the implementation logic using expression blocks where the final expression becomes the return value:
fun calculate_fee(amount: u64): u64 {
let base_fee = 10;
let percentage_fee = amount / 100;
base_fee + percentage_fee // this expression is returned
}
fun process_payment(sender: address, amount: u64): bool {
let sender_balance = get_balance(sender);
if (sender_balance >= amount) {
deduct_balance(sender, amount);
true // return success
} else {
false // return failure
}
}Expression block characteristics:
- Sequential execution: Statements execute in order
- Final expression: Last expression becomes the return value
- No explicit return needed: Final expression is automatically returned
- Type consistency: Final expression must match declared return type
Native Functions
Native functions are implemented in the Move VM rather than in Move code, providing access to system-level operations:
Characteristics of native functions:
- VM implementation: Body provided by the virtual machine, not Move code
- Standard library: Most natives are in
stdmodules - No custom natives: Developers cannot create new native functions
Common native functions:
module std::vector {
native public fun empty<Element>(): vector<Element>;
// ...
}Calling Functions
When calling a function, the name can be specified either through an alias or fully qualified:
module 0x42::math {
public fun get_pi(): u64 { 314 }
}script {
use 0x42::example::{Self, zero};
fun call_zero() {
// With the `use` above all of these calls are equivalent
0x42::example::zero();
example::zero();
zero();
}
}Function Arguments
Arguments must be provided for every parameter when calling functions, with strict type matching:
module 0x42::trading_engine {
public fun get_base_fee(): u64 { 25 }
public fun apply_discount(fee: u64): u64 { fee * 90 / 100 }
public fun calculate_total(principal: u64, fee: u64): u64 { principal + fee }
public fun create_order(trader: address, amount: u64, price: u64): bool { true }
}Calling with correct arguments:
script {
use 0x42::trading_engine;
fun execute_trades() {
// No parameters
let base_fee = trading_engine::get_base_fee();
// Single parameter
let discounted_fee = trading_engine::apply_discount(base_fee);
// Multiple parameters
let total_cost = trading_engine::calculate_total(1000, discounted_fee);
// Complex parameter types
let success = trading_engine::create_order(@0x123, 500, 2000);
}
}Argument requirements:
- Exact count: Must provide argument for every parameter
- Type matching: Arguments must match parameter types exactly
- Order matters: Arguments passed in parameter declaration order
Generic Type Arguments
Type arguments for generic functions can be explicitly specified or automatically inferred:
module 0x42::container_utils {
public fun create_empty<T>(): vector<T> {
vector::empty<T>()
}
public fun swap<T>(x: T, y: T): (T, T) {
(y, x)
}
public fun first_element<T: copy>(v: &vector<T>): T {
*vector::borrow(v, 0)
}
}Type argument usage:
script {
use 0x42::container_utils;
fun demonstrate_generics() {
// Explicit type arguments
let empty_u64_vec = container_utils::create_empty<u64>();
let empty_bool_vec = container_utils::create_empty<bool>();
// Type inference (compiler determines types)
let (b, a) = container_utils::swap(100u64, 200u64); // infers T = u64
let (second, first) = container_utils::swap(true, false); // infers T = bool
// Mixed approach
let numbers = vector[1, 2, 3];
let first_num = container_utils::first_element<u64>(&numbers);
}
}When to use explicit type arguments:
- Ambiguous contexts: When compiler cannot infer the type
- Clarity: When explicit types improve code readability
- Empty containers: When creating empty generic containers
Return Value Mechanics
Return values are produced by the final expression in a function's body, enabling both simple and complex computations:
Simple return example:
fun calculate_tax(amount: u64): u64 {
amount * 8 / 100 // final expression becomes return value
}Complex return with intermediate calculations:
fun calculate_compound_yield(principal: u64, rate: u64, periods: u64): u64 {
let rate_per_period = rate / periods;
let compound_factor = 100 + rate_per_period;
let final_amount = principal * compound_factor / 100;
final_amount // this final expression is returned
}Conditional returns:
fun determine_fee_tier(volume: u64): u64 {
if (volume >= 1000000) {
5 // premium tier
} else if (volume >= 100000) {
10 // standard tier
} else {
25 // basic tier
}
// the if-else expression result is returned
}Explicit Return Statements
Explicit returns provide early exit from functions, especially useful in complex control flow:
Basic explicit return:
fun validate_amount(amount: u64): u64 {
if (amount == 0) return 0; // early exit
amount * 105 / 100 // normal calculation
}These two functions are equivalent. In this slightly more involved example, the function subtracts two u64 values, but returns early with 0 if the second value is too large:
fun safe_sub(x: u64, y: u64): u64 {
if (y > x) return 0;
x - y
}Note that the body of this function could also have been written as if (y > x) 0 else x - y.
However return really shines is in exiting deep within other control flow constructs. In this example, the function iterates through a vector to find the index of a given value:
use std::vector;
use std::option::{Self, Option};
fun index_of<T>(v: &vector<T>, target: &T): Option<u64> {
let i = 0;
let n = vector::length(v);
while (i < n) {
if (vector::borrow(v, i) == target) return option::some(i);
i = i + 1
};
option::none()
}Using return without an argument is shorthand for return (). That is, the following two functions are equivalent:
fun foo() { return }
fun foo() { return () }Function Visibility Summary
| Visibility | Callable From | Use Case | Example |
|---|---|---|---|
| (default) | Same module only | Internal helpers | fun calculate_internal_fee() |
public | Any module or script | Public APIs | public fun get_balance() |
public(friend) | Same module + friends | Controlled access | public(friend) fun admin_operation() |
entry | Transaction entry point | Main functions | entry fun create_account() |
Summary
Functions are the primary building blocks for organizing and reusing code in Move:
- Declaration: Use
funkeyword with parameters, return types, and optionalacquiresannotations - Visibility: Control access with
public,public(friend), orentrymodifiers - Safety:
acquiresannotations ensure safe global storage access - Flexibility: Support generics, multiple return values, and early returns
- Patterns: Common patterns include constructors, accessors, mutators, and utilities
Functions enable modular, safe, and reusable code organization in Move programs.