25.7 Accessing Fields of Unions

Rust includes union types, similar to C unions, allowing different fields to share the same memory location. Unlike Rust’s enums, unions are untagged; there is no built-in mechanism to track which field currently holds valid data.

// A union that can store either an integer or a floating-point number.
union IntOrFloat {
    i: i32,
    f: f32,
}

fn main() {
    // Initialize the union, specifying one field.
    let mut u = IntOrFloat { i: 10 };

    // Accessing union fields (read or write) is unsafe.
    unsafe {
        // Write to the integer field.
        u.i = 20;
        println!("Union as integer: {}", u.i); // OK: Reading the field we just wrote.

        // Write to the float field. This overwrites the memory occupied by `i`.
        u.f = 3.14;
        println!("Union as float: {}", u.f); // OK: Reading the field we just wrote.

        // Reading `i` after writing `f` reads the raw bytes of the float
        // interpreted as an integer. This is usually logically incorrect
        // and can be undefined behavior depending on the types and values involved.
        // The specific bit pattern of 3.14f32 might happen to be a valid i32,
        // but this is not guaranteed and relies on implementation details.
        println!("Union as integer after float write: {}", u.i);
    }
}

Accessing any field of a union is unsafe. The compiler cannot guarantee that the field being accessed corresponds to the type of data last written to that memory location. Reading the bits of one type (f32 in the example) as if they were another type (i32) can lead to incorrect program logic or, depending on the types involved (e.g., types with validity invariants like bool or references), undefined behavior. The programmer is responsible for tracking which field is currently active and valid. Unions are typically used in specific low-level scenarios like FFI or implementing space-efficient data structures.