For example, a C library might have the following API:
// All examples below assume these two types.
extern "C" {
type Foo;
type Bar;
}
extern "C" {
fn foo_each_bar(
foo: *mut Foo,
callback_data: *mut c_void,
callback: unsafe extern "C" fn(*mut c_void, *mut Bar),
);
}
However, the c_void pointee makes it harder to use (having to cast to and from *mut c_void) and more error-prone (having no real type safety other than "it's a raw pointer and unsafe to deref").
We could, instead, allow this definition:
extern "C" {
fn foo_each_bar<T>(
foo: *mut Foo,
callback_data: *mut T,
callback: unsafe extern "C" fn(*mut T, *mut Bar),
);
}
This is valid because we can fully compute the call ABI for foo_each_bar::<T> without knowing T (and this is something rustc has been able to do independently of LLVM for a while now).
If, e.g. <T> is replaced with <T: ?Sized>, the definition would be disallowed, since the layout of *mut T would then depend on the choice of T, as opposed to always being a pointer scalar.
If that last example works, we can also combine it with Rust references:
extern "C" {
fn foo_each_bar<T>(
foo: &Foo,
callback_data: &mut T,
callback: extern "C" fn(&mut T, &Bar),
);
}
Note how the callback no longer needs to be unsafe (since it doesn't take raw pointers)!
An adapter for a closure being used as the callback can be as simple as:
extern "C" fn callback(f: &mut impl FnMut(&Bar), bar: &Bar) {
f(bar);
}
(or even just |f, bar| f(bar) if we start coercing closures to non-Rust-ABI fn pointers)
cc @rust-lang/compiler
For example, a C library might have the following API:
However, the
c_voidpointee makes it harder to use (having to cast to and from*mut c_void) and more error-prone (having no real type safety other than "it's a raw pointer and unsafe to deref").We could, instead, allow this definition:
This is valid because we can fully compute the call ABI for
foo_each_bar::<T>without knowingT(and this is somethingrustchas been able to do independently of LLVM for a while now).If, e.g.
<T>is replaced with<T: ?Sized>, the definition would be disallowed, since the layout of*mut Twould then depend on the choice ofT, as opposed to always being a pointer scalar.If that last example works, we can also combine it with Rust references:
Note how the callback no longer needs to be unsafe (since it doesn't take raw pointers)!
An adapter for a closure being used as the callback can be as simple as:
(or even just
|f, bar| f(bar)if we start coercing closures to non-Rust-ABIfnpointers)cc @rust-lang/compiler