Cone Language Reference

Function References

On this page

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.

Function references are useful when you want to generalize some piece of code, such that part of its logic is performed by one of several compatible functions selected at at runtime.

Use the ampersand operator to borrow a reference to any existing function:

fn incr(nbr u32) u32 {
  nbr + 1;
}

fn doit() {
  imm fnref = &incr;  // fn ref points to the function "incr"
}

As with any borrowed reference, this reference may be copied around between variables and functions. It carries no lifetime restrictions, as there is no risk of a global function going away.

The reference may be used at any time to call the function it refers to:

fn doit() {
	dostuff(&incr);   // Pass along a reference to the incr function

// Note the parameter needs to declare the reference's function signature
}
fn dostuff(fnref &fn(u32) u32) {
	fnref(4);         // Call the function
}

A function reference's 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-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 {
	mut a u32;
	mut 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.

The default (and only permissible) permission for a function reference is opaq. This permission does not allow the reference to be dereferenced in any way, thereby prohibiting the ability to read or change the logic of the function it points to.

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 a C library's callback parameter takes.

Anonymous Functions#

When the function you want to get a reference to is small, it may be more convenient to specify the logic for the function right where the function reference is to be captured. This is called an anonymous function, as the function has no name:

fn doit() {
	dostuff(&fn(nbr u32) u32 {nbr+1;});
	dostuff(&fn(nbr u32) u32 {nbr+nbr;});
}

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:

fn doit() {
	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 with a message saying so. The parameter is then declared &<fn(u32) u32.