Matching C Types with FFI Types
When you are dealing with C libraries and may need to support multiple platforms, Rust includes a bunch of "c types" to assist you.
Let's rewrite our Rust implementation of double_byte to use one of Rust's FFI types. These are defined for each platform to match the equivalent C code - they are type aliases.
#![allow(unused)] fn main() { mod rs { use std::ffi::c_char; pub fn double_byte(n: c_char) -> c_char { n.wrapping_mul(2) } } }
There's a few implications here to ponder:
- You no longer know exactly how big your
charis on your target platform. So you can no longer make assumptions beyond the lowest common denominator: the C standard does now require that acharhave at least 8 bits. - Could you safely assume that it's an
i8, and write accordingly? In a lot of cases, the answer is "yes". It's up to you.
Let's go and see what bindgen created:
#![allow(unused)] fn main() { pub fn double_byte(n: ::std::os::raw::c_char) -> ::std::os::raw::c_char; }
Oh dear. We have os::raw and std:ffi???
Some of Rust's Murkier Corners
When you dive deeply into the Rust standard library, a few things become less than clear. Here's the definition of std::os::raw:c_char:
#![allow(unused)] fn main() { macro_rules! alias_core_ffi { ($($t:ident)*) => {$( #[stable(feature = "raw_os", since = "1.1.0")] #[doc = include_str!(concat!("../../../../core/src/ffi/", stringify!($t), ".md"))] #[doc(cfg(all()))] pub type $t = core::ffi::$t; )*} } alias_core_ffi! { c_char c_schar c_uchar c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong c_float c_double c_void } }
That's a very long way of definiing type aliases into the FFI types we used. So that's ok then! We used the same type. How does FFI define it?
#![allow(unused)] fn main() { type_alias! { "c_char.md", c_char = c_char_definition::c_char; #[doc(cfg(all()))] } }
Oh boy, another macro! And now it points into a thing called c_char_definition? Following the definition, you see some of the magic that makes Rust work. In .rustup/toolchains/stable-aarch-apple-darwin/lib/rustlib/src/rust/library/core/src/ffi/mod.rs (whew!), there's a platform specific definition that maps all of the platform C types to the FFI types, which are in turn aliases into os types.
Isn't it nice to not have to worry about this, most of the time? Since the C ABI is the lingua-franca between languages, every single Rust implementation has to provide these.