Movement Labs LogoMovement Docs
Move Book

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 allowed

Best 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 storage
  • borrow_global<T>(address): Immutable borrow from global storage
  • borrow_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 std modules
  • 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

VisibilityCallable FromUse CaseExample
(default)Same module onlyInternal helpersfun calculate_internal_fee()
publicAny module or scriptPublic APIspublic fun get_balance()
public(friend)Same module + friendsControlled accesspublic(friend) fun admin_operation()
entryTransaction entry pointMain functionsentry fun create_account()

Summary

Functions are the primary building blocks for organizing and reusing code in Move:

  • Declaration: Use fun keyword with parameters, return types, and optional acquires annotations
  • Visibility: Control access with public, public(friend), or entry modifiers
  • Safety: acquires annotations 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.