Functions are a fundamental part of any programming language, including Stylus, enabling you to encapsulate logic into reusable components.
This guide covers the syntax and usage of functions, including internal and external functions, and how to return multiple values.
A function in Stylus consists of a name, a set of parameters, an optional return type, and a body.
Just as with storage, Stylus methods are Solidity ABI equivalent. This means that contracts written in different programming languages are fully interoperable.
Functions are declared with the fn
keyword. Parameters allow the function to accept inputs, and the return type specifies the output of the function. If no return type is specified, the function returns void
.
Following is an example of a function add
that takes two uint256
values and returns their sum.
1fn add(a: uint256, b: uint256) -> uint256 {
2 return a + b;
3}
1fn add(a: uint256, b: uint256) -> uint256 {
2 return a + b;
3}
Function parameters are the inputs to a function. They are specified as a list of IDENTIFIER: Type
pairs, separated by commas.
In this example, the function add_numbers
takes two u32
parameters, a
and b
and returns the sum of the two numbers.
1fn add_numbers(a: u32, b: u32) -> u32 {
2 a + b
3}
1fn add_numbers(a: u32, b: u32) -> u32 {
2 a + b
3}
Return types in functions are an essential part of defining the behavior and expected outcomes of your smart contract methods.
Here, we explain the syntax and usage of return types in Stylus with general examples.
A function with a return type in Stylus follows this basic structure. The return type is specified after the ->
arrow.
Values are returned using the return
keyword or implicitly as the last expression of the function. In Rust and Stylus, the last expression in a function is implicitly returned, so the return
keyword is often omitted.
1pub fn function_name(&self) -> ReturnType {
2 // Function body
3}
1pub fn function_name(&self) -> ReturnType {
2 // Function body
3}
Function returning a String: This get_greeting
function returns a String
. The return type is specified as String
after the ->
arrow.
1pub fn get_greeting() -> String {
2 "Hello, Stylus!".into()
3}
1pub fn get_greeting() -> String {
2 "Hello, Stylus!".into()
3}
Function returning an Integer: This get_number
function returns an unsigned 32-bit integer (u32
).
1pub fn get_number() -> u32 {
2 42
3}
1pub fn get_number() -> u32 {
2 42
3}
Function returning a Result with Ok
and Err
variants: The perform_operation
function returns a Result<u32, CustomError>
.
The Result
type is used for functions that can return either a success value (Ok
) or an error (Err
). In this case, it returns Ok(value)
on success and an error variant of CustomError
on failure.
1pub enum CustomError {
2 ErrorVariant,
3}
4
5pub fn perform_operation(value: u32) -> Result<u32, CustomError> {
6 if value > 0 {
7 Ok(value)
8 } else {
9 Err(CustomError::ErrorVariant)
10 }
11}
1pub enum CustomError {
2 ErrorVariant,
3}
4
5pub fn perform_operation(value: u32) -> Result<u32, CustomError> {
6 if value > 0 {
7 Ok(value)
8 } else {
9 Err(CustomError::ErrorVariant)
10 }
11}
Public functions are those that can be called by other contracts.
To define a public function in a Stylus contract, you use the #[public]
macro. This macro ensures that the function is accessible from outside the contract.
Previously, all public methods were required to return a Result
type with Vec<u8>
as the error type. This is now optional. Specifically, if a method is "infallible" (i.e., it cannot produce an error), it does not need to return a Result type. Here's what this means:
Infallible methods: Methods that are guaranteed not to fail (no errors possible) do not need to use the Result
type. They can return their result directly without wrapping it in Result
.
Optional error handling: The Result
type with Vec<u8>
as the error type is now optional for methods that cannot produce an error.
In the following example, owner
is a public function that returns the contract owner's address. Since this function is infallible (i.e., it cannot produce an error), it does not need to return a Result
type.
1#[external]
2impl Contract {
3 // Define an external function to get the owner of the contract
4 pub fn owner(&self) -> Address {
5 self.owner.get()
6 }
7}
1#[external]
2impl Contract {
3 // Define an external function to get the owner of the contract
4 pub fn owner(&self) -> Address {
5 self.owner.get()
6 }
7}
Internal functions are those that can only be called within the contract itself. These functions are not exposed to external calls.
To define an internal function, you simply include it within your contract's implementation without the #[public]
macro.
The choice between public and internal functions depends on the desired level of accessibility and interaction within and across contracts.
In the followinge example, set_owner
is an internal function that sets a new owner for the contract. It is only callable within the contract itself.
1impl Contract {
2 // Define an internal function to set a new owner
3 pub fn set_owner(&mut self, new_owner: Address) {
4 self.owner.set(new_owner);
5 }
6}
1impl Contract {
2 // Define an internal function to set a new owner
3 pub fn set_owner(&mut self, new_owner: Address) {
4 self.owner.set(new_owner);
5 }
6}
To mix public and internal functions within the same contract, you should use two separate impl
blocks with the same contract name. Public functions are defined within an impl
block annotated with the #[public]
attribute, signifying that these functions are part of the contract's public interface and can be invoked from outside the contract.
In contrast, internal functions are placed within a separate impl
block that does not have the #[public]
attribute, making them internal to the contract and inaccessible to external entities.
1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5use alloc::vec;
6use stylus_sdk::alloy_primitives::Address;
7use stylus_sdk::prelude::*;
8use stylus_sdk::storage::StorageAddress;
9
10use stylus_sdk::alloy_primitives::U256;
11use stylus_sdk::storage::StorageU256;
12use stylus_sdk::console;
13
14
15#[storage]
16#[entrypoint]
17pub struct ExampleContract {
18 owner: StorageAddress,
19 data: StorageU256,
20}
21
22#[public]
23impl ExampleContract {
24 // External function to set the data
25 pub fn set_data(&mut self, value: U256) {
26 self.data.set(value);
27 }
28
29 // External function to get the data
30 pub fn get_data(&self) -> U256 {
31 self.data.get()
32 }
33
34 // External function to get the contract owner
35 pub fn get_owner(&self) -> Address {
36 self.owner.get()
37 }
38}
39
40impl ExampleContract {
41 // Internal function to set a new owner
42 pub fn set_owner(&mut self, new_owner: Address) {
43 self.owner.set(new_owner);
44 }
45
46 // Internal function to log data
47 pub fn log_data(&self) {
48 let _data = self.data.get();
49 console!("Current data is: {:?}", _data);
50 }
51}
1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5use alloc::vec;
6use stylus_sdk::alloy_primitives::Address;
7use stylus_sdk::prelude::*;
8use stylus_sdk::storage::StorageAddress;
9
10use stylus_sdk::alloy_primitives::U256;
11use stylus_sdk::storage::StorageU256;
12use stylus_sdk::console;
13
14
15#[storage]
16#[entrypoint]
17pub struct ExampleContract {
18 owner: StorageAddress,
19 data: StorageU256,
20}
21
22#[public]
23impl ExampleContract {
24 // External function to set the data
25 pub fn set_data(&mut self, value: U256) {
26 self.data.set(value);
27 }
28
29 // External function to get the data
30 pub fn get_data(&self) -> U256 {
31 self.data.get()
32 }
33
34 // External function to get the contract owner
35 pub fn get_owner(&self) -> Address {
36 self.owner.get()
37 }
38}
39
40impl ExampleContract {
41 // Internal function to set a new owner
42 pub fn set_owner(&mut self, new_owner: Address) {
43 self.owner.set(new_owner);
44 }
45
46 // Internal function to log data
47 pub fn log_data(&self) {
48 let _data = self.data.get();
49 console!("Current data is: {:?}", _data);
50 }
51}
1[package]
2name = "stylus-functions"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "=0.7.6"
8alloy-sol-types = "=0.7.6"
9mini-alloc = "0.4.2"
10stylus-sdk = "0.6.0"
11hex = "0.4.3"
12sha3 = "0.10.8"
13
14[features]
15export-abi = ["stylus-sdk/export-abi"]
16debug = ["stylus-sdk/debug"]
17
18[lib]
19crate-type = ["lib", "cdylib"]
20
21[profile.release]
22codegen-units = 1
23strip = true
24lto = true
25panic = "abort"
26opt-level = "s"
1[package]
2name = "stylus-functions"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7alloy-primitives = "=0.7.6"
8alloy-sol-types = "=0.7.6"
9mini-alloc = "0.4.2"
10stylus-sdk = "0.6.0"
11hex = "0.4.3"
12sha3 = "0.10.8"
13
14[features]
15export-abi = ["stylus-sdk/export-abi"]
16debug = ["stylus-sdk/debug"]
17
18[lib]
19crate-type = ["lib", "cdylib"]
20
21[profile.release]
22codegen-units = 1
23strip = true
24lto = true
25panic = "abort"
26opt-level = "s"