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

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.


#![allow(unused)]
fn main() {
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 in Rust

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 AST is required to call a function pointer, as well as the entire execution context that the script is running in.

For a rust function taking a function pointer as parameter, the Low-Level API must be used to register the function.

Essentially, use the low-level Engine::register_raw_fn method to register the function. FnPtr::call_dynamic is used to actually call the function pointer, passing to it the current native call context, the this pointer, and other necessary arguments.


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

let mut engine = Engine::new();

// Define Rust function in required low-level API signature
fn call_fn_ptr_with_value(context: NativeCallContext, args: &mut [&mut Dynamic])
    -> Result<Dynamic, Box<EvalAltResult>>
{
    // 'args' is guaranteed to contain enough arguments of the correct types
    let fp = std::mem::take(args[1]).cast::<FnPtr>();   // 2nd argument - function pointer
    let value = std::mem::take(args[2]);                // 3rd argument - function argument
    let this_ptr = args.get_mut(0).unwrap();            // 1st argument - 'this' pointer

    // Use 'FnPtr::call_dynamic' to call the function pointer.
    fp.call_dynamic(&context, Some(this_ptr), [value])
}

// Register a Rust function using the low-level API
engine.register_raw_fn("super_call",
    &[ // parameter types
        std::any::TypeId::of::<i64>(),
        std::any::TypeId::of::<FnPtr>(),
        std::any::TypeId::of::<i64>()
    ],
    call_fn_ptr_with_value
);
}

NativeCallContext

FnPtr::call_dynamic takes a parameter of type NativeCallContext which holds the native call context of the particular call to a registered Rust function. It is a type that exposes the following:

MethodTypeDescription
engine()&Enginethe current Engine, with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using Engine::call_fn, or calling a function pointer.
fn_name()&strname of the function called (useful when the same Rust function is mapped to multiple Rhai-callable function names)
source()Option<&str>reference to the current source, if any
iter_imports()impl Iterator<Item = (&str, &Module)>iterator of the current stack of modules imported via import statements
imports()&Importsreference to the current stack of modules imported via import statements; requires the internals feature
iter_namespaces()impl Iterator<Item = &Module>iterator of the namespaces (as modules) containing all script-defined functions
namespaces()&[&Module]reference to the namespaces (as modules) containing all script-defined functions; requires the internals feature
call_fn_dynamic_raw()Result<Dynamic, Box<EvalAltResult>>call a native Rust function with the supplied arguments; this is an advanced method

This type is normally provided by the Engine (e.g. when using Engine::register_fn_raw). However, it may also be manually constructed from a tuple:


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

let engine = Engine::new();

// Compile script to AST
let mut 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)?;

// Get rid of the script, retaining only functions
ast.retain_functions(|_, _, _| true);

// Create function namespace from the 'AST'
let lib = [ast.as_ref()];

// Create native call context
let fn_name = fn_ptr.fn_name().to_string();
let context = NativeCallContext::new(&engine, &fn_name, &lib);

// 'f' captures: the engine, the AST, and the closure
let f = move |x: i64| fn_ptr.call_dynamic(&context, None, [x.into()]);

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