mirror of
https://github.com/leonardomso/rust-skills.git
synced 2026-09-14 19:33:21 +08:00
5f0b2079f8
Add unsafe, concurrency, conversions, and pattern-matching categories plus rules across existing ones, and correct advice that was outdated for Rust 1.96.
3.6 KiB
3.6 KiB
unsafe-maybeuninit
Use
MaybeUninit<T>for uninitialized memory; never usemem::uninitialized()ormem::zeroed()for types with validity invariants.
Why It Matters
mem::uninitialized() was deprecated in Rust 1.39 and is immediate undefined behavior for any type whose bit patterns have validity invariants — bool (only 0 and 1 are valid), &T (must be non-null and aligned), NonZeroU32, char, and enum types. Even mem::zeroed() triggers UB for references and NonZero* types. MaybeUninit<T> is the correct abstraction: it wraps uninitialized memory without ever producing an invalid T, and the compiler cannot optimize around it incorrectly. The standard library uses MaybeUninit pervasively for its data structures.
Bad
use std::mem;
// Instant UB: `bool` has validity invariants; uninitialized bits are not
// guaranteed to be 0 or 1. The optimizer may miscompile code that follows.
let b: bool = unsafe { mem::uninitialized() };
// Also UB for references — a zero reference is immediately invalid.
let r: &u32 = unsafe { mem::zeroed() };
// Uninitialized array the wrong way — triggers UB during construction.
let mut buf: [u8; 1024] = unsafe { mem::uninitialized() };
Good
use std::mem::MaybeUninit;
// ---- 1. Single value ----
let mut x = MaybeUninit::<u32>::uninit();
x.write(42);
// SAFETY: we just wrote a valid u32 via `write`, so the value is initialized.
let value: u32 = unsafe { x.assume_init() };
// ---- 2. Array initialization (manual, element-by-element) ----
// `[const { MaybeUninit::uninit() }; N]` works for any `T` (no `Copy` bound).
let mut buf: [MaybeUninit<u8>; 1024] = [const { MaybeUninit::uninit() }; 1024];
for elem in &mut buf {
elem.write(0u8);
}
// SAFETY: every element was written above.
// `From<[MaybeUninit<T>; N]> for MaybeUninit<[T; N]>` is stable since Rust 1.95.
let buf: [u8; 1024] = unsafe {
MaybeUninit::<[u8; 1024]>::from(buf).assume_init()
};
// ---- 3. Growing a Vec into spare capacity ----
fn fill_vec(v: &mut Vec<u8>, extra: usize) {
v.reserve(extra);
let spare = v.spare_capacity_mut(); // &mut [MaybeUninit<u8>]
for slot in spare.iter_mut().take(extra) {
slot.write(0u8);
}
// SAFETY: we initialized `extra` elements in the spare capacity.
unsafe { v.set_len(v.len() + extra) };
}
Key Points
assume_initis only sound after every byte is initialized viawrite, an FFI call that fills the buffer, or another provably complete initialization path. Calling it on partially initialized memory is UB.- Create arrays of uninitialized memory with
[const { MaybeUninit::uninit() }; N](works for anyT). Convert a fully-initialized[MaybeUninit<T>; N]to[T; N]viaMaybeUninit::<[T; N]>::from(arr)thenassume_init()— theseFromconversions are stable since Rust 1.95. TheMaybeUninit::array_assume_inithelper remains nightly-only (featuremaybe_uninit_array_assume_init). Vec::spare_capacity_mutreturns&mut [MaybeUninit<T>]— the idiomatic way to write into Vec capacity before extending its length.- For zeroed memory where zero is a valid bit pattern for all fields (e.g.
u8,i32, plain C structs with no references),mem::zeroed()is technically sound butMaybeUninitis still preferred for clarity. mem::uninitializedis not just deprecated — it is#[deprecated(since = "1.39.0")]and has no safe migration; replace every usage withMaybeUninit.
See Also
- unsafe-safety-comment - document every unsafe block including
assume_initcalls - mem-with-capacity - use
with_capacitywhen size is known to avoid extra allocations