Return a closure
Return an owned closure to keep its captured data after its creating function
returns. Closure describes the returned call signature:
def counter(start: i64) -> Closure[[], Result[i64, OverflowError[i64]]]:
def [mut start]() -> Result[i64, OverflowError[i64]]:
start = start + 1
Ok(start)
def apply(f: &mut Closure[[], Result[i64, OverflowError[i64]]]) -> Result[i64, OverflowError[i64]]:
f()
def main() -> Result[(), IoError | OverflowError[i64]]:
mut first = counter(10)
mut second = counter(30)
print(apply(&mut first)?)?
print(second()?)?
print(first()?)?
Ok(())
11
31
12
apply needs an exclusive borrow because calling the counter changes its state.
For a read-only closure, use a shared parameter such as &Closure[[i64], Result[i64, OverflowError[i64]]].
Different anonymous expressions have different concrete environment types, even with identical signatures. A factory must return one concrete type on every path. Borrowing closures cannot escape their creating function. To return different owned shared-call environments, explicitly allocate a BoxedCallable.
An owned closure can capture another closure or be wrapped in Option or
Result:
def make(offset: i64) -> Option[Closure[[i64], Result[i64, OverflowError[i64]]]]:
inner = def [offset](value: i64) -> Result[i64, OverflowError[i64]]:
Ok(offset + value)
outer = def [inner](value: i64) -> Result[i64, OverflowError[i64]]:
Ok(inner(value)? * 2)
Some(outer)
def main() -> Result[(), IoError | OverflowError[i64]]:
match make(3):
case Some(calculate):
print(calculate(4)?)?
case Nothing:
print("no callback")?
Ok(())
14
Captured resources drop in reverse capture order, including nested environments.
You can call a returned closure immediately, for example counter(10)().
Its environment remains alive through the complete expression and is then
dropped. ? in a later argument also cleans up that temporary. Match an
optional closure before calling it, as in the example above.