They are modelled using the pattern known as a tagged union. You can find an example of that here, which I have copied in below:
// This Enum has the same representation as ...
#[repr(C)]
enum MyEnum {
A(u32),
B(f32, u64),
C { x: u32, y: u8 },
D,
}
// ... this struct.
#[repr(C)]
struct MyEnumRepr {
tag: MyEnumDiscriminant,
payload: MyEnumFields,
}
// This is the discriminant enum.
#[repr(C)]
enum MyEnumDiscriminant { A, B, C, D }
// This is the variant union.
#[repr(C)]
union MyEnumFields {
A: MyAFields,
B: MyBFields,
C: MyCFields,
D: MyDFields,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct MyAFields(u32);
#[repr(C)]
#[derive(Copy, Clone)]
struct MyBFields(f32, u64);
#[repr(C)]
#[derive(Copy, Clone)]
struct MyCFields { x: u32, y: u8 }
// This struct could be omitted (it is a zero-sized type)
#[repr(C)]
#[derive(Copy, Clone)]
struct MyDFields;
The above describes the layout that Rust guarantees for an #[repr(C)] enum, but it is also the layout that miri uses for #[repr(Rust)] enums (except that the *Fields structs are not marked #[repr(C)] for a #[repr(Rust)] enum.) Of course, this is not guaranteed behavior, since nothing is guaranteed about the layout of #[repr(Rust)] types.