RAII

Variables in Cairo do more than just hold data in memory: they also own resources, e.g. Box<T> owns a pointer to memory, Felt252Dict owns a memory segment. Cairo enforces RAII (Resource Acquisition Is Initialization), so whenever an object goes out of scope, its destructor is called to handle the cleanup of its owned resources.

This behavior protects against soundness issues and concurrent memory accesses, ensuring that:

  1. It would be infeasible for a cheating prover to convince a verifier of a false statement, as some resources (like dictionaries) require specialized destruction logic.
  2. No memory cell is written to twice due to concurrent accesses, which would crash the VM.

Here's a quick showcase:

fn create_box() {
    // Write an integer to memory and return a pointer to it
    let _box1 = BoxTrait::new(3_u32);
    // `_box1` is dropped here: the associated pointer will no longer be accessible
}

fn main() {
    // Allocate a memory segment for an array and return a pointer to it
    let _box2 = BoxTrait::new(array![1_u8]);

    // A nested scope:
    {
        // Write an integer to memory and return a pointer to it
        let _box3 = BoxTrait::new(4_u32);

        let _pointee_2 = _box2.unbox();
        println!("Pointee 2 is: {:?}", _pointee_2);
        // `_box3` is dropped here: the associated pointer will no longer be accessible
    }
    // `_box2` has been moved to the inner scope when we accessed its inner resources, so it can no
// longer be accessed.
// let pointee_2 = _box2.unbox();
// TODO: uncomment this line and notice the compiler error.
}

Destructor

The notion of a destructor in Rust is provided through the Drop and Destruct traits. The destructor is called when the resource goes out of scope. One of the two traits is required to be implemented for every type:

  • If your type does not require any specific destructor logic, implement the Drop trait, which can trivially be derived.
  • If your type requires does specific destructor logic, applicable to any type containing a dictionary, implement the Destruct trait. It can also be derived.

Run the below example to see how the Drop trait works. When the variables in the main function goes out of scope the custom destructor will be invoked. Try to fix the error for the variable of type ToDestruct.

use core::dict::Felt252Dict;

#[derive(Drop)]
struct ToDrop {}

struct ToDestruct {
    inner: Felt252Dict<u8>,
}

fn main() {
    let _to_drop = ToDrop {};
    let _to_destruct = ToDestruct { inner: Default::default() };
    // TODO: modify the definition of ToDestruct to fix the error.
}

See also:

Box