Skip to content
This repository has been archived by the owner on Sep 25, 2024. It is now read-only.

Latest commit

 

History

History
106 lines (79 loc) · 3.82 KB

10. Ownership, references, snapshots II.md

File metadata and controls

106 lines (79 loc) · 3.82 KB

Ownership, references and Snapshots II

In the previous chapter, we introduced the concept of Ownership as can be applied to Cairo, in this chapter we are going to take a deeper dive into Ownerships by understanding how References and Snapshots works.

References

References acts as pointers to the address at which a data (mostly owned by some other variable) is stored. Unlike a pointer though, a reference is guaranteed to point to a valid value of a particular type for the life of that reference.

References in Cairo are required to be passed as mutable variables, as they allow you to mutate the value passed while sustaining the original ownership by returning it automatically at the end of the function execution.

A mutable reference can be declared using the ref keyword:

use array::ArrayTrait;

fn main() {
    let mut arr = ArrayTrait::<felt252>::new();
    let arr_len: u32 = calc_arr_len(ref arr);
    let arr_length = arr.len();
    assert(arr_len == arr_length, 'length error');
}

fn calc_arr_len(ref arr: Array<felt252>) -> u32 {
    arr.len()
}

In the code above, we declare a new mutable array arr, then we pass this new array by referencing to the calc_arr_len function, which calculates and returns the length of the array.

As you can notice arr is still in scope and was reused in the next line afterwards, without throwing the variable was moved error. This is because we never passed ownership of the arr variable to the calc_arr_len function, but rather passed a reference to arr.

Snapshots

In Cairo, a snapshot is an immutable view of a value at a certain point in time. Similar to References, it enables you manage ownerships, but unlike References, Snapshots can not be mutable.

We use snapshots by appending variables with @:

use array::ArrayTrait;

fn main() {
    let mut arr = ArrayTrait::<felt252>::new();
    let arr_len: u32 = calc_arr_len(@arr);
    let arr_length = arr.len();
    assert(arr_len == arr_length, 'length error');
}

fn calc_arr_len(arr: @Array<felt252>) -> u32 {
    arr.len()
}

The code snippet does same thing as the first example, but in this case we make use of Snapshots instead. Its also important to note that snapshots like we mentioned earlier cannot be mutated. For example the code snippet below will throw an error:

use array::ArrayTrait;

fn main() {
    let mut arr = ArrayTrait::<felt252>::new();
    let arr_len: u32 = calc_arr_len(@arr);
    let arr_length = arr.len();
    assert(arr_len == arr_length, 'length error');
}

fn calc_arr_len(arr: @Array<felt252>) -> u32 {
    arr.append(4);
    arr.len()
}

Here we try to add a new element to arr. But since what we rather have is a snapshot and not a reference, the append method is intentionally not implemented for snapshots and as such we'll run into the error below:

error: Method `append` not found on type "@core::array::Array::<core::felt252>". Did you import the correct trait and impl?
 --> ownership.cairo:11:9
    arr.append(4);
        ^****^

Desnapping Snapshots

Snapshots as we stated earlier, cannot be modified and as such if you try to modify something which is being passed as a snapshot, the compiler panics.

You can convert snapshots into regular values using the desnap operator *, except for arrays (as they do not implement the Copy trait).

Here's an example use case of a desnap operator:

#[derive(Copy, Drop)]
struct Rectangle {
    width: u64,
    height: u64,
}

trait RectangleTrait {
    fn area(self: @Rectangle) -> u64;
    fn can_hold(self: @Rectangle, other: @Rectangle) -> bool;
}

impl RectangleImpl of RectangleTrait {
    fn area(self: @Rectangle) -> u64 {
        *self.width * *self.height
    }

    fn can_hold(self: @Rectangle, other: @Rectangle) -> bool {
        *self.width > *other.width & *self.height > *other.height
    }
}