{{#title Shared types — Rust ♡ C++}} # Shared types Shared types enable *both* languages to have visibility into the internals of a type. This is in contrast to opaque Rust types and opaque C++ types, for which only one side gets to manipulate the internals. Unlike opaque types, the FFI bridge is allowed to pass and return shared types by value. The order in which shared types are written is not important. C++ is order sensitive but CXX will topologically sort and forward-declare your types as necessary. ## Shared structs and enums For enums, only C-like a.k.a. unit variants are currently supported. ```rust,noplayground #[cxx::bridge] mod ffi { struct PlayingCard { suit: Suit, value: u8, // A=1, J=11, Q=12, K=13 } enum Suit { Clubs, Diamonds, Hearts, Spades, } unsafe extern "C++" { fn deck() -> Vec; fn sort(cards: &mut Vec); } } ``` ## The generated data structures Shared structs compile to an aggregate-initialization compatible C++ struct. Shared enums compile to a C++ `enum class` with a sufficiently sized integral base type decided by CXX. ```cpp // generated header struct PlayingCard final { Suit suit; uint8_t value; }; enum class Suit : uint8_t { Clubs = 0, Diamonds = 1, Hearts = 2, Spades = 3, }; ``` Because it is not UB in C++ for an `enum class` to hold a value different from all of the listed variants, we use a Rust representation for shared enums that is compatible with this. The API you'll get is something like: ```rust,noplayground #[derive(Copy, Clone, PartialEq, Eq)] #[repr(transparent)] pub struct Suit { pub repr: u8, } #[allow(non_upper_case_globals)] impl Suit { pub const Clubs: Self = Suit { repr: 0 }; pub const Diamonds: Self = Suit { repr: 1 }; pub const Hearts: Self = Suit { repr: 2 }; pub const Spades: Self = Suit { repr: 3 }; } ``` Notice you're free to treat the enum as an integer in Rust code via the public `repr` field. Pattern matching with `match` still works but will require you to write wildcard arms to handle the situation of an enum value that is not one of the listed variants. ```rust,noplayground fn main() { let suit: Suit = /*...*/; match suit { Suit::Clubs => ..., Suit::Diamonds => ..., Suit::Hearts => ..., Suit::Spades => ..., _ => ..., // fallback arm } } ``` If a shared struct has generic lifetime parameters, the lifetimes are simply not represented on the C++ side. C++ code will need care when working with borrowed data (as usual in C++). ```rust,noplayground #[cxx::bridge] mod ffi { struct Borrowed<'a> { flags: &'a [&'a str], } } ``` ```cpp // generated header struct Borrowed final { rust::Slice flags; }; ``` ## Enum discriminants You may provide explicit discriminants for some or all of the enum variants, in which case those numbers will be propagated into the generated C++ `enum class`. ```rust,noplayground #[cxx::bridge] mod ffi { enum SmallPrime { Two = 2, Three = 3, Five = 5, Seven = 7, } } ``` Variants without an explicit discriminant are assigned the previous discriminant plus 1. If the first variant has not been given an explicit discriminant, it is assigned discriminant 0. By default CXX represents your enum using the smallest integer type capable of fitting all the discriminants (whether explicit or implicit). If you need a different representation for reasons, provide a `repr` attribute. ```rust,noplayground #[cxx::bridge] mod ffi { #[repr(i32)] enum Enum { Zero, One, Five = 5, Six, } } ``` ```cpp // generated header enum class Enum : int32_t { Zero = 0, One = 1, Five = 5, Six = 6, }; ``` ## Extern enums If you need to interoperate with an already existing enum for which an existing C++ definition is the source of truth, make sure that definition is provided by some header in the bridge and then declare your enum *additionally* as an extern C++ type. ```rust,noplayground #[cxx::bridge] mod ffi { enum Enum { Yes, No, } extern "C++" { include!("path/to/the/header.h"); type Enum; } } ``` CXX will recognize this pattern and, instead of generating a C++ definition of the enum, will instead generate C++ static assertions asserting that the variants and discriminant values and integer representation written in Rust all correctly match the existing C++ enum definition. Extern enums support all the same features as ordinary shared enums (explicit discriminants, repr). Again, CXX will static assert that all of those things you wrote are correct. ## Derives The following standard traits are supported in `derive(...)` within the CXX bridge module. - `Clone` - `Copy` - `Debug` - `Default` - `Eq` - `Hash` - `Ord` - `PartialEq` - `PartialOrd` Note that shared enums automatically always come with impls of `Copy`, `Clone`, `Eq`, and `PartialEq`, so you're free to omit those derives on an enum. ```rust,noplayground #[cxx::bridge] mod ffi { #[derive(Clone, Debug, Hash)] struct ExampleStruct { x: u32, s: String, } #[derive(Hash, Ord, PartialOrd)] enum ExampleEnum { Yes, No, } } ``` The derives naturally apply to *both* the Rust data type *and* the corresponding C++ data type: - `Hash` gives you a specialization of [`template <> struct std::hash`][hash] in C++ - `PartialEq` produces `operator==` and `operator!=` - `PartialOrd` produces `operator<`, `operator<=`, `operator>`, `operator>=` [hash]: https://en.cppreference.com/w/cpp/utility/hash