Function Declaration
Functions are declared using the fn keyword:
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
// Function body
return value;
}
Basic Functions
Simple Functions
Functions with no parameters or return value:
fn initialize() {
// Initialization logic
}
fn new() -> Board {
return Board {
r1: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
r2: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
r3: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
};
}
Functions with Parameters
Functions accept typed parameters:
fn mint_private(receiver: address, amount: u64) -> token {
return token {
owner: receiver,
amount: amount,
};
}
fn check_for_win(b: Board, p: u8) -> bool {
return
(b.r1.c1 == p && b.r1.c2 == p && b.r1.c3 == p) ||
(b.r2.c1 == p && b.r2.c2 == p && b.r2.c3 == p);
}
Parameter Modes
Leo supports different parameter visibility modes:
Private Parameters (Default)
Parameters are private by default:
fn transfer_private(sender: token, receiver: address, amount: u64)
-> (token, token) {
let difference: u64 = sender.amount - amount;
let remaining: token = token {
owner: sender.owner,
amount: difference,
};
let transferred: token = token {
owner: receiver,
amount: amount,
};
return (remaining, transferred);
}
Public Parameters
Mark parameters as public to expose them on-chain:
fn transfer_public(public receiver: address, public amount: u64) -> Final {
return final { finalize_transfer_public(self.caller, receiver, amount); };
}
fn mint_public(public receiver: address, public amount: u64) -> Final {
return final { finalize_mint_public(receiver, amount); };
}
Public parameters are visible on the blockchain. Use them only for data that should be publicly accessible.
Return Types
Single Return Values
Functions can return a single value:
fn get_balance() -> u64 {
return 1000u64;
}
fn create_board() -> Board {
return Board {
r1: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
r2: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
r3: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
};
}
Multiple Return Values
Return tuples for multiple values:
fn transfer_private(sender: token, receiver: address, amount: u64)
-> (token, token) {
let remaining: token = token {
owner: sender.owner,
amount: sender.amount - amount,
};
let transferred: token = token {
owner: receiver,
amount: amount,
};
return (remaining, transferred);
}
fn make_move(player: u8, row: u8, col: u8, board: Board) -> (Board, u8) {
// Game logic...
return (updated_board, winner);
}
No Return Value
Functions without a return type implicitly return ():
fn process() {
// No return statement needed
}
fn update_state() -> () {
// Explicit unit return
return ();
}
Function Types
Regular Functions
Standard functions for off-chain computation:
fn calculate_total(price: u64, quantity: u32) -> u64 {
return price * (quantity as u64);
}
fn validate_input(value: u32, min: u32, max: u32) -> bool {
return value >= min && value <= max;
}
Entry Point Functions
Functions that can be called from outside the program (transitions):
fn play() -> (Ticket, Final) {
let ticket: Ticket = Ticket {
owner: self.caller,
};
return (ticket, final { finalize_play() });
}
fn transfer_public(public receiver: address, public amount: u64) -> Final {
return final { finalize_transfer_public(self.caller, receiver, amount); };
}
Entry point functions are defined at the program scope level and can return Final to trigger on-chain state changes.
Finalize Functions
Functions that execute on-chain and can modify mappings:
final fn finalize_mint_public(public receiver: address, public amount: u64) {
let current_amount: u64 = Mapping::get_or_use(account, receiver, 0u64);
Mapping::set(account, receiver, current_amount + amount);
}
final fn finalize_transfer_public(
public sender: address,
public receiver: address,
public amount: u64
) {
let sender_amount: u64 = Mapping::get_or_use(account, sender, 0u64);
Mapping::set(account, sender, sender_amount - amount);
let receiver_amount: u64 = Mapping::get_or_use(account, receiver, 0u64);
Mapping::set(account, receiver, receiver_amount + amount);
}
See Finalize for detailed information about on-chain functions.
Special Variables
self.caller
Access the caller’s address within functions:
fn play() -> (Ticket, Final) {
let ticket: Ticket = Ticket {
owner: self.caller, // Address of the caller
};
return (ticket, final { finalize_play() });
}
fn transfer_public(public receiver: address, public amount: u64) -> Final {
return final { finalize_transfer_public(self.caller, receiver, amount); };
}
Function Composition
Calling Functions
Functions can call other functions:
fn check_for_win(b: Board, p: u8) -> bool {
return
(b.r1.c1 == p && b.r1.c2 == p && b.r1.c3 == p) ||
(b.r2.c1 == p && b.r2.c2 == p && b.r2.c3 == p);
}
fn make_move(player: u8, row: u8, col: u8, board: Board) -> (Board, u8) {
// ... update board logic
if check_for_win(updated, 1u8) {
return (updated, 1u8);
} else if check_for_win(updated, 2u8) {
return (updated, 2u8);
} else {
return (updated, 0u8);
}
}
Helper Functions
Create helper functions for reusable logic:
fn calculate_fee(amount: u64, rate: u64) -> u64 {
return (amount * rate) / 10000u64;
}
fn apply_fee(amount: u64) -> u64 {
let fee: u64 = calculate_fee(amount, 100u64); // 1% fee
return amount - fee;
}
Function Documentation
Document functions with comments:
// Returns an updated tic tac toe board with a move made by a player.
// Returns a `u8` corresponding to the player who won the game, or 0 if no one has won yet.
// - `player` : A number corresponding to a player.
// - `row` : The row of the move.
// - `col` : The column of the move.
// - `board` : A tic tac toe board.
// Assumes that `player` is either 1 or 2.
// Assumes that `row` and `col` are valid indices into the board.
fn make_move(player: u8, row: u8, col: u8, board: Board) -> (Board, u8) {
// Implementation
}
Best Practices
1. Single Responsibility
Each function should have one clear purpose:
// Good: Single purpose
fn validate_amount(amount: u64, max: u64) -> bool {
return amount > 0u64 && amount <= max;
}
fn transfer_tokens(sender: token, receiver: address, amount: u64) -> (token, token) {
// Transfer logic only
}
// Avoid: Multiple responsibilities
fn validate_and_transfer() {
// Doing too much
}
2. Descriptive Names
Use clear, descriptive function names:
// Good: Clear intent
fn mint_public()
fn transfer_private()
fn check_for_win()
// Avoid: Unclear names
fn do_stuff()
fn process()
fn handle()
Check preconditions at the start of functions:
fn make_move(player: u8, row: u8, col: u8, board: Board) -> (Board, u8) {
// Validate inputs first
assert(player == 1u8 || player == 2u8);
assert(1u8 <= row && row <= 3u8);
assert(1u8 <= col && col <= 3u8);
// Main logic
// ...
}
4. Small, Focused Functions
Break complex logic into smaller functions:
// Good: Broken into steps
fn validate_player(player: u8) {
assert(player == 1u8 || player == 2u8);
}
fn validate_position(row: u8, col: u8) {
assert(1u8 <= row && row <= 3u8);
assert(1u8 <= col && col <= 3u8);
}
fn make_move(player: u8, row: u8, col: u8, board: Board) -> (Board, u8) {
validate_player(player);
validate_position(row, col);
// Make move
}
5. Consistent Parameter Order
Use consistent parameter ordering:
// Good: Consistent order (actor, target, amount)
fn transfer_public(public receiver: address, public amount: u64)
fn mint_public(public receiver: address, public amount: u64)
Common Patterns
Factory Functions
Create instances of types:
fn create_token(owner: address, amount: u64) -> token {
return token {
owner: owner,
amount: amount,
};
}
fn create_empty_board() -> Board {
return Board {
r1: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
r2: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
r3: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
};
}
Predicate Functions
Return boolean conditions:
fn is_valid_move(row: u8, col: u8, cell: u8) -> bool {
return 1u8 <= row && row <= 3u8 &&
1u8 <= col && col <= 3u8 &&
cell == 0u8;
}
fn has_sufficient_balance(balance: u64, amount: u64) -> bool {
return balance >= amount;
}
Transform data from one form to another:
fn split_token(token: token, amount: u64) -> (token, token) {
let remaining: token = token {
owner: token.owner,
amount: token.amount - amount,
};
let new_token: token = token {
owner: token.owner,
amount: amount,
};
return (remaining, new_token);
}
Builder Pattern
Build complex structures step by step:
fn build_row(c1: u8, c2: u8, c3: u8) -> Row {
return Row { c1: c1, c2: c2, c3: c3 };
}
fn build_board(r1: Row, r2: Row, r3: Row) -> Board {
return Board { r1: r1, r2: r2, r3: r3 };
}
Finalize Pattern
Combine off-chain and on-chain computation:
// Off-chain: Create records and prepare data
fn transfer_private_to_public(
sender: token,
public receiver: address,
public amount: u64
) -> (token, Final) {
let difference: u64 = sender.amount - amount;
let remaining: token = token {
owner: sender.owner,
amount: difference,
};
return (remaining, final {
finalize_transfer_private_to_public(receiver, amount)
});
}
// On-chain: Update public state
final fn finalize_transfer_private_to_public(
public receiver: address,
public amount: u64
) {
let current_amount: u64 = Mapping::get_or_use(account, receiver, 0u64);
Mapping::set(account, receiver, current_amount + amount);
}
Next Steps
Finalize On-chain computation
Records Work with private records
Statements Control flow in functions
Operators Use operators in functions