Functions in Move are the brain of the modules. It contains logic that defines what actions needs to be performed on the global storage.
They allow developers to write reusable code to manipulate data and implement application logic. Functions in Move are deterministic, meaning they always produce the same output given the same input, ensuring reliability and predictability in blockchain operations
Let's dive a bit deeper in the world of functions with an example:
- Go to demos/functions folder.
- Setup the repo through README present there.
There are 3 contracts
- friend_1: Contains internal, public, entry functions
- friend_2: Contains public, entry and friend functions
- friend_3: Contains test cases and tries to call other contract functions
Functions are declared with the fun keyword followed by the function name, type parameters, parameters, a return type, acquires annotations, and finally the function body.
fun <identifier><[type_parameters: constraint],*>([identifier: type],*): <return_type> <acquires [identifier],*> <function_body>
Eg:
fun foo<T1, T2>(x: u64, y: T1, z: T2): (T2, T1, u64) { (z, y, x) }
All functions in move modules are internal(private) by default,i.e., they can only be called by other functions inside the same module Example: In file friend_1.move
// This function can only be called from other functions in friend_1
fun get_year(): u64{
return 2025
}
When you add public in front of any other func it can be called from any other module as well. Public functions can be called by:
- other functions defined in the same module
- functions defined in another module
Example: In file friend_1.move
// This function can be called from any module
public fun get_age(friend_address: address): u64 acquires Information {
let name_exists = exists<Information>(friend_address);
assert!(name_exists, error::not_found(E_ADDRESS_NOT_FOUND));
return borrow_global<Information>(friend_address).birth_year
}
You can also define which module can call your functions by using a keyword friend
. This will add extra layer of check. It can be called by:
- other functions defined in the same module
- functions defined in modules which are explicitly specified in the friend list
Example: In file friend_2.move
module friends::friend_2{
friend friends::friend_1; // friend_1 module can call the friend functions but no other module can call them
public(friend) fun get_age(friend_address: address): u64 acquires Information {
let name_exists = exists<Information>(friend_address);
assert!(name_exists, error::not_found(E_ADDRESS_NOT_FOUND));
return borrow_global<Information>(friend_address).birth_year
}
}
In the above example only friend_1
is able to call the function get_age
.
These functions are the one that users use to interact with modules. Essentially, entry functions are the "main" functions of a module, and they specify where Move programs start executing. Example: In file friend_2.move
entry public fun setInformation(author: &signer,_name:string::String, _age:u64){
move_to<Information>(author,Information{name:_name,birth_year:_age});
}
If public is removed from the function, other modules won't be able to call it but users will still be able to interact with the contract
If a module is reading from a global storage they should use the keyword acquire
to gain access to the storage
Example: In file friend_2.move
module friends::friend_2{
struct Information has key, drop {
name: string::String ,
birth_year: u64
}
// the below function uses the Information struct to make changes and thus it uses the acquire keyword
entry fun change_name(author: &signer,_name:string::String) acquires Information{
let author_address = signer::address_of(author);
let name_exists = exists<Information>(author_address);
assert!(name_exists, error::not_found(E_ADDRESS_NOT_FOUND));
let author_name = borrow_global_mut<Information>(author_address);
author_name.name = _name;
}
}
A function can acquire multiple structs
acquires
annotations must also be added for transitive calls within the module.
Example
module example {
struct Balance has key { value: u64 }
public fun add_balance(s: &signer, value: u64) {
move_to(s, Balance { value })
}
public fun extract_balance(addr: address): u64 acquires Balance {
let Balance { value } = move_from(addr); // acquires needed
value
}
public fun extract_and_add(sender: address, receiver: &signer) acquires Balance {
let value = extract_balance(sender); // acquires needed here
add_balance(receiver, value)
}
}
If a function returns something it should specify the return type. Also, when returning data from function one should not use ;
Example: In file friend_2.move
#[view]
public fun get_name(author: address): string::String acquires Information{
let name_exists = exists<Information>(author);
assert!(name_exists, error::not_found(E_ADDRESS_NOT_FOUND));
return borrow_global<Information>(author).name // ; is not used as it returns a value
}
When a module calls another module function it should mention the location of the function and params. It should follow the format: [Address]::[Module]::[Function](<PARAMS>)
Example: In file friend_3.move
friends::friend_2::setInformation(test_addr,string::utf8(b"Move"),21);
Function names can start with letters a to z or letters A to Z. After the first character, function names can contain underscores _, letters a to z, letters A to Z, or digits 0 to 9.
fun FOO() {}
fun bar_42() {}
fun _bAZ19() {} // Invalid function name '_bAZ19'Function names cannot start with '_'
Some functions do not have a body specified, and instead have the body provided by the VM. These functions are marked native.
Without modifying the VM source code, a programmer cannot add new native functions. Furthermore, it is the intent that native functions are used for either standard library code or for functionality needed for the given Move environment
module std::vector {
native public fun empty<Element>(): vector<Element>;
...
}
In this tutorial you learnt how to define and use different kind of functions depending on our use case. In the next tutorial we'll learn about fungible asset and coins.