There are two things here:
-
callback::vtab::on_wake
is a zero-sized type uniquely representing that function (item);
That is, given fn foo() {}
and fn bar() {}
, foo
and bar
have different types.
-
[unsafe] [extern [ABI]] fn (Args) [-> Ret]
is a runtime / dynamic type, no longer zero-sized, but pointer-sized, pointing to the beginning of machine code (while still having the ABI and unsafe
-ty encoded at the type-level).
Transmuting between the function pointer types can be done (since they are all equally pointer-sized), it just requires coercing the input zero-sized function item type to the runtime function pointer type:
use ::core::mem;
fn main ()
{
fn foo () {}
assert_eq!(mem::size_of_val(&foo), 0);
let foo_ptr: fn() = foo; // coercion to runtime type (function pointer)
assert_eq!(mem::size_of_val(&foo_ptr), mem::size_of::<usize>());
let unsafe_fptr = unsafe {
mem::transmute::<_, unsafe fn()>(foo_ptr) // works
};
// or
let unsafe_fptr = unsafe {
mem::transmute::<_, unsafe fn()>(foo as fn()) // explicit coercion from foo to foo_ptr
};
// or
let unsafe_fptr = unsafe {
mem::transmute::<fn(), unsafe fn()>(foo) // implicit coercion from foo to foo_ptr
};
// calling foo through an `unsafe fn()` function pointer requires an `unsafe` block
unsafe {
unsafe_fptr()
}
}
But the only time this transmute is sound is when:
-
transmuting an input parameter to a compatible type that is at least as strict.
-
Example
fn(*const i32)
to fn(&i32)
-
making a non-unsafe
fn
become unsafe
:
-
Example
[extern _] fn(Args) [-> Ret]
to unsafe [extern _] fn(Args) [-> Ret]
-
Sometimes, you may just know that an extern
function is not unsafe
, i.e., that calling it with any valid input cannot possibly be unsound. Then you can transmute "in the other direction" (i.e., stripping the unsafe
).
Something that will never ever be sound, however, is transmuting the call ABI: an extern X
function must always remain extern X
.
Now, in all this transmute
rant, we have downgraded our compile-time zero-sized function items to runtime function pointers (leading to dynamic calls) just for the sake of changing something about the function's signature; and that isn't optimal. Moreover, changing the ABI has not been possible.
It turns out that to keep zero-sized function items, and being even able to "change" function call ABI can very simply be done by wrapping the function call in another function with the ABI and API of our liking.
Changing the ABI
-
instead of
use ::libc::c_int;
/// Static / compile-time linking
extern "C" { fn abs (_: c_int) -> c_int; }
// abs function pointer type is `extern "C"`
-
you can do:
use ::libc::c_int;
/// # Safety: same safety requirements as the extern `abs` function.
unsafe
fn abs (x: c_int) -> c_int
{
/// Static / compile-time linking
extern "C" { fn abs (_: c_int) -> c_int; }
abs(x)
}
// abs function pointer type is not `extern "C"`
Changing the API only (without touching the ABI):
Imagine we have a extern "C" { fn clear (_: *mut c_int); }
that we would like to use in Rust, and it taking raw pointers does not grant the compile-time checks that Rust is usually able to give. Then you can just go and do:
extern "C" { // same ABI
fn clear (_: &'_ mut c_int); // changed API to a more restrictive input
}
Chaning both the API and the ABI
And if wanting to also make the function non-unsafe
(and now that we are at it, not have an extern "C"
ABI), we can just go and wrap it:
although to be honest this is usually done within one-time closures:
-
extern "C" { fn clear (_: &'_ mut c_int); }
fn main ()
{
let mut array: [c_int; 4] = [1, 2, 3, 4];
array.iter_mut().for_each(|x| unsafe { clear(x); });
assert_eq!(array, [0, 0, 0, 0]);
}