💥 It is said that Rust bids farewell to segmentation faults forever, yet I encountered a <span>cargo build</span> crash with SIGSEGV just 3 seconds later.Memory safety? Yes, but it only guarantees “safety” without ensuring “correctness”.Today, this post will dissect the three UB monsters I created with Rust, along with a troubleshooting toolbox, which I recommend saving and sharing to help you avoid detours.
| Commit | Phenomenon | Cause |
| commit 3ea74 | <span>unsafe</span> dereferencing <span>*mut T</span> immediately followed by <span>free</span> |
Dangling pointer |
| commit 8c112 | <span>tokio::spawn</span> closure capturing <span>&mut self</span> |
Data race |
| commit 9f5ab | <span>Vec::set_len</span> increased by 1, new slot uninitialized |
Memory uninitialized |
Commonality: All compilations passed, but miri failed all tests.
Key Point 1: Dangling Pointers – A Landmine Beyond the “Safe” Boundary
rust
let<span><span>mut</span></span><span> x </span><span><span>=</span></span><span><span>Box</span></span><span><span>::</span></span><span><span>new</span></span><span><span>(</span></span><span><span>42</span></span><span><span>)</span></span><span><span>;let</span></span><span> p </span><span><span>=</span></span><span><span>&</span></span><span><span>mut</span></span><span><span>*</span></span><span>x </span><span><span>as</span></span><span><span>*</span></span><span><span>mut</span></span><span><span>i32</span></span><span><span>;</span></span><span><span>drop</span></span><span><span>(</span></span><span>x</span><span><span>);</span></span><span><span>// Memory has been freed</span></span><span><span>unsafe</span></span><span><span>{</span></span><span><span>*</span></span><span>p </span><span><span>=</span></span><span><span>0</span></span><span><span>;</span></span><span><span>}</span></span><span><span>// BOOM</span></span>
✅ Defusal Plan
-
Use
<span>NonNull<T></span>to mark “potentially invalid” pointers -
Add lifetimes
<span>'a</span>and<span>PhantomData</span>to the type to ensure<span>&self</span>lifespan ≥ pointer usage period -
If raw pointers are necessary, implement a
<span>Drop</span>guard: set the pointer to<span>null</span>in<span>Drop</span>, and check for null in subsequent<span>unsafe</span>blocks
Key Point 2: “Invisible &mut” in Asynchronous Closures
rust
struct<span>Conn</span><span>{</span><span> buf</span><span>:</span><span>[</span><span>u8</span><span>;</span><span>4096</span><span>]</span><span>}</span>
<span>impl</span><span>Conn</span><span>{</span><span>async</span><span>fn</span><span>read</span><span>(</span><span>&</span><span>mut</span><span>self</span><span>)</span><span>{</span><span>tokio</span><span>::</span><span>spawn</span><span>(</span><span>async</span><span>move</span><span>{</span><span>self</span><span>.</span><span>buf</span><span>[</span><span>0</span><span>]</span><span>=</span><span>1</span><span>;</span><span>// Compiled, but crashes on run</span><span>}</span><span>)</span><span>;</span><span>}</span><span>}</span>
Issue:<span>&mut self</span><code><span> is moved to a new thread by spawn,</span><strong><span> leading to two &mut</span></strong><span> existing simultaneously = data race.</span>
✅ Defusal Plan
-
Extract shared fields into
<span>Arc<Mutex<T>></span><span> or </span><code><span>RwLock</span> -
Use message channels (
<span>tokio::mpsc</span>) to return “memory modification rights” to a single thread -
If zero-copy is desired, use
<span>tokio::io::AsyncReadExt::read_buf</span>+<span>Vec<u8></span>pooling to avoid mutable references across threads
Key Point 3: The “Uninitialized Trap” of Vec::set_len
rust
let mut v = Vec::with_capacity(10);
unsafe { v.set_len(5); }// 0..5 are garbage
v[0] // UB: Reading uninitialized memory
✅ Defusal Plan
-
Use
<span>resize(n, default)</span><span> or </span><code><span>vec![val; n]</span>for safe initialization -
If you must call
<span>set_len</span>directly, write before expanding: fill new slots using<span>ptr::write</span>, then<span>set_len(old + n)</span> -
Run
<span>cargo +nightly miri test</span><span> to set CI gates,</span><strong><span> catching any uninitialized reads instantly</span></strong>
Troubleshooting Toolbox (Tested End-to-End)
| Tool | Scenario | Command Example |
| miri | Detect UB | cargo +nightly miri test |
| san | Runtime overflow/out-of-bounds | RUSTFLAGS=”-Z sanitizer=address” cargo test |
| cargo-asm | View generated assembly | cargo asm –rust |
| tokio-console | Async scheduling anomalies | <span>console-subscriber</span> + <span>tokio-console</span> |
| valgrind | Raw C interaction segmentation faults | valgrind –tool=memhog ./target/debug/app |
A Summary Chart: Rust UB Quick Reference
(Save to your phone, review for 10 seconds before writing unsafe code)
Dangling pointer → NonNull + PhantomData<span>Data race → Arc<Mutex> or channels</span><span>Uninitialized → ptr::write then set_len</span>
Comments Section Open
👇 Share your “safe but crashed” experiences with Rust, and five lucky winners will receive miri stickers + a physical copy of “The Rust Programming Language”, let’s write memory safety into CI and leave crashes in the past!
Memory safety ≠logical correctness; Rust is not magic, it is a sharper scalpel—don’t cut yourself.