Representation of Function Pointers

Terminology

In Rust, a function pointer type, is either fn(Args...) -> Ret, extern "ABI" fn(Args...) -> Ret, unsafe fn(Args...) -> Ret, or unsafe extern "ABI" fn(Args...) -> Ret. A function pointer is the address of a function, and has function pointer type. The pointer is implicit in the fn type, and they have no lifetime of their own; therefore, function pointers are assumed to point to a block of code with static lifetime. This is not necessarily always true, since, for example, you can unload a dynamic library. Therefore, this is only a safety invariant, not a validity invariant; as long as one doesn't call a function pointer which points to freed memory, it is not undefined behavior.

In C, a function pointer type is Ret (*)(Args...), or Ret ABI (*)(Args...), and values of function pointer type are either a null pointer value, or the address of a function.

Representation

The ABI and layout of (unsafe)? (extern "ABI")? fn(Args...) -> Ret is exactly that of the corresponding C type -- the lack of a null value does not change this. On common platforms, this means that *const () and fn(Args...) -> Ret have the same ABI and layout. This is, in fact, guaranteed by POSIX and Windows. This means that for the vast majority of platforms,

#![allow(unused)] fn main() { fn go_through_pointer(x: fn()) -> fn() { let ptr = x as *const (); unsafe { std::mem::transmute::<*const (), fn()>(ptr) } } }

is both perfectly safe, and, in fact, required for some APIs -- notably, GetProcAddress on Windows requires you to convert from void (*)() to void*, to get the address of a variable; and the opposite is true of dlsym, which requires you to convert from void* to void (*)() in order to get the address of functions. This conversion is not guaranteed by Rust itself, however; simply the implementation. If the underlying platform allows this conversion, so will Rust.

However, null values are not supported by the Rust function pointer types -- just like references, the expectation is that you use Option to create nullable pointers. Option<fn(Args...) -> Ret> will have the exact same ABI as fn(Args...) -> Ret, but additionally allows null pointer values.

Use

Function pointers are mostly useful for talking to C -- in Rust, you would mostly use T: Fn() instead of fn(). If talking to a C API, the same caveats as apply to other FFI code should be followed. As an example, we shall implement the following C interface in Rust:

struct Cons { int data; struct Cons *next; }; struct Cons *cons(struct Cons *self, int data); /* notes: - func must be non-null - thunk may be null, and shall be passed unchanged to func - self may be null, in which case no iteration is done */ void iterate(struct Cons const *self, void (*func)(int, void *), void *thunk); bool for_all(struct Cons const *self, bool (*func)(int, void *), void *thunk);
#![allow(unused)] fn main() { use std::{ ffi::c_void, os::raw::c_int, }; #[repr(C)] pub struct Cons { data: c_int, next: Option<Box<Cons>>, } #[no_mangle] pub extern "C" fn cons(node: Option<Box<Cons>>, data: c_int) -> Box<Cons> { Box::new(Cons { data, next: node }) } #[no_mangle] pub unsafe extern "C" fn iterate( node: Option<&Cons>, func: unsafe extern "C" fn(i32, *mut c_void), // note - non-nullable thunk: *mut c_void, // note - this is a thunk, so it's just passed raw ) { let mut it = node; while let Some(node) = it { func(node.data, thunk); it = node.next.as_ref().map(|x| &**x); } } #[no_mangle] pub unsafe extern "C" fn for_all( node: Option<&Cons>, func: unsafe extern "C" fn(i32, *mut c_void) -> bool, thunk: *mut c_void, ) -> bool { let mut it = node; while let Some(node) = node { if !func(node.data, thunk) { return false; } it = node.next.as_ref().map(|x| &**x); } true } }