List
By default, there is no list type supported in Cairo, but you can use Alexandria. You can refer to the Alexandria documentation for more details.
What is List
?
An ordered sequence of values that can be used in Starknet storage:
#[storage]
struct Storage {
amounts: List<u128>
}
Interface
trait ListTrait<T> {
fn len(self: @List<T>) -> u32;
fn is_empty(self: @List<T>) -> bool;
fn append(ref self: List<T>, value: T) -> u32;
fn get(self: @List<T>, index: u32) -> Option<T>;
fn set(ref self: List<T>, index: u32, value: T);
fn pop_front(ref self: List<T>) -> Option<T>;
fn array(self: @List<T>) -> Array<T>;
}
List
also implements IndexView
so you can use the familiar bracket notation to access its members:
let second = self.amounts.read()[1];
Note that unlike get
, using this bracket notation panics when accessing an out of bounds index.
Support for custom types
List
supports most of the corelib types out of the box. If you want to store a your own custom type in a List
, it has to implement the Store
trait. You can have the compiler derive it for you using the #[derive(starknet::Store)]
attribute.
Caveats
There are two idiosyncacies you should be aware of when using List
- The
append
operation costs 2 storage writes - one for the value itself and another one for updating the List's length - Due to a compiler limitation, it is not possible to use mutating operations with a single inline statement. For example,
self.amounts.read().append(42);
will not work. You have to do it in 2 steps:
let mut amounts = self.amounts.read();
amounts.append(42);
Dependencies
Update your project dependencies by in the Scarb.toml
file:
[dependencies]
(...)
alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git" }
For example, let's use List
to create a contract that tracks a list of amounts and tasks:
#[derive(Copy, Drop, Serde, starknet::Store)]
pub struct Task {
pub description: felt252,
pub status: felt252
}
#[starknet::interface]
pub trait IListExample<TContractState> {
fn add_in_amount(ref self: TContractState, number: u128);
fn add_in_task(ref self: TContractState, description: felt252, status: felt252);
fn is_empty_list(self: @TContractState) -> bool;
fn list_length(self: @TContractState) -> u32;
fn get_from_index(self: @TContractState, index: u32) -> u128;
fn set_from_index(ref self: TContractState, index: u32, number: u128);
fn pop_front_list(ref self: TContractState);
fn array_conversion(self: @TContractState) -> Array<u128>;
}
#[starknet::contract]
pub mod ListExample {
use super::Task;
use alexandria_storage::list::{List, ListTrait};
#[storage]
pub struct Storage {
amount: List<u128>,
tasks: List<Task>
}
#[abi(embed_v0)]
impl ListExample of super::IListExample<ContractState> {
fn add_in_amount(ref self: ContractState, number: u128) {
let mut current_amount_list = self.amount.read();
current_amount_list.append(number).unwrap();
}
fn add_in_task(ref self: ContractState, description: felt252, status: felt252) {
let new_task = Task { description: description, status: status };
let mut current_tasks_list = self.tasks.read();
current_tasks_list.append(new_task).unwrap();
}
fn is_empty_list(self: @ContractState) -> bool {
let mut current_amount_list = self.amount.read();
current_amount_list.is_empty()
}
fn list_length(self: @ContractState) -> u32 {
let mut current_amount_list = self.amount.read();
current_amount_list.len()
}
fn get_from_index(self: @ContractState, index: u32) -> u128 {
self.amount.read()[index]
}
fn set_from_index(ref self: ContractState, index: u32, number: u128) {
let mut current_amount_list = self.amount.read();
current_amount_list.set(index, number).unwrap();
}
fn pop_front_list(ref self: ContractState) {
let mut current_amount_list = self.amount.read();
current_amount_list.pop_front().unwrap().unwrap();
}
fn array_conversion(self: @ContractState) -> Array<u128> {
let mut current_amount_list = self.amount.read();
current_amount_list.array().unwrap()
}
}
}
istings/advanced-concepts/using_lists/src/contract.cairo