id: union-01 original_id: P.UNS.UNI.01 level: P impact: 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
你可能感兴趣的文章
Do Not Expose Raw Pointers in Public APIs
Unsafe Code Authors Must Verify Safety Invariants
Ensure Consistent Data Layout for Custom Types
Add Safety Section in Docs for Public Unsafe Functions
Do Not Use Union Variants Across Different Lifetimes
Use MaybeUninit
Ensure C-ABI Compatibility for Strings Between Rust and C
Do Not Dereference Pointers Cast to Misaligned Types
Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types