diff --git a/src/ffi.md b/src/ffi.md index 03693bb..d766cf2 100644 --- a/src/ffi.md +++ b/src/ffi.md @@ -737,11 +737,11 @@ void foo(struct Foo *arg); void bar(struct Bar *arg); ``` -To do this in Rust, let’s create our own opaque types with `enum`: +To do this in Rust, let’s create our own opaque types: ```rust -pub enum Foo {} -pub enum Bar {} +#[repr(C)] pub struct Foo { _private: [u8; 0] } +#[repr(C)] pub struct Bar { _private: [u8; 0] } extern "C" { pub fn foo(arg: *mut Foo); @@ -750,7 +750,18 @@ extern "C" { # fn main() {} ``` -By using an `enum` with no variants, we create an opaque type that we can’t -instantiate, as it has no variants. But because our `Foo` and `Bar` types are +By including a private field and no constructor, +we create an opaque type that we can't instantiate outside of this module. +(A struct with no field could be instantiated by anyone.) +We also want to use this type in FFI, so we have to add `#[repr(C)]`. +And to avoid warning around using `()` in FFI, we instead use an empty array, +which works just as well as an empty type but is FFI-compatible. + +But because our `Foo` and `Bar` types are different, we’ll get type safety between the two of them, so we cannot accidentally pass a pointer to `Foo` to `bar()`. + +Notice that it is a really bad idea to use an empty enum as FFI type. +The compiler relies on empty enums being uninhabited, so handling values of type +`&Empty` is a huge footgun and can lead to buggy program behavior (by triggering +undefined behavior).