Skip to content

Commit 4d188f1

Browse files
ubsanavadacatavra
authored andcommitted
initial pass at function pointers (#45)
* initial pass at function pointers * minor nits * add a > * correct lifetime stuff
1 parent 50c65d0 commit 4d188f1

File tree

1 file changed

+125
-0
lines changed

1 file changed

+125
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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

Comments
 (0)