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 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
.
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);
^****^
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
}
}