Learning Cairo - Common Collections (5)

Learning Cairo - Common Collections (5)

Prerequisites

Install cairo-run.

Arrays

Arrays can store a series of data of the same type. When using arrays, you need to first import the array::ArrayTrait trait.

The concept of arrays also exists in Solidity. We can define an array with a length of 2 and type uint256 in Solidity as follows:

uint256[] memory array = new uint256[](2);

In Cairo, we can define an array of type u128 as follows:

let mut arr = ArrayTrait::<u128>::new();

You can also not specify the type, and Cairo will infer it from the data added:

let mut arr = ArrayTrait::new();
arr.append(0);
arr.append(1);
arr.append(2);

Cairo’s arrays, compared to Solidity’s arrays, have some modification restrictions. Due to Cairo’s memory limitations, array content cannot be modified. Operations on arrays are limited to reading, appending (append), and removing from the head (pop_front).

Update Array

Adding Elements

We can use the append() method to add elements to the array:

let mut a = ArrayTrait::new();
a.append(10);
a.append(1);
a.append(2);

Removing Elements

We can use the pop_front() method to remove elements from the array. This method will return an Option containing the removed element. If the array is empty, it will return Option::None.

use option::OptionTrait;
use array::ArrayTrait;
use debug::PrintTrait;

fn main() {
    let mut a = ArrayTrait::new();
    a.append(10);
    a.append(1);
    a.append(2);

    let first_value = a.pop_front().unwrap();
    first_value.print(); // print '10'

    let second_value = a.pop_front().unwrap();
    second_value.print(); // print '1'

    let third_value = a.pop_front().unwrap();
    third_value.print(); // print '2'

    // let null_value = a.pop_front().unwrap();
    // null_value.print(); // panic
}

In the code above, 3 elements are added and then removed. If we try to remove an element again, it will cause an error:

Run panicked with [29721761890975875353235833581453094220424382983267374 ('Option::unwrap failed.'), ].

Cairo’s memory is immutable, so the above append and pop_front operations do not directly modify memory but use updated pointers to achieve the effect.

Reading Array Elements

In Solidity, we can use arr[index] to get the value in the array. In Cairo, we can use arr.at(index) or arr.get(index) to read the elements in the array. They return different types, where arr.at(index) is similar to arr[index] in Solidity.

The get() method returns an Option<Box<@T>>. If the element exists, it returns an option of the Box type containing a snapshot of the element at the corresponding index. The Box type is a smart pointer type in Cairo. If the element does not exist, get() will return None. The get() method handles some error conditions gracefully, such as array out-of-bounds. Using get() can avoid panic when encountering errors.

Compared to the get() method, the at() function is more straightforward. It directly returns a snapshot of

the element at the corresponding index and uses unbox() to unpack. If the index is out of bounds, it will cause a panic.

In short, Cairo provides more ways to read array elements than Solidity. If the array is out of bounds in Solidity, it will cause an error. Cairo provides both a similar at() method and a more graceful get() method to handle errors.

use array::ArrayTrait;
use debug::PrintTrait;
fn main() {
    let mut a = ArrayTrait::new();
    a.append(0);
    a.append(1);

    let first = *a.at(0_usize);
    let second = *a.at(1_usize);

    first.print();
    second.print();

    // let third = *a.at(2_usize);
}

In the above example, we use at to read the elements in the array. In the last line, if the index is out of bounds, it will directly cause a panic.

If we use get(), we can handle errors more gracefully:

use array::ArrayTrait;
use box::BoxTrait;
fn main() -> u128 {
    let mut arr = ArrayTrait::<u128>::new();
    arr.append(100_u128);
    let index_to_access =
        1_usize;        // Change this value to see different results, what would happen if the index doesn't exist ?
    match arr.get(index_to_access) {
        Option::Some(x) => {
            *x.unbox()  // Don't worry about * for now, if you are curious see Chapter 3.2 #desnap operator
                        // It basically means "transform what get(idx) returned into a real value"
        },
        Option::None(_) => {
            let mut data = ArrayTrait::new();
            data.append('out of bounds');
            panic(data)
        }
    }
}

Array Length

We can use the len() method to get the length of the array, which returns a usize type.

use array::ArrayTrait;
use debug::PrintTrait;
fn main() {
    let mut a = ArrayTrait::new();
    a.append(0);
    a.append(1);
    let length = a.len();
    length.print();
}

In Solidity, we use the length attribute to get the array length:

uint256[] memory arr = new uint256[](2);
uint256 len = arr.length;

Determining Whether an Array is Empty

use array::ArrayTrait;
use debug::PrintTrait;
fn main() {
    let mut a = ArrayTrait::new();
    a.append(0);
    a.append(1);

    let empty = a.is_empty();
    empty.print();
}

Using Enum to Store Multiple Types

If you want to store multiple types in an array, you can use Enum to define a custom data type that stores multiple types:

use array::ArrayTrait;
use traits::Into;

#[derive(Copy, Drop)]
enum Data {
    Integer: u128,
    Felt: felt252,
    Tuple: (u32, u32),
}

fn main() {
    let mut messages: Array<Data> = ArrayTrait::new();
    messages.append(Data::Integer(100_u128));
    messages.append(Data::Felt('hello world'));
    messages.append(Data::Tuple((10_u32, 30_u32)));
}

Span

Span is a structure that represents a snapshot of an array. It’s designed to provide safe and controlled access to array elements without modifying the original array. Span is especially useful for ensuring data integrity

and avoiding borrowing issues when passing arrays between functions or performing read-only operations.

Apart from the append() method, all other methods provided by Array can be used for Span.

To create a Span of an array, you need to call the span() method:

let span = array.span();

Summary

In this article, we learned about an important collection type in Cairo - the array, as well as its various methods. We can use append() and pop_front() to manipulate the array, use at() and get() to access array elements, use len() to get the array length, and use is_empty() to check if the array is empty. The types of elements in the array must be consistent, but we can also use Enum to create an array that stores multiple types. We can use span() to create a snapshot of the array.

Call out StarkNet Beta Testers!

Reddio is building developer tools for StarkNet to help you accelerate the process to develop StarkNet applications. We are inviting all of StarkNet developers to join our beta testing group, try out brand-new features and tell us what you think.

https://share.hsforms.com/1E88oQkqMSJifUV1CqR_WrQd30xn

Low latency and free Starknet node awaits!

For a limited time, Reddio is offering unrestricted access to its high-speed StarkNet Node, completely free of charge. This is an unparalleled opportunity to experience the fastest connection with the lowest delay. All you need to do is register an account on Reddio at https://dashboard.reddio.com/ and start exploring the limitless possibilities.

You can discover why Reddio claims the fastest connection by reading more here.