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