As input parameters

While Cairo chooses how to capture variables on the fly mostly without type annotation, this ambiguity is not allowed when writing functions. When taking a closure as an input parameter, the closure's complete type must be annotated using one of two traits, and they're determined by what the closure does with captured value. In order of decreasing restriction, they are:

  • Fn: the closure uses the captured value by snapshot (@T)
  • FnOnce: the closure uses the captured value by value (T)

On a variable-by-variable basis, the compiler will capture variables in the least restrictive manner possible.

For instance, consider a parameter annotated as FnOnce. This specifies that the closure may capture by @T or T, but the compiler will ultimately choose based on how the captured variables are used in the closure.

This is because if a move is possible, then any type of snapshot should also be possible. Note that the reverse is not true. If the parameter is annotated as Fn, then capturing variables by T is not allowed. However, @T is allowed.

In the following example, try swapping the usage of Fn and FnOnce to see what happens:

Note: Cairo 2.11 provides an experimental feature allowing you to specify the associated type of trait, using experimental-features = ["associated_item_constraints"] in your Scarb.toml.

// A function which takes a closure as an argument and calls it.
// <F> denotes that F is a "Generic type parameter"
fn apply<F, +Drop<F>, impl func: core::ops::FnOnce<F, ()>, +Drop<func::Output>>(f: F) {
    // ^ TODO: Try changing this to `Fn`.
    f();
}

// A function which takes a closure and returns a `u32`.
fn apply_to_3<F, +Drop<F>, impl func: core::ops::Fn<F, (u32,)>[Output: u32]>(f: F) -> u32 {
    // The closure takes a `u32` and returns a `u32`.
    f(3)
}

fn main() {
    // A non-copy type.
    let greeting: ByteArray = "hello";
    let farewell: ByteArray = "goodbye";

    // // Capture 2 variables: `greeting` by snapshot and
    // // `farewell` by value.
    let diary = 
        || {
            // `greeting` is by snapshot: requires `Fn`.
            println!("I said {}.", greeting);

            // Using farewell by value requires `FnOnce`.
            // Convert farewell to uppercase to demonstrate value capture through `into_iter`
            let mut iter = farewell.into_iter();
            let uppercase: ByteArray = iter.map(|c| if c >= 'a' {
                c - 32
            } else {
                c
            }).collect();
            println!("Then I screamed {}!", uppercase.clone());
        };

    // Call the function which applies the closure.
    // apply(diary);
    diary();

    // `double` satisfies `apply_to_3`'s trait bound
    let double = |x: u32| 2 * x;

    println!("3 doubled: {}", apply_to_3(double));
}

See also:

Fn, Generics, and FnOnce