Function Pointers¶
It is possible to store a function pointer in a variable just like a normal value.
A function pointer is created via the Fn
function, which takes a string parameter.
Call a function pointer via the call
method.
Short-Hand Notation¶
Native Rust functions cannot use this short-hand notation.
Having to write Fn("foo")
in order to create a function pointer to the function foo
is a chore, so there is a short-hand available.
A function pointer to any script-defined function within the same script can be obtained simply by referring to the function's name.
fn foo() { ... } // function definition
let f = foo; // function pointer to 'foo'
let f = Fn("foo"); // <- the above is equivalent to this
let g = bar; // error: variable 'bar' not found
The short-hand notation is particularly useful when passing functions as closure arguments.
fn is_even(n) { n % 2 == 0 }
let array = [1, 2, 3, 4, 5];
array.filter(is_even);
array.filter(Fn("is_even")); // <- the above is equivalent to this
array.filter(|n| n % 2 == 0); // <- ... or this
Built-in Functions¶
The following standard methods operate on function pointers.
Function | Parameter(s) | Description |
---|---|---|
name method and property | none | returns the name of the function encapsulated by the function pointer |
is_anonymous method and property | none | does the function pointer refer to an anonymous function? |
call | arguments | calls the function matching the function pointer's name with the arguments |
Examples¶
fn foo(x) { 41 + x }
let func = Fn("foo"); // use the 'Fn' function to create a function pointer
let func = foo; // <- short-hand: equivalent to 'Fn("foo")'
print(func); // prints 'Fn(foo)'
let func = fn_name.Fn(); // <- error: 'Fn' cannot be called in method-call style
func.type_of() == "Fn"; // type_of() as function pointer is 'Fn'
func.name == "foo";
func.call(1) == 42; // call a function pointer with the 'call' method
foo(1) == 42; // <- the above de-sugars to this
call(func, 1); // normal function call style also works for 'call'
let len = Fn("len"); // 'Fn' also works with registered native Rust functions
len.call("hello") == 5;
let fn_name = "hello"; // the function name does not have to exist yet
let hello = Fn(fn_name + "_world");
hello.call(0); // error: function not found - 'hello_world (i64)'
Beware that function pointers are not first-class functions.
They are syntactic sugar only, capturing only the name of a function to call. They do not hold the actual functions.
The actual function must be defined in the appropriate namespace for the call to succeed.
Because of their dynamic nature, function pointers cannot refer to functions in import
-ed modules.
They can only refer to functions defined globally within the script or a built-in function.
import "foo" as f; // assume there is 'f::do_work()'
f::do_work(); // works!
let p = Fn("f::do_work"); // error: invalid function name
fn do_work_now() { // call it from a local function
f::do_work();
}
let p = Fn("do_work_now");
p.call(); // works!
Dynamic Dispatch¶
The purpose of function pointers is to enable rudimentary dynamic dispatch, meaning to determine, at runtime, which function to call among a group.
Although it is possible to simulate dynamic dispatch via a number and a large if-then-else-if
statement, using function pointers significantly simplifies the code.
let x = some_calculation();
// These are the functions to call depending on the value of 'x'
fn method1(x) { ... }
fn method2(x) { ... }
fn method3(x) { ... }
// Traditional - using decision variable
let func = sign(x);
// Dispatch with if-statement
if func == -1 {
method1(42);
} else if func == 0 {
method2(42);
} else if func == 1 {
method3(42);
}
// Using pure function pointer
let func = if x < 0 {
method1
} else if x == 0 {
method2
} else if x > 0 {
method3
};
// Dynamic dispatch
func.call(42);
// Using functions map
let map = [ method1, method2, method3 ];
let func = sign(x) + 1;
// Dynamic dispatch
map[func].call(42);
Bind the this
Pointer¶
When call
is called as a method but not on a function pointer, it is possible to dynamically dispatch to a function call while binding the object in the method call to the this
pointer of the function.
To achieve this, pass the function pointer as the first argument to call
:
fn add(x) { // define function which uses 'this'
this += x;
}
let func = add; // function pointer to 'add'
func.call(1); // error: 'this' pointer is not bound
let x = 41;
func.call(x, 1); // error: function 'add (i64, i64)' not found
call(func, x, 1); // error: function 'add (i64, i64)' not found
x.call(func, 1); // 'this' is bound to 'x', dispatched to 'func'
x == 42;
Beware that this only works for method-call style. Normal function-call style cannot bind the this
pointer (for syntactic reasons).
Currying¶
It is possible to curry a function pointer by providing partial (or all) arguments.
Currying is done via the curry
keyword and produces a new function pointer which carries the curried arguments.
When the curried function pointer is called, the curried arguments are inserted starting from the left.
The actual call arguments should be reduced by the number of curried arguments.
fn mul(x, y) { // function with two parameters
x * y
}
let func = mul; // <- de-sugars to 'Fn("mul")'
func.call(21, 2) == 42; // two arguments are required for 'mul'
let curried = func.curry(21); // currying produces a new function pointer which
// carries 21 as the first argument
let curried = curry(func, 21); // function-call style also works
curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
// only one argument is now required