1{{#title Shared types — Rust ♡ C++}} 2# Shared types 3 4Shared types enable *both* languages to have visibility into the internals of a 5type. This is in contrast to opaque Rust types and opaque C++ types, for which 6only one side gets to manipulate the internals. 7 8Unlike opaque types, the FFI bridge is allowed to pass and return shared types 9by value. 10 11The order in which shared types are written is not important. C++ is order 12sensitive but CXX will topologically sort and forward-declare your types as 13necessary. 14 15## Shared structs and enums 16 17For enums, only C-like a.k.a. unit variants are currently supported. 18 19```rust,noplayground 20#[cxx::bridge] 21mod ffi { 22 struct PlayingCard { 23 suit: Suit, 24 value: u8, // A=1, J=11, Q=12, K=13 25 } 26 27 enum Suit { 28 Clubs, 29 Diamonds, 30 Hearts, 31 Spades, 32 } 33 34 unsafe extern "C++" { 35 fn deck() -> Vec<PlayingCard>; 36 fn sort(cards: &mut Vec<PlayingCard>); 37 } 38} 39``` 40 41## The generated data structures 42 43Shared structs compile to an aggregate-initialization compatible C++ struct. 44 45Shared enums compile to a C++ `enum class` with a sufficiently sized integral 46base type decided by CXX. 47 48```cpp 49// generated header 50 51struct PlayingCard final { 52 Suit suit; 53 uint8_t value; 54}; 55 56enum class Suit : uint8_t { 57 Clubs = 0, 58 Diamonds = 1, 59 Hearts = 2, 60 Spades = 3, 61}; 62``` 63 64Because it is not UB in C++ for an `enum class` to hold a value different from 65all of the listed variants, we use a Rust representation for shared enums that 66is compatible with this. The API you'll get is something like: 67 68```rust,noplayground 69#[derive(Copy, Clone, PartialEq, Eq)] 70#[repr(transparent)] 71pub struct Suit { 72 pub repr: u8, 73} 74#[allow(non_upper_case_globals)] 75impl Suit { 76 pub const Clubs: Self = Suit { repr: 0 }; 77 pub const Diamonds: Self = Suit { repr: 1 }; 78 pub const Hearts: Self = Suit { repr: 2 }; 79 pub const Spades: Self = Suit { repr: 3 }; 80} 81``` 82 83Notice you're free to treat the enum as an integer in Rust code via the public 84`repr` field. 85 86Pattern matching with `match` still works but will require you to write wildcard 87arms to handle the situation of an enum value that is not one of the listed 88variants. 89 90```rust,noplayground 91fn main() { 92 let suit: Suit = /*...*/; 93 match suit { 94 Suit::Clubs => ..., 95 Suit::Diamonds => ..., 96 Suit::Hearts => ..., 97 Suit::Spades => ..., 98 _ => ..., // fallback arm 99 } 100} 101``` 102 103If a shared struct has generic lifetime parameters, the lifetimes are simply not 104represented on the C++ side. C++ code will need care when working with borrowed 105data (as usual in C++). 106 107```rust,noplayground 108#[cxx::bridge] 109mod ffi { 110 struct Borrowed<'a> { 111 flags: &'a [&'a str], 112 } 113} 114``` 115 116```cpp 117// generated header 118 119struct Borrowed final { 120 rust::Slice<const rust::Str> flags; 121}; 122``` 123 124## Enum discriminants 125 126You may provide explicit discriminants for some or all of the enum variants, in 127which case those numbers will be propagated into the generated C++ `enum class`. 128 129```rust,noplayground 130#[cxx::bridge] 131mod ffi { 132 enum SmallPrime { 133 Two = 2, 134 Three = 3, 135 Five = 5, 136 Seven = 7, 137 } 138} 139``` 140 141Variants without an explicit discriminant are assigned the previous discriminant 142plus 1. If the first variant has not been given an explicit discriminant, it is 143assigned discriminant 0. 144 145By default CXX represents your enum using the smallest integer type capable of 146fitting all the discriminants (whether explicit or implicit). If you need a 147different representation for reasons, provide a `repr` attribute. 148 149```rust,noplayground 150#[cxx::bridge] 151mod ffi { 152 #[repr(i32)] 153 enum Enum { 154 Zero, 155 One, 156 Five = 5, 157 Six, 158 } 159} 160``` 161 162```cpp 163// generated header 164 165enum class Enum : int32_t { 166 Zero = 0, 167 One = 1, 168 Five = 5, 169 Six = 6, 170}; 171``` 172 173## Extern enums 174 175If you need to interoperate with an already existing enum for which an existing 176C++ definition is the source of truth, make sure that definition is provided by 177some header in the bridge and then declare your enum *additionally* as an extern 178C++ type. 179 180```rust,noplayground 181#[cxx::bridge] 182mod ffi { 183 enum Enum { 184 Yes, 185 No, 186 } 187 188 extern "C++" { 189 include!("path/to/the/header.h"); 190 type Enum; 191 } 192} 193``` 194 195CXX will recognize this pattern and, instead of generating a C++ definition of 196the enum, will instead generate C++ static assertions asserting that the 197variants and discriminant values and integer representation written in Rust all 198correctly match the existing C++ enum definition. 199 200Extern enums support all the same features as ordinary shared enums (explicit 201discriminants, repr). Again, CXX will static assert that all of those things you 202wrote are correct. 203 204## Derives 205 206The following standard traits are supported in `derive(...)` within the CXX 207bridge module. 208 209- `Clone` 210- `Copy` 211- `Debug` 212- `Default` 213- `Eq` 214- `Hash` 215- `Ord` 216- `PartialEq` 217- `PartialOrd` 218 219Note that shared enums automatically always come with impls of `Copy`, `Clone`, 220`Eq`, and `PartialEq`, so you're free to omit those derives on an enum. 221 222```rust,noplayground 223#[cxx::bridge] 224mod ffi { 225 #[derive(Clone, Debug, Hash)] 226 struct ExampleStruct { 227 x: u32, 228 s: String, 229 } 230 231 #[derive(Hash, Ord, PartialOrd)] 232 enum ExampleEnum { 233 Yes, 234 No, 235 } 236} 237``` 238 239The derives naturally apply to *both* the Rust data type *and* the corresponding 240C++ data type: 241 242- `Hash` gives you a specialization of [`template <> struct std::hash<T>`][hash] in C++ 243- `PartialEq` produces `operator==` and `operator!=` 244- `PartialOrd` produces `operator<`, `operator<=`, `operator>`, `operator>=` 245 246[hash]: https://en.cppreference.com/w/cpp/utility/hash 247