Function and closure references: behaviour as a part
On this page
- Function references: any function of a signature
- Anonymous functions
- A reference to a C-named function
- Closures as parts
- Closures are values
- Whether the closure changes
- Where a closure may go
- Closure references: any callable of a signature
- The forms of a closure reference
- Calling a closure while a collection is borrowed
Sometimes the part you want to swap is not a type but a piece of behaviour: the order a sort uses, what a button does when it is pressed, the profile a sweep follows. At the function's layer the interface is a signature, and anything that can be called that way may stand in, as any type that meets a trait stands in for it (Traits). This page covers the two ways to hand behaviour around as a part: function references, which take any function of a signature, and closure references, which take any callable of it, whatever state it carries. Afterwards you can write code that takes its behaviour from its caller, and choose how that behaviour is held and called.
mod behaviour;
import stdio use *;
// A function reference: any function of this signature will do
fn twice(f &fn(x i32) i32, x i32) i32 {
f(f(x));
}
fn inc(x i32) i32 { x + 1; }
// A closure reference: any callable of the signature, whatever state it carries
fn sumOf(f &<mut fn(x i32) i32) i32 {
f(1) + f(2) + f(3);
}
fn main() {
print <- "twice inc: ", twice(&inc, 5), "\n";
var calls = 0;
let total = sumOf(fn(x i32) i32 { calls += 1; x * 10; });
print <- "sum: ", total, " in ", calls, " calls\n";
}
It prints:
twice inc: 7 sum: 60 in 3 calls
Try this: give sumOf a struct with a () method of the same signature instead of the closure. sumOf does not change.
This is subtyping at the function's layer (Subtyping): the signature is the interface, and the parts are settled while the program runs, through a reference. The same signature may instead be a generic constraint, settled when the program is compiled, which is on the next page. Generators, functions whose result is a sequence, are not a part of this kind; Generators: writing your own, in Recipes, has them in depth.
Function references: any function of a signature#
Note: This is implemented: borrowing a reference to a named function, holding one in a variable, passing it to a parameter that spells out its signature, with the signature's parameters named or written as their types alone, calling through it, writing an anonymous function where the reference is wanted, and giving it a closure that holds and borrows nothing.
At the function's layer, the interface is a signature, and the subtypes are the functions that have it. A function reference lets part of some code's logic be done by any of several compatible functions, chosen while the program runs. The ampersand borrows a reference to any function:
fn incr(nbr u32) u32 {
nbr + 1;
}
// The parameter's type is the signature it takes
fn dostuff(fnref &fn(u32) u32) u32 {
fnref(4); // Call the function
}
let fnref = &incr; // points to the function incr
dostuff(&incr); // 5
As with any borrowed reference, a function reference may be copied between variables and functions. It carries no lifetime restrictions, as there is no risk of a global function going away. Its type is its signature. Its parameters may be written as their types alone, as here, or with names, as in &fn(nbr u32) u32: only the types are part of the type, so either spelling accepts a reference to incr. A function whose signature differs from the type's only by a recast is accepted as well, and one that would need converting is not (Variance has the rule). Its permission is opaq, the default and the only one permitted, which allows no dereference at all, so the function's code can be neither read nor changed through it.
A function-reference parameter may stand anywhere in a parameter list. Inside a list in parentheses (a function's parameters, or a function type's own) a function type's return type ends at the comma, which goes on to the next parameter. A function type there that returns several values writes them as a tuple, in parentheses:
fn combine(f &fn(u32) u32, g &fn(n u32) (u32, u32), x u32) u32 {
var a u32;
var b u32;
a, b = g(f(x));
a + b;
}
// A field whose function takes another function and a u32
struct Applier {
pub run &fn(&fn(u32) u32, u32) u32;
}
Outside a list, as a field's or a variable's whole type, a comma after the return type adds another return type, as it does on a function's own signature: a field typed &fn(u32) u32, u32 holds a function returning two values.
A function reference is one code address, so it takes only a function: no state travels with it. Something that should take a function, a closure that carries state, or any struct with a () method of the same signature asks for one of two things instead:
- a closure reference, &<fn(u32) u32 (or &<mut fn(u32) u32 where the callable may change its state), which is compiled once and calls through the reference; a reference to a plain function, &incr, is accepted where one is wanted;
- the signature as a constraint, fn sortBy[T, F fn(a &T, b &T) i32](xs &mut Array[T], order &F), which is compiled for each type it is given and is given &byName, a closure, or a struct (A function signature as a constraint).
The one-word &fn(sig) stays what it is, and is what another Cone function taking a function takes. A C library's callback parameter takes the reference to a C-named function instead, &fn @c (sig).
Anonymous functions#
When the function you want a reference to is small, it may be written right where the reference is wanted, with no name:
dostuff(&fn(nbr u32) u32 {nbr+1;}); // 5
dostuff(&fn(nbr u32) u32 {nbr+nbr;}); // 8
An anonymous function is a function that happens to be written in place. It is made at the module level, so it sees none of the variables around it, and it is the same function every time. A closure literal, written without the ampersand, fn(nbr u32) u32 {nbr+1;} or nbr => nbr+1, may name those variables. Given where a function reference is wanted, a closure that holds and borrows nothing becomes a plain function reference, so either spelling works for such a case:
dostuff(fn(nbr u32) u32 {nbr+1;}); // a closure, here a plain function
dostuff(nbr => nbr+nbr); // the parameter's type comes from the signature
A closure that does name a variable of the code around it cannot be a function reference, which has no place to keep it, and is refused there: "This closure borrows step, so it is not a function and cannot be given where a function reference, '&fn(...)', is wanted: only a closure that holds and borrows nothing is one." The parameter is then declared &<fn(u32) u32, a closure reference.
A reference to a C-named function#
A Cone function and a C-named one (an extern function C defines, a fn @c, a function of an @c module) are called differently: C passes a struct by value in registers or through a pointer as its ABI says (Structs by value), and Cone keeps its own convention for its own functions. So the calling convention is part of a function reference's type. A reference to a C-named function has the type &fn @c (Vec3) f32, marked as fn @c marks a function; a reference to a Cone function is the plain &fn(Vec3) f32. In a declaration a name always follows the attribute, and in a type it never does, so in a type a ( after @c opens the parameters: the type's @c takes no arguments. If a second C convention is ever needed, it gets its own attribute name, not @c(...) arguments in a type; &fn @c(system) is not a spelling.
struct Vec3 { pub x f32; pub y f32; pub z f32; }
extern fn @c vec3_len(v Vec3) f32; // defined by C
fn @c doubled(v Vec3) f32 { v.x * 2.; } // a Cone function C may call
fn sum(v Vec3) f32 { v.x + v.y + v.z; } // Cone's own
fn doit(v Vec3) {
let a &fn @c (Vec3) f32 = &vec3_len; // a C-named function's reference
let b &fn @c (Vec3) f32 = &doubled; // so is this
let c &fn(Vec3) f32 = ∑ // a Cone function's
a(v); // called as C passes a struct
c(v); // called as Cone does
}
Neither converts to the other, and there is no thunk to bridge them: giving one where the other is wanted, in an initializer, an assignment, an argument, a returned value or a struct's field, is a compile error, so a call through a reference never passes its values the wrong way. A C library's callback parameter is typed &fn @c, and the Cone function handed to it is written fn @c; handing C a plain Cone function is refused:
extern fn @c c_apply(n i32, f &fn @c (Vec3) f32) f32; // C calls f
fn @c scale(v Vec3) f32 { v.x * 3.; } // C-named, so C may call it
fn twice(v Vec3) f32 { v.x * 2.; }
fn doit() {
c_apply(1, &scale); // fine
c_apply(1, &twice); // ERROR: a Cone function, where a C-named one is wanted
}
Error 1503: `&fn(Vec3) f32` is a reference to a Cone function, and `&fn @c (Vec3) f32`, a C-named function's, is wanted. C would call it in C's convention, which passes a struct differently from Cone's, so its values would arrive wrong, and neither converts to the other. Make the function C-named, `fn @c name(...)`, or put it in an `@c` module.
A closure has no C form, so it is never @c, and an anonymous function with a body has no name for @c to give C spelling to. A type's method keeps Cone's convention even when @c names its symbol (a vtable calls it by its Cone signature), so a reference to a method is a plain &fn. A closure reference (&<fn, Uni[fn]) calls what it holds in Cone's convention, so a C-named function's reference is not accepted where one is wanted: call the C function from a Cone function and make that the callable.
Closures as parts#
A function reference carries no state, and a function written in place sees nothing around it. A closure is the callable that does: a function written where it is used, which keeps state of its own from one call to the next and may borrow the variables of the code it is written in. Functions teaches writing one: its state list, what it captures, its return type and its short form. Handing one around as a part takes a few facts more: what a closure is as a value, whether calling it changes it, and where it may go. Closures handed around this way are useful for:
- the steps a cursor's chain applies to each item (map, filter, fold), written once as the methods every cursor declaring is Iterator[T] shares (adapters); a source that runs its own loop around the body of an each is not built yet;
- sequences handed out one value at a time, though a generator needs no closure;
- event-triggered callbacks, whose state guides what happens after an event;
- function-valued inputs of a library, such as the profile a sweep follows or the order a sort uses, whose numbers belong to the caller rather than to a fixed function.
Closures are values#
A closure may be thought of as a function with persistent state, but it is more accurate to see it as a value that carries one method. It is a struct with no name, whose fields are the state list's entries and a reference for each variable it borrows; since the struct has no name that can be written, a closure is held in a variable whose type is inferred, and handed on as one of the references below. Like any value it may be put on the heap, in an owner such as Uni[fn(sig)]. A function that should accept any closure of a signature, whatever the shape of its state, takes it as a closure reference, &<fn(sig), or as a generic over the signature, [F fn(sig)], which is compiled for each closure and inlined. And a closure may fill a trait with one required method, as a struct does. A closure that borrows nothing and has no state is a plain function, so where a function reference is wanted it becomes one (Anonymous functions, above).
Whether the closure changes#
A closure's one method, (), is the body. That method's self permission is not written: it follows the body. A body that only reads what the closure holds and borrows has self &. A body that changes a captured variable or a var state entry has self &mut. This decides what the closure may be used for. A closure whose () takes self &mut is held in a var variable to be called, and goes only where a callable may change its state, &mut F and &<mut fn(sig) (Generics, Closure references). Given to a parameter that promises only to read it (&F, &<fn(sig)), it is refused, and the error says that this closure changes its state. And because a closure holds a variable it changes by &mut, its body cannot lend that variable &uni (bump(&uni total) is refused): a shared reference does not see through to a unique one.
Where a closure may go#
A closure is a value of a struct of what it holds and borrows, so where it can go is decided by the rules for its fields, with nothing special for closures. Its borrows are ordinary ones (Borrowed references), so a closure that borrows a variable cannot outlive it:
- Down a call, passed to a function that uses it and returns, it may borrow any variable of the function around it, with one exception: a call that is also handed a &mut (or &uni) borrow, a method's receiver among them, of something that can hold borrows (a struct with a borrowed-reference field, a list of borrowed references) that the caller lent, since the call could store the closure's borrow there, where it would outlive the variable. That call is refused (error 1085) when the closure borrows a local of the function, or another argument is a borrow of a local, even though the callee only reads the closure. The error names the variable. A closure lists the variable, fn(x &i64) [k] bool { *x > k; }, to hold its own copy and borrow nothing. A receiver the function owns is not refused, and nor is one that cannot hold a borrow, such as a List[i64].
- Kept in a local, it blocks moving or reshaping what it borrows until its last use.
- Stored past the function or returned (in a field, in an owner such as Uni[fn(sig)], or as a returned borrow), it is refused if it borrows a local. The error names the variable and says to list it, so that the closure holds its own copy.
- Stored in the object that made it: a closure in a method that names a member borrows self, so it cannot be stored in self. The error says to copy the members it needs into its list ([tag = tag]), or to capture a counted handle to the object.
- Sent to another thread or actor, or run in a parallel each, it must be Sendable, or Shareable for the loop, judged by what it holds (Sendable and Shareable). A closure that borrows a variable is neither; the error names the variable and says to list it. A closure holding an Rc is told to hold an Arc.
- On a GPU, a closure is allowed in its static form only (Closures on the GPU).
fn returned() Uni[fn(x i32) i32] {
let k = 3;
return new Uni[fn(x i32) i32](fn(x i32) i32 { x * k; }); // ERROR
}
fn listed() Uni[fn(x i32) i32] {
let k = 3;
return new Uni[fn(x i32) i32](fn(x i32) [k] i32 { x * k; }); // its own copy of k
}
Error 1249: An owning virtual reference ('Uni[Trait]', 'Rc[Trait]') names no lifetime, so it may outlive any
borrow: the value made one may hold only global borrows, as if bounded by ''static'. This one holds a borrow
of 'k' (made at 4:32), which belongs to this function. The closure borrows 'k' because its body names it:
list it to give the closure its own copy instead, 'fn (...) [k] ... { ... }'.
And the exception for a call down a function, with a holder of borrows that the caller lent:
struct Seen {
pub items List[&i64];
pub fn count[F fn(x &i64) bool](self &mut, f F) i64 {
var n = 0i64;
var i = 0usize;
while i < self.items.len() {
if f(*self.items[i]) { n = n + 1; }
i = i + 1usize;
}
n;
}
}
fn refused(seen &mut Seen) i64 {
let k = 3i64;
seen.count(x => *x > k); // ERROR 1085: seen holds borrows, and the call could store k's borrow in it
}
fn listed(seen &mut Seen) i64 {
let k = 3i64;
seen.count(fn(x &i64) [k] bool { *x > k; }); // its own copy of k: nothing borrowed
}
fn owned() i64 {
let base = 5i64;
let k = 3i64;
var seen = new Seen(List[&i64].empty());
seen.items.push(&base);
seen.count(x => *x > k); // fine: this function owns seen
}
One more limit comes from what a closure may reach: while a collection is borrowed, a call of a closure that may change what it reaches is limited (Calling a closure while a collection is borrowed, below).
Closure references: any callable of a signature#
Note: Not built yet, for closure references: calling a closure once, which consumes it, through an owner; and + joining any trait but the markers Sendable, Shareable and ShapeKeeping.
A closure is a value of a struct that has no name, and its size and fields depend on what it holds. That is a problem when a function, or a field, should take any closure of one calling signature, whatever struct holds its state. A closure reference solves it: the closure is passed as a reference to the signature it calls. It is to a signature what a virtual reference is to a trait. Here a button holds whatever click handler it is given:
struct Window { pub id i32; }
struct Button {
pub label i32;
pub onClick Uni[fn(n i32)];
pub fn setHandler(self &mut, f Uni[fn(n i32)]) { onClick = f; }
pub fn press(self &mut, n i32) { onClick(n); }
}
let w = new Window(7);
var b = new Button(1, new Uni[fn(n i32)](fn(n i32) { }));
b.setHandler(new Uni[fn(n i32)](fn(n i32) [id = w.id] { print <- id + n, "\n"; }));
b.press(5); // 12
The click handler is allocated in the single-owner region, so it is freed when it is replaced or discarded. Its state remembers the window's id, which its logic uses when the button is pressed. The Button holds it as a Uni[fn(n i32)], which accepts any closure, whatever its state, whose signature matches: one i32 in, no value back. The button calls the handler by the field's name, onClick(n).
The forms of a closure reference#
The reference is written as the signature of the call, fn(x i32) i32, behind a reference or an owner. The signature stands for the idea "anything that can be called like this", a trait that needs no declaring, and the reference is a virtual reference to it:
- &<fn(x i32) i32, a borrowed reference that may read what it points at. The closure's () takes self &.
- &<mut fn(x i32) i32, a borrowed reference that may change what it points at: a closure that counts, or changes a variable it borrows.
- Uni[fn(x i32) i32], an owner of a closure on the heap. The permission of an owner works as it does for any owner: Uni[fn(sig)] may change the closure's state, Uni[imm, fn(sig)] only reads it.
- Rc[imm, fn(x i32) i32], shared owners of a closure that only reads; Rc[mut, fn(x i32) i32], shared owners of a state they all change, so every holder sees one count. Rc[fn(x i32) i32] with no permission is uni, the sole owner, as for any Rc, and moves when copied.
These are the same permissions as elsewhere, so the call kind is the reference's permission, with no separate families of function types: a function that takes a callable it only reads takes &<fn(sig); one that lets it change takes &<mut fn(sig). The call is written as any call, and goes through the reference to the closure's code:
fn apply(f &<fn(x i32) i32, x i32) i32 { f(x); }
fn applyMut(f &<mut fn(x i32) i32, x i32) i32 { f(x); }
fn run(f Uni[fn(x i32) i32], x i32) i32 { f(x); }
What meets it#
Anything that has a pub () method of the signature, parameters and return type (the parameters' names do not matter, and a recast's difference is allowed, below):
struct Triple { pub k i32; pub fn `()`(self &, x i32) i32 { x * k; } }
struct Count { pub n i32; pub fn `()`(self &mut, x i32) i32 { n = n + x; n; } }
fn succ(x i32) i32 { x + 1; }
let off = 10;
let shift = fn(x i32) i32 { x + off; };
let t = new Triple(3);
var tally = new Count(0);
apply(&shift, 5); // 15: a closure
apply(&t, 5); // 15: a struct with a '()'
apply(&succ, 5); // 6: a plain function
applyMut(&mut tally, 5); // 5
applyMut(&mut tally, 7); // 12, and tally.n is 12
A plain function needs no state, so a reference to it meets any of the references above. A callable that reads meets &<mut fn as well, since a reference that may change can read. A function whose signature differs from the callable's by a recast alone meets it too, as a method meets a trait's: a parameter that asks for less (&P for &mut P), a result that promises more. A wider number, a value made an Option, or a variant held by value for its enum is a conversion, and is refused. A callable that changes its state (its () takes self &mut) is refused behind &<fn(sig), which promises only to read: "`&<fn(i32) i32` may only call a callable that reads its state; this closure changes it (its `()` takes `self &mut`). To let it change, the type is `&<mut fn(i32) i32`." Likewise a &<mut fn is not lent where a &<fn is wanted, nor is a Uni[fn(sig)], which may change: the callable behind it might. An owner meant to be lent to a reader is made Uni[imm, fn(sig)].
A closure literal, lent#
A closure literal written as an argument to a parameter taken by &<fn or &<mut fn is lent as a temporary, dropped at the end of the statement. The permission lent is the parameter's, so a literal given to &<fn that changes a variable is refused by the message above. The literal's parameter types come from the signature:
apply(x => x * 2, 21); // 42
applyMut(fn(x i32) i32 { total += x; total; }, 10); // total is changed
A literal is lent only as an argument. To keep one, move it into an owner.
An owner of a closure#
new Uni[fn(sig)](value) takes one value, a closure literal or a struct with a () of the signature, moves it into an allocation of its own, and gives the owner of that as the callable. The literal's parameter types may come from the signature. The same holds for Rc:
var owned Uni[fn(x i32) i32] = new Uni[fn(x i32) i32](fn(x i32) [var sum = 100] i32 { sum += x; sum; });
owned(1); // 101
applyMut(owned, 2); // 103: an owner is lent as &<mut
run(owned, 3); // 106: or handed on whole, moved
let shared = new Rc[imm, fn(x i32) i32](new Triple(4));
let again = shared; // another holder of the same closure
The owner holds whatever the closure holds, and finalizes it when the last owner dies. A closure that holds nothing is allocated like any other. A plain function is wrapped by giving its reference, new Uni[fn(sig)](&f); a function of another signature, or a value with no () of the signature, is an error. An owner outlives the function that made it, so the closure in it may not borrow a local: the refusal is the one in Where a closure may go.
A parameter of type Uni[fn(sig)] takes an owner, and a closure literal is not one, so a literal given straight to it is refused (for one whose parameter types are left out, error 1391, "The type of a is not written, and nothing here says what it is"). Wrap it: c.set(new Uni[fn(a i64, b i64) bool]((a, b) => a < b)). A let given a closure whose parameter types are left out is refused the same way, even where the let is written with a &<fn(sig) type.
Naming a signature#
An alias gives a signature a name, and the name works wherever the signature does:
alias Profile = fn(u i32) i32;
fn viaAlias(f &<Profile, u i32) i32 { f(u); }
A bare fn(sig) is read as a signature in the brackets of a type (Uni[fn(i32) i32], Applied[fn(i32) i32]), as the target of an alias and as a generic's bound (Generics). It is not itself the type of a variable or a return value: write &<fn(..), Uni[fn(..)] or the alias. A signature is a trait, so it has no size, and a field of a generic type holds it by an owner or a reference, not by value:
struct Applied[F] { pub f Uni[F]; } // Applied[fn(i32) i32] holds a Uni[fn(i32) i32]
A callable held in a field#
A field that holds a callable is called by its name, with no parentheses around it: b.onClick(5) calls the closure in onClick, and (b.onClick)(5) works too. A field and a method of one name cannot both exist, so there is nothing to choose between. A field that cannot be called, an i32 given arguments, is an error that says what the field holds. An owner whose state may change is called through a reference that may change, so a method that calls a Uni[fn(sig)] field takes self &mut, as press does above. The handler is replaced by assigning the field.
Saying what is behind a reference#
A virtual reference hides the type behind it, so a reference to a callable or a trait is not Sendable or Shareable unless it says so. The markers follow the signature or the trait with a +, Uni[fn(x i32) i32 + Sendable], and a third, ShapeKeeping, says that a call through the reference reshapes nothing the caller holds a borrow into. All three are described together, with the other rules for what may cross a thread, in Sendable and Shareable.
The other two ways to take a callable#
A function may instead be generic over the signature, [F fn(x i32) i32]. That form is compiled for each closure and inlined, where the references above are compiled once and call through the closure's code (A function signature as a constraint). The reference is what a field or a variable of unknown closure holds; the generic is what a function that only calls it uses. And a closure literal also fills a trait that has exactly one required method, wherever that trait is wanted (A closure written where a trait is wanted).
Calling a closure while a collection is borrowed#
Seen from a closure: a call through a &<mut fn(sig) parameter is assumed able to reshape any container, because the closure behind it may have captured one. A visitor that walks a list while its closure may push to that list would move the block the walk reads:
fn visit[T](xs &List[T], f &<mut fn(x &T)) {
each x in xs.iter() { f(x); } // ERROR 1339: f may push to xs
}
fn grow(list &mut List[i64]) {
var push = fn(x &i64) { list.push(*x); };
visit(list, &mut push);
}
Error 1339: This call could change the shape of a 'List' while a borrow into it (made by 'iter' at 4:13, through a path other references share) is still to be used by this call or by a value around it: '()' is handed something that reaches a 'List' it may change, and its body is not visible here, so it is assumed to change its shape (a virtual reference marked '+ ShapeKeeping' keeps the borrow across the call). A 'List' that changes shape may move or free what it lent. Reach it through an 'imm' or 'uni' reference, or copy what is needed out before this call.
Lending the list &imm cures it: nothing can write it, so no closure handed in can have captured it by &mut (the caller's list is a let variable). Marking the closure + ShapeKeeping, or copying what is needed out before the call, also works. A read-only &<fn is not affected.
fn visit[T](xs &imm List[T], f &<mut fn(x &T)) {
each x in xs.iter() { f(x); } // Ok
}
let nums = numbers; // 1, 5, 9 and 12, in a let variable
var sum = 0i64;
visit(&imm nums, fn(x &i64) { sum += *x; }); // sum is 27
A closure, read-only or not, may also write a var global by its name, so a view into a global is refused across a call of a closure, read-only or not, and across a call of a function that makes one (A borrow into a var global).
You can now hand behaviour around as a part: a function by reference, or any callable of a signature, a closure with its state or a struct with a () method, held by a borrow or an owner. Each of these is chosen while the program runs; the next page makes the same choices while it is compiled, with generics, and adds macros.