Rust Unions
A union is a type whose fields all share the same memory. A union holds one value at a time, and that value can be read in more than one way.
Size is the largest field
Every field starts at the same address. They overlap in one shared piece of memory. So the union needs room only for its biggest field. It also needs any padding that type needs:
union SmallOrBig { small: u8, big: u64 }
println!("size = {} bytes", std::mem::size_of::<SmallOrBig>());Write one field, read it back
Reading any field needs unsafe. Rust cannot know which field was written last. So it cannot promise the read makes sense:
union IntOrFloat { whole: i32, fraction: f32 }
let value = IntOrFloat { whole: 42 };
unsafe { println!("whole = {}", value.whole); }Read the same bits as another type
Writing one field fills the shared memory. Reading a different field does not convert the value — it reinterprets those same bits:
let bits = IntOrFloat { fraction: 2.5 };
unsafe { println!("2.5 as whole = {}", bits.whole); }A union can have methods
Methods and impl blocks work on a union, just like on a struct:
impl IntOrFloat {
fn size(&self) -> usize {
std::mem::size_of::<Self>()
}
}Fields must be Copy, or wrapped
A union cannot drop fields it cannot track. So fields must be Copy types. Numbers, bools and raw pointers qualify. Or wrap the field in ManuallyDrop:
union TextOrNum {
text: std::mem::ManuallyDrop<String>,
num: u64,
}The C mirror image
C libraries use unions heavily. With #[repr(C)], a Rust union matches the C layout — a Rust union is their mirror image for FFI:
#[repr(C)]
union Value { i: i32, f: f32 }Example
union IntOrFloat {
whole: i32,
fraction: f32,
}
fn main() {
let value = IntOrFloat { whole: 42 };
unsafe {
println!("as whole = {}", value.whole);
}
let bits = IntOrFloat { fraction: 2.5 };
unsafe {
println!("2.5 read as whole = {}", bits.whole);
}
}Key points
- std::mem::transmute does this same bit reinterpretation as a one-off function call.
- It works between two same-sized types.
- In everyday Rust, an enum is the safe answer to "one of several types".
- It stores a tag.
- The tag says which kind is inside.
- A struct gives every field its own memory, side by side.
- A union stores one value in memory shared by all its fields.
In this example, IntOrFloat is a union of an i32 and an f32. Both are 4 bytes, so the union is 4 bytes: whole and fraction are two names for the same memory. First we store the whole number 42 and read it back as whole, which prints 42. Then we store the fraction 2.5. Reading that same memory as whole does not convert 2.5 into a whole number. It reinterprets the raw bits of 2.5 as an i32. That value is 1075838976. Both reads sit inside unsafe blocks, because only the programmer knows which field was written last. Rust Transmute shows std::mem::transmute in full.
Note
A union stores no tag — that missing check is exactly why reading it is unsafe.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why does reading a union field require unsafe?