Files
Sprimo/skills/unsafe-checker/rules/union-01-avoid-except-ffi.md
2026-02-12 22:58:33 +08:00

2.6 KiB

id, original_id, level, impact
id original_id level impact
union-01 P.UNS.UNI.01 P HIGH

Avoid Union Except for C Interop

Summary

Only use union for FFI with C code. For Rust-only code, use enum with explicit tags.

Rationale

  • Unions require unsafe to read (any field access is unsafe)
  • Easy to read wrong field, causing undefined behavior
  • Enums are type-safe and the compiler tracks the active variant
  • Unions don't run destructors properly

Bad Example

// DON'T: Use union for space optimization in Rust-only code
union IntOrFloat {
    i: i32,
    f: f32,
}

fn bad_usage() {
    let mut u = IntOrFloat { i: 42 };

    // BAD: Reading wrong field is UB
    let f = unsafe { u.f };  // UB if i was the last written field
}

// DON'T: Use union for variant types
union Variant {
    string: std::mem::ManuallyDrop<String>,
    number: i64,
}

// Problems:
// 1. Must manually track which variant is active
// 2. Must manually call drop on String variant
// 3. Easy to have memory leaks or double-free

Good Example

// DO: Use enum for variant types in Rust
enum Variant {
    String(String),
    Number(i64),
}

// Compiler tracks active variant, runs correct destructor

// DO: Use union only for C FFI
#[repr(C)]
union CUnion {
    i: i32,
    f: f32,
}

// When interfacing with C code that uses this union
extern "C" {
    fn c_function_returns_union() -> CUnion;
    fn c_function_takes_union(u: CUnion);
}

// DO: Wrap in safe API with explicit variant tracking
#[repr(C)]
pub struct SafeUnion {
    tag: u8,
    data: CUnion,
}

impl SafeUnion {
    pub fn as_int(&self) -> Option<i32> {
        if self.tag == 0 {
            // SAFETY: Tag indicates integer variant is active
            Some(unsafe { self.data.i })
        } else {
            None
        }
    }
}

When Union Is Appropriate

  1. C FFI: Matching C union layout for interoperability
  2. MaybeUninit: The standard library uses union internally
  3. Very low-level optimization: Only after profiling and careful safety analysis

Alternatives to Union

Use Case Instead of Union Use
Variant types union + tag enum
Optional value union + bool Option<T>
Type punning union transmute or from_ne_bytes
Uninitialized memory union MaybeUninit<T>

Checklist

  • Is this for C FFI? If not, use enum
  • If union is necessary, is there a tag tracking active variant?
  • Are destructors handled correctly for Drop types?
  • Is the union #[repr(C)] for FFI?
  • union-02: Don't use union variants across lifetimes
  • ffi-13: Ensure consistent data layout