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