Function Pointers

It is possible to store a function pointer in a variable just like a normal value. In fact, internally a function pointer simply stores the name of the function as a string.

A function pointer is created via the Fn function, which takes a string parameter.

Call a function pointer using the call method.

Built-in Functions

The following standard methods (mostly defined in the BasicFnPackage but excluded if using a raw Engine) operate on function pointers.

FunctionParameter(s)Description
name method and propertynonereturns the name of the function encapsulated by the function pointer
is_anonymous method and propertynonedoes the function pointer refer to an anonymous function? Not available under no_function.
callargumentscalls the function matching the function pointer’s name with the arguments

Examples


#![allow(unused)]
fn main() {
fn foo(x) { 41 + x }

let func = Fn("foo");       // use the 'Fn' function to create a function pointer

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)'
}

Warning – NOT First-Class Functions

Beware that function pointers are not first-class functions.

They are syntactic sugar only, capturing the name of a function to call.

The actual function must be defined in order for the call to succeed.

Warning – Global Namespace Only

Because of their dynamic nature, function pointers cannot refer to functions in import-ed modules. They can only refer to functions within the global namespace. See Function Namespaces for more details.

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.


#![allow(unused)]
fn main() {
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 {
    Fn("method1")
} else if x == 0 {
    Fn("method2")
} else if x > 0 {
    Fn("method3")
};

// Dynamic dispatch
func.call(42);

// Using functions map
let map = [ Fn("method1"), Fn("method2"), Fn("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:


#![allow(unused)]
fn main() {
fn add(x) {                 // define function which uses 'this'
    this += x;
}

let func = Fn("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).

Therefore, obviously, binding the this pointer is unsupported under no_object.

Call a Function Pointer within a Rust Function (as a Callback)

It is completely normal to register a Rust function with an Engine that takes parameters whose types are function pointers. The Rust type in question is rhai::FnPtr.

A function pointer in Rhai is essentially syntactic sugar wrapping the name of a function to call in script. Therefore, the script’s execution context is needed in order to call a function pointer.

The type NativeCallContext holds the native call context of the particular call to a registered Rust function. This type is normally provided by the Engine when a function is registered with the first parameter type being NativeCallContext.


#![allow(unused)]
fn main() {
use rhai::{Engine, FnPtr, NativeCallContext};

let mut engine = Engine::new();

// A function expecting a callback in form of a function pointer.
fn super_call(context: NativeCallContext, callback: FnPtr, value: i64)
                -> Result<String, Box<EvalAltResult>>
{
    // Use 'FnPtr::call_within_context' to call the function pointer using the call context.
    // 'FnPtr::call_within_context' automatically casts to the required result type.
    callback.call_within_context(&context, (value,))
    //                                     ^^^^^^^^ arguments passed in tuple
}

engine.register_result_fn("super_call", super_call);
}

Call a Function Pointer Directly

The FnPtr::call method allows the function pointer to be called directly on any Engine and AST, making it possible to reuse the FnPtr data type in may different calls and scripting environments.


#![allow(unused)]
fn main() {
use rhai::{Engine, FnPtr};

let engine = Engine::new();

// Compile script to AST
let ast = engine.compile(
r#"
    let test = "hello";
    |x| test + x            // this creates a closure
"#)?;

// Save the closure together with captured variables
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;

// 'f' captures: the Engine, the AST, and the closure
let f = move |x: i64| -> Result<String, Box<EvalAltResult>> {
            fn_ptr.call(&engine, &ast, (x,))
        };

// 'f' can be called like a normal function
let result = f(42)?;

result == "hello42";
}