Function pointers
Function pointers exist; closures do not. There is no captured environment to reason about.
Type position
fn(i32, i32) -> i32 // takes two i32, returns i32
fn(*u8) // takes *u8, returns unit
Argument ownership modes
A function-pointer argument carries the same ownership mode a parameter does. A bare fn(T) borrows its argument (read-only), fn(take T) consumes it, and fn(ref T) borrows through the pointer so the callee can write back:
fn(T) // borrows the argument (read-only)
fn(take T) // consumes the argument
fn(ref T) // writes back through the pointer
For example, Rc::with_mut / Arc::with_mut take a fn(ref T) so a callback can mutate the shared value in place when the reference is unique.
Coercion
A bare function identifier coerces to a function pointer in a position that expects one:
extern fn atexit(cb: fn()) -> i32;
fn cleanup() { #println(99); }
fn main() -> i32 {
atexit(cleanup); // bare name coerces
return 0;
}
A struct of callbacks
struct Actions {
_on_click: fn(i32) -> i32,
_on_hover: fn(i32) -> i32,
}
let a: Actions = Actions { _on_click: handle_click, _on_hover: handle_hover };
let r: i32 = a._on_click(7); // indirect call through the field
Stateful callbacks: the C convention
Function pointers do not capture environment, so for a callback that needs state, do what C does: pass (fn_ptr, user_data: *u8) and have the library thread user_data back to you unchanged.
extern fn libfoo_subscribe(cb: fn(*u8, i32), user_data: *u8);
This is also how appkit wires UI callbacks: a named function plus an associated object, never a closure.