Closures
Onyx has experimental support for closures. Currently, this is in the form of explicit closure, where every captured variable has to be declared before it can be used. This restriction will likely be lifted in the future when other internal details are figured out.
To declare a closure, simply add use (...)
to the procedure definition,
between the arguments and the return type.
main :: () {
x := 10
// Here, x is captured by value, and a copy is made for
// this quick procedure.
f := (y: i32) use (x) -> i32 {
return y + x
}
f(20) |> println() // Prints 30
}
Captured values can either by value, or by pointer.
To capture by pointer, simply place a &
in front of the variable name.
main :: () {
x := 10
// Here, x is captured by pointer
f := (y) use (&x) => {
*x = 20
return y + *x
}
f(20) |> println() // Prints 40
println(x) // Prints 20
}
Currying
A form of function currying is possible in Onyx using chained quick procedures, and passing previous arguments to each subsequent quick procedure.
add :: (x: i32) => (y: i32) use (x) => (z: i32) use (x, y) => {
return x + y + z
}
main :: () {
partial_sum := add(1)(2)
sum1 := partial_sum(3)
sum2 := partial_sum(10)
println(sum1)
println(sum2)
}
Internal details of Closures
Every time a closure is encountered at runtime, a memory allocation must be made
to accommodate the memory needed to store the captured values. To do this, a
builtin procedure called __closure_block_allocate
is called. This procedure is
implemented by default to invoke context.closure_allocate
. By default,
context.closure_allocate
allocates a buffer from the temporary allocator. If you want to change
how closures are allocated, you can change this procedure pointer to do something
different.
main :: () {
context.closure_allocate = (size: i32) -> rawptr {
printf("Allocating {} bytes for closure.\n", size)
// Allocate out of the heap
return context.allocator->alloc(size)
}
x := 10
f := (y: i32) use (x) => {
return y + x
}
f(20) |> println() // Prints 30
}