Integer Representation Attributes
Integer representation attributes (#[repr(u8)], #[repr(u16)], #[repr(u32)], #[repr(u64)], #[repr(u128)], #[repr(usize)], #[repr(i8)], #[repr(i16)], #[repr(i32)], #[repr(i64)], #[repr(i128)], #[repr(isize)]) specify the exact primitive integer type used to store the discriminant of an enum.
Fieldless Enums with #[repr(Int)]
When applied to a fieldless enum, #[repr(Int)] guarantees that the enum has the exact same size, alignment, and ABI as the chosen primitive integer type Int. Every discriminant value must fit within the range of Int, or compilation fails with error E0080.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Opcode {
Nop = 0x00,
Load = 0x10,
Store = 0x20,
Halt = 0xFF,
}
#[repr(i16)]
#[derive(Debug, Clone, Copy)]
enum ElevationTier {
Subterranean = -500,
SeaLevel = 0,
Alpine = 2500,
}
fn main() {
println!(
"Opcode: size = {}, align = {}",
size_of::<Opcode>(),
align_of::<Opcode>()
);
println!(
"ElevationTier: size = {}, align = {}",
size_of::<ElevationTier>(),
align_of::<ElevationTier>()
);
println!("Opcode::Store as u8 = {:#04x}", Opcode::Store as u8);
}
Opcode: size = 1, align = 1
ElevationTier: size = 2, align = 2
Opcode::Store as u8 = 0x20
Data-Carrying Enums with #[repr(Int)]
When applied to an enum whose variants carry fields, #[repr(Int)] forces the compiler to represent the enum as a union of #[repr(C)] structs where the first field of every variant is the Int discriminant at byte offset 0, followed by that variant's fields in declaration order.
#[repr(u8)]
enum WireEvent {
Tick, // { tag: u8 }
KeyPress(u8, u16), // { tag: u8, _0: u8, _1: u16 }
Resize { w: u32, h: u32 } // { tag: u8, pad: [u8; 3], w: u32, h: u32 }
}
Memory Layout of `WireEvent` (12 bytes total, align 4):
Variant `KeyPress(0x41, 0x0102)`:
+----------+----------+-------------------+-----------------------+
| tag: u8 | _0: u8 | _1: u16 | unused (8 bytes) |
+----------+----------+-------------------+-----------------------+
+0 +1 +2..+3 +4..+11
Variant `Resize { w: 1920, h: 1080 }`:
+----------+----------+-------------------+-----------------------+
| tag: u8 | pad (3B) | w: u32 | h: u32 |
+----------+----------+-------------------+-----------------------+
+0 +1..+3 +4..+7 +8..+11
Notice how in KeyPress(u8, u16), the 1-byte _0: u8 field sits immediately at offset +1 right after the 1-byte tag: u8 at offset +0, whereas in #[repr(C, u8)] the payload struct is aligned separately from the tag.
Comparing #[repr(u8)] and #[repr(C, u8)] on Data Enums
| Representation | Equivalent Structure | KeyPress(u8, u16) Size |
|---|---|---|
#[repr(u8)] |
union { struct { tag: u8, ... }, ... } (tag fused into each variant struct) |
4 bytes (tag at +0, u8 at +1, u16 at +2..+3) |
#[repr(C, u8)] |
struct { tag: u8, pad, payload: union { ... } } (tag separate from payload union) |
6 bytes (tag at +0, 1 byte pad at +1, struct(u8, pad, u16) at +2..+5) |
#[repr(u8)]
enum FusedTag {
KeyPress(u8, u16),
}
#[repr(C, u8)]
enum SeparateTag {
KeyPress(u8, u16),
}
fn main() {
println!("FusedTag (#[repr(u8)]) size: {} bytes", size_of::<FusedTag>());
println!("SeparateTag (#[repr(C, u8)]) size: {} bytes", size_of::<SeparateTag>());
}
FusedTag (#[repr(u8)]) size: 4 bytes
SeparateTag (#[repr(C, u8)]) size: 6 bytes
Effect on Niche Optimization
Applying any explicit integer representation attribute (#[repr(u8)], #[repr(C)], etc.) fixes the discriminant representation and disables niche layout optimizations on that enum itself.