-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
initial pass at function pointers #45
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# 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`, and | ||
`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 corresonding 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, | ||
|
||
```rust | ||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should likely reference the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep! |
||
|
||
|
||
### 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: | ||
|
||
```c | ||
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); | ||
``` | ||
|
||
```rust | ||
pub struct Cons { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the lack of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably accidental, however you should probably open a new issue about this, because a years old PR is a little too out of the way to get much attention. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall I stop using this algorithm (spot suspicious place in a git-based book -> go to repo -> go to that file -> blame -> comment) in general? Github UI seems to provide nice way of attaching comments to specific lines in files, so I expected those comments would be delivered precisely to those who deal with that particular file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well as I said this PR is years old. The person who wrote it doesn't even really work on Rust any more. We are happy to have people taking an interest in fixing anything amiss, it's just that a new issue is generally preferred over continued discussion on a merged or closed PR, particularly an older one. That way the question and/or comment will stay on the list of things to resolve rather than be potentially forgotten by accident. Many people working on Rust have so many watched issues and projects that individual notifications/emails can easily get lost among them all. |
||
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 extern "C" fn iterate( | ||
node: Option<&Cons>, | ||
func: extern 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 extern "C" fn for_all( | ||
node: Option<&Cons>, | ||
func: extern 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); | ||
} | ||
} | ||
``` | ||
|
||
### Unresolved Questions | ||
|
||
- dunno |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just say something like: "Function pointers have no lifetime. They must point to an allocated block of code when being called."
The rest of this belongs to #72, doesn't it?