2.6 KiB
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
- C FFI: Matching C union layout for interoperability
- MaybeUninit: The standard library uses union internally
- 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?
Related Rules
union-02: Don't use union variants across lifetimesffi-13: Ensure consistent data layout