Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Function Pointers

A C function pointer can be null, compared for equality, cast to another function pointer type and back, and stored in a void *. A Rust fn value can be called and compared, but it is never null and its type is fixed, so the refcount model translates function pointers as FnPtr<T>, where T is the Rust fn type of the target:

#![allow(unused)]
fn main() {
pub struct FnPtr<T> { /* the function as first stored, and its current cast */ }

impl<T> FnPtr<T> {
    pub fn null() -> Self;
    pub fn new(f: T) -> Self;
    pub fn is_null(&self) -> bool;
    pub fn cast<U>(&self, adapter: Option<U>) -> FnPtr<U>;
    pub fn to_any(&self) -> AnyPtr;
}
}

FnPtr dereferences to the function, so a call through it is (*fp)(args). Calling a null pointer panics with ub:.

FnPtr stores the function inline, together with its address, which is how pointers are compared; the FnAddr trait provides the address. Creating, copying, and calling a function pointer does not allocate. Rust has no way to write an impl for every fn arity at once, so FnAddr is implemented by a macro for fn types of zero to sixteen parameters. A function with more parameters cannot be wrapped in an FnPtr, and taking its address fails to compile with a missing FnAddr bound.

typedef int (*int_fn)(int);
int double_it(int x) { return x * 2; }

int_fn fn = double_it;
int r = fn(5);
#![allow(unused)]
fn main() {
let fn_: Value<FnPtr<fn(i32) -> i32>> =
    Rc::new(RefCell::new(FnPtr::<fn(i32) -> i32>::new(double_it_0)));
let r: Value<i32> = Rc::new(RefCell::new((*(*fn_.borrow()))(5)));
}

Casts

C code casts function pointers to a different type and calls through the new type. When the two types are not compatible this is undefined behavior, but the argument types involved usually have the same representation, so implementations accept the call and programs rely on it. Below, add_offset takes an int *, but is called through a pointer that takes a void *:

typedef int (*generic_int_fn)(void *, int);
int add_offset(int *base, int offset) { return *base + offset; }

generic_int_fn gfn = (generic_int_fn)add_offset;
int result = gfn(&val, 42);

In Rust fn(Ptr<i32>, i32) -> i32 and fn(AnyPtr, i32) -> i32 are unrelated types, so the code generator emits an adapter: a function of the target type that converts the arguments and calls the original. cast stores it, and calls through the cast pointer go through the adapter:

#![allow(unused)]
fn main() {
let gfn: Value<FnPtr<fn(AnyPtr, i32) -> i32>> = Rc::new(RefCell::new(
    FnPtr::<fn(Ptr<i32>, i32) -> i32>::new(add_offset_4)
        .cast::<fn(AnyPtr, i32) -> i32>(Some(
            (|a0: AnyPtr, a1: i32| -> i32 {
                add_offset_4(a0.reinterpret_cast::<i32>(), a1)
            }) as fn(AnyPtr, i32) -> i32,
        )),
));
let result: Value<i32> = Rc::new(RefCell::new(
    (*(*gfn.borrow()))(val.as_pointer().to_any(), 42),
));
}

The code generator can build an adapter when the arguments and return type of the two function types have the same representation. Otherwise it passes None, and calling through the cast pointer panics with ub:.

A cast to a different type is the only operation that allocates: the pointer then also keeps the function it was created with, type-erased, so that casting back to that type can restore it. Equality compares the address of the function the pointer was created with.

Casting a function pointer to void * is to_any, and AnyPtr::cast_fn::<T> recovers it. reinterpret_cast on an AnyPtr holding a function currently panics, as do integer casts on a Ptr; both are set to be fixed in the near future.

Lambdas

A lambda is an FnPtr too, in both models (see Lambdas). One without captures is built with new from a closure, like a function. One with captures is built by the lambda! and lambda_unsafe! macros, which declare a struct holding the captures, with the body as its method, and pass both to from_lambda or from_lambda_unsafe:

#![allow(unused)]
fn main() {
impl<A, R> FnPtr<fn(A) -> R> {
    pub fn from_lambda<L>(lambda: L, call: fn(&L, A) -> R) -> Self;
    pub fn from_lambda_unsafe<L>(lambda: L, call: fn(&mut L, A) -> R) -> Self;
}
}

The unsafe model translates function pointers as Option<unsafe fn> and uses FnPtr only for lambdas. For this, FnPtrArg is also implemented for raw pointers and for Option<unsafe fn>, and derived by the structs and unions of the unsafe model.