Skip to content
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

Merged
merged 4 commits into from
Jan 31, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions reference/src/representation/function-pointers.md
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.
Copy link
Member

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?



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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should likely reference the enum chapter once one exists; I guess the rules here will be exactly the same as the ones for reference types?

Copy link
Author

Choose a reason for hiding this comment

The 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the lack of #[repr(C)] accidental or intentional?

Copy link
Contributor

@Lokathor Lokathor Jun 24, 2021

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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