Make transmuting from fn item types to pointer-sized types a hard error.

This commit is contained in:
Eduard Burtescu 2016-06-10 13:00:21 +03:00 committed by Eduard-Mihai Burtescu
parent e1cb9ba221
commit 7650afc1ce
8 changed files with 153 additions and 116 deletions

View file

@ -1725,6 +1725,68 @@ If you want to get command-line arguments, use `std::env::args`. To exit with a
specified exit code, use `std::process::exit`.
"##,
E0591: r##"
Per [RFC 401][rfc401], if you have a function declaration `foo`:
```rust,ignore
// For the purposes of this explanation, all of these
// different kinds of `fn` declarations are equivalent:
fn foo(x: i32) { ... }
extern "C" fn foo(x: i32);
impl i32 { fn foo(x: self) { ... } }
```
the type of `foo` is **not** `fn(i32)`, as one might expect.
Rather, it is a unique, zero-sized marker type written here as `typeof(foo)`.
However, `typeof(foo)` can be _coerced_ to a function pointer `fn(i32)`,
so you rarely notice this:
```rust,ignore
let x: fn(i32) = foo; // OK, coerces
```
The reason that this matter is that the type `fn(i32)` is not specific to
any particular function: it's a function _pointer_. So calling `x()` results
in a virtual call, whereas `foo()` is statically dispatched, because the type
of `foo` tells us precisely what function is being called.
As noted above, coercions mean that most code doesn't have to be
concerned with this distinction. However, you can tell the difference
when using **transmute** to convert a fn item into a fn pointer.
This is sometimes done as part of an FFI:
```rust,ignore
extern "C" fn foo(userdata: Box<i32>) {
...
}
let f: extern "C" fn(*mut i32) = transmute(foo);
callback(f);
```
Here, transmute is being used to convert the types of the fn arguments.
This pattern is incorrect because, because the type of `foo` is a function
**item** (`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
is a function pointer, which is not zero-sized.
This pattern should be rewritten. There are a few possible ways to do this:
- change the original fn declaration to match the expected signature,
and do the cast in the fn body (the prefered option)
- cast the fn item fo a fn pointer before calling transmute, as shown here:
- `let f: extern "C" fn(*mut i32) = transmute(foo as extern "C" fn(_))`
- `let f: extern "C" fn(*mut i32) = transmute(foo as usize) /* works too */`
The same applies to transmutes to `*mut fn()`, which were observedin practice.
Note though that use of this type is generally incorrect.
The intention is typically to describe a function pointer, but just `fn()`
alone suffices for that. `*mut fn()` is a pointer to a fn pointer.
(Since these values are typically just passed to C code, however, this rarely
makes a difference in practice.)
[rfc401]: https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md
"##,
}