I’m very happy to see improved FFI in the 2017 goals.
In that area one thing that bugs me to no end is that pub extern "C" fn
is not sufficient for C interoperability*.
*
(I know I could probably find Rust’s internal hashed name somehow and use that in C, but that just makes Rust/C interoperability even less usable).
Rust’s unique strength is in ability to create a library usable directly from C/C++ (and other languages via FFI), but in such case declarations of all API functions require #[no_mangle]
.
I’m fully aware that calling convention and name mangling are orthogonal concepts in theory. However, in Rust they aren’t orthogonal already, because function declarations it extern "C" {}
imply mangling automagically (which is very handy).
In Rust pub extern "C" fn
ends up creating an exotic mixed combination, instead of satisfying the most common use case of compatibility with C.
Inconsistent mangling defaults for extern "C"
also means that this:
pub extern "C" fn foo() {};
won’t link with this:
extern "C" { fn foo(); }
To me that’s a language wart: similar syntax has different meaning in different contexts, and the shortest and most obvious syntax (especially for users familiar with C++) does not seem to work and causes a link error (and link errors aren’t nearly as nice as other errors in Rust).
To me adding #[no_mangle]
to C functions is as practical as having to add #[no_ternary]
to usize
.
I’m proposing:
-
No change for
extern "C"
that isn’t “public” (this form is commonly used for callbacks). -
Add
#[mangle]
to preserve orthogonality of calling convention and mangling, so that it’s still possible to have Rust-specific symbol name with foreign calling convention. -
Have a transition period where
pub extern "C"
without#[mangle]
causes a warning. It’d logically complement the existing warning for privateextern "C"
having#[no_mangle]
. -
Make “public”
pub extern "C" fn
default to no name mangling (which would make it consistent withextern "C" {}
in Rust and C++).
I’m not sure about exact definition of “public”. Does pub extern
in a private module count as public? pub(restrict)
? For interoperability with C this is only important for symbols exported from the crate.
While it’s theoretically a breaking change, I think it’s very unlikely to break anything in practice. Admittedly, if it breaks, it would be due to a duplicate symbol, which is an ugly error to have.
In case breaking change is unacceptable, then a minimal proposal is:
- Add
#[mangle]
(or maybe#[mangle(Rust)]
/#[mangle(C)]
). It will allow linters to flag uses ofpub extern "C"
that don’t also specify what name mangling is required. This way a forgotten#[no_mangle]
can be found other than by a linker error.