|
| 1 | +# Representation of Function Pointers |
| 2 | + |
| 3 | +### Terminology |
| 4 | + |
| 5 | +In Rust, a function pointer type, is either `fn(Args...) -> Ret`, |
| 6 | +`extern "ABI" fn(Args...) -> Ret`, `unsafe fn(Args...) -> Ret`, and |
| 7 | +`unsafe extern "ABI" fn(Args...) -> Ret`. |
| 8 | +A function pointer is the address of a function, |
| 9 | +and has function pointer type. |
| 10 | +The pointer is implicit in the `fn` type, |
| 11 | +and they have no lifetime of their own; |
| 12 | +therefore, function pointers are assumed to point to |
| 13 | +a block of code with static lifetime. |
| 14 | +This is not necessarily always true, |
| 15 | +since, for example, you can unload a dynamic library. |
| 16 | +Therefore, this is _only_ a safety invariant, |
| 17 | +not a validity invariant; |
| 18 | +as long as one doesn't call a function pointer which points to freed memory, |
| 19 | +it is not undefined behavior. |
| 20 | + |
| 21 | + |
| 22 | +In C, a function pointer type is `Ret (*)(Args...)`, or `Ret ABI (*)(Args...)`, |
| 23 | +and values of function pointer type are either a null pointer value, |
| 24 | +or the address of a function. |
| 25 | + |
| 26 | +### Representation |
| 27 | + |
| 28 | +The ABI and layout of `(unsafe)? (extern "ABI")? fn(Args...) -> Ret` |
| 29 | +is exactly that of the corresonding C type -- |
| 30 | +the lack of a null value does not change this. |
| 31 | +On common platforms, this means that `*const ()` and `fn(Args...) -> Ret` have |
| 32 | +the same ABI and layout. This is, in fact, guaranteed by POSIX and Windows. |
| 33 | +This means that for the vast majority of platforms, |
| 34 | + |
| 35 | +```rust |
| 36 | +fn go_through_pointer(x: fn()) -> fn() { |
| 37 | + let ptr = x as *const (); |
| 38 | + unsafe { std::mem::transmute::<*const (), fn()>(ptr) } |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +is both perfectly safe, and, in fact, required for some APIs -- notably, |
| 43 | +`GetProcAddress` on Windows requires you to convert from `void (*)()` to |
| 44 | +`void*`, to get the address of a variable; |
| 45 | +and the opposite is true of `dlsym`, which requires you to convert from |
| 46 | +`void*` to `void (*)()` in order to get the address of functions. |
| 47 | +This conversion is _not_ guaranteed by Rust itself, however; |
| 48 | +simply the implementation. If the underlying platform allows this conversion, |
| 49 | +so will Rust. |
| 50 | + |
| 51 | +However, null values are not supported by the Rust function pointer types -- |
| 52 | +just like references, the expectation is that you use `Option` to create |
| 53 | +nullable pointers. `Option<fn(Args...) -> Ret>` will have the exact same ABI |
| 54 | +as `fn(Args...) -> Ret`, but additionally allows null pointer values. |
| 55 | + |
| 56 | + |
| 57 | +### Use |
| 58 | + |
| 59 | +Function pointers are mostly useful for talking to C -- in Rust, you would |
| 60 | +mostly use `T: Fn()` instead of `fn()`. If talking to a C API, |
| 61 | +the same caveats as apply to other FFI code should be followed. |
| 62 | +As an example, we shall implement the following C interface in Rust: |
| 63 | + |
| 64 | +```c |
| 65 | +struct Cons { |
| 66 | + int data; |
| 67 | + struct Cons *next; |
| 68 | +}; |
| 69 | + |
| 70 | +struct Cons *cons(struct Cons *self, int data); |
| 71 | + |
| 72 | +/* |
| 73 | + notes: |
| 74 | + - func must be non-null |
| 75 | + - thunk may be null, and shall be passed unchanged to func |
| 76 | + - self may be null, in which case no iteration is done |
| 77 | +*/ |
| 78 | + |
| 79 | +void iterate(struct Cons const *self, void (*func)(int, void *), void *thunk); |
| 80 | +bool for_all(struct Cons const *self, bool (*func)(int, void *), void *thunk); |
| 81 | +``` |
| 82 | +
|
| 83 | +```rust |
| 84 | +pub struct Cons { |
| 85 | + data: c_int, |
| 86 | + next: Option<Box<Cons>>, |
| 87 | +} |
| 88 | +
|
| 89 | +#[no_mangle] |
| 90 | +pub extern "C" fn cons(node: Option<Box<Cons>>, data: c_int) -> Box<Cons> { |
| 91 | + Box::new(Cons { data, next: node }) |
| 92 | +} |
| 93 | +
|
| 94 | +#[no_mangle] |
| 95 | +pub extern "C" fn iterate( |
| 96 | + node: Option<&Cons>, |
| 97 | + func: extern fn(i32, *mut c_void), // note - non-nullable |
| 98 | + thunk: *mut c_void, // note - this is a thunk, so it's just passed raw |
| 99 | +) { |
| 100 | + let mut it = node; |
| 101 | + while let Some(node) = it { |
| 102 | + func(node.data, thunk); |
| 103 | + it = node.next.as_ref().map(|x| &**x); |
| 104 | + } |
| 105 | +} |
| 106 | +
|
| 107 | +#[no_mangle] |
| 108 | +pub extern "C" fn for_all( |
| 109 | + node: Option<&Cons>, |
| 110 | + func: extern fn(i32, *mut c_void) -> bool, |
| 111 | + thunk: *mut c_void, |
| 112 | +) -> bool { |
| 113 | + let mut it = node; |
| 114 | + while let Some(node) = node { |
| 115 | + if !func(node.data, thunk) { |
| 116 | + return false; |
| 117 | + } |
| 118 | + it = node.next.as_ref().map(|x| &**x); |
| 119 | + } |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +### Unresolved Questions |
| 124 | + |
| 125 | +- dunno |
0 commit comments