Function Visibility
In Starknet contracts, functions can have two types of visibility:- External Functions: Can be called by anyone, including other contracts and users
- Internal Functions: Can only be called by other functions within the same contract
State Mutability
Every function in a contract can either modify or just read the contract’s state. This behavior is determined by how we pass theContractState
parameter:
-
State-Modifying Functions: Use
ref self: ContractState
- Can read and write to storage
- Require a transaction to execute
- Cost gas to run
-
View Functions: Use
self: @ContractState
- Can only read from storage
- Can be called directly through an RPC node
- Free to call (no transaction needed)
Internal functions follow the same state mutability rules as external functions. The only difference is who can call them.
Implementation
External Functions
For external functions (both state-modifying and view), you need:-
Interface Definition
- Defined with
#[starknet::interface]
attribute - Lists all functions that can be called externally
- Functions can be called as transactions or view calls
- Part of the contract’s public API
- Defined with
-
Interface Implementation
- Uses
#[abi(embed_v0)]
attribute - Becomes part of the contract’s ABI (Application Binary Interface)
- ABI defines how to interact with the contract from outside
- Must implement all functions defined in the interface
- Uses
Internal Functions
For internal functions, there are two options:-
Implementation Block
- Can use
#[generate_trait]
attribute - Recommended for functions that need
ContractState
access - Sometimes prefixed with
_
to indicate internal use
- Can use
-
Direct Contract Body
- Functions defined directly in the contract
- Recommended for pure functions
- Useful for helper functions and calculations
Example
Here’s a complete example demonstrating these concepts:Cairo contracts can implement multiple interfaces and have multiple internal implementation blocks. This is not only possible but recommended because it:
- Keeps each implementation block focused on a single responsibility
- Makes the code more maintainable and easier to test
- Simplifies the implementation of standard interfaces
- Allows for better organization of related functionality