Fundamentals 14 min read

What Rust’s ‘unsafe’ Reveals About Real‑World Memory and Thread Safety

A PLDI'20 study of open‑source Rust projects shows that while Rust’s safe code effectively prevents most memory errors, many bugs still stem from unsafe blocks, and even safe code can suffer thread‑safety issues, highlighting the need for better tooling and developer awareness.

Huawei Cloud Developer Alliance
Huawei Cloud Developer Alliance
Huawei Cloud Developer Alliance
What Rust’s ‘unsafe’ Reveals About Real‑World Memory and Thread Safety

Introduction

In recent years Rust has attracted massive attention for offering C/C++‑level performance while guaranteeing high safety, making it a promising choice for system programming. This article reviews a comprehensive safety investigation of Rust open‑source projects presented at PLDI'20.

Study Overview

The researchers examined five Rust applications, five widely used Rust libraries, and two vulnerability databases, covering 850 unsafe code usages, 70 memory‑safety defects, and 100 thread‑safety defects.

Memory Safety Analysis

Seventy memory‑safety bugs were classified by root cause (where the faulty code was written) and effect (where the error manifested). The cause‑effect relationship was expressed as four categories: safe→safe, safe→unsafe, unsafe→safe, unsafe→unsafe.

Traditional memory‑safety taxonomy was also applied, splitting bugs into spatial (Wrong Access) and temporal (Lifetime Violation) issues, with sub‑categories such as buffer overflow, null‑pointer dereference, use‑after‑free, double free, etc.

Only one memory‑safety problem did not involve unsafe code; it existed in an early Rust v0.3 version and has been fixed in later stable releases. All other memory‑safety bugs in stable versions are tied to unsafe code.

However, checking unsafe blocks alone is insufficient because some bugs originate in safe code but manifest in unsafe sections. The article provides an example where a raw pointer created in safe code becomes a dangling pointer after a match expression, leading to use‑after‑free. The corrected version moves the object’s lifetime into the safe region, eliminating the bug without touching unsafe code.

pub fn sign(data: Option<&[u8]>) {</code><code>    let p = match data {</code><code>        Some(d) => BioSlice::new(d).as_ptr(),</code><code>        None => std::ptr::null_mut(),</code><code>    };</code><code>    unsafe {</code><code>        let _cms = cvt_p(CMS_sign(p));</code><code>    }</code><code>}
pub fn sign(data: Option<&[u8]>) {</code><code>    let bio = match data {</code><code>        Some(d) => Some(BioSlice::new(d)),</code><code>        None => None,</code><code>    };</code><code>    let p = bio.map_or(std::ptr::null_mut(), |b| b.as_ptr());</code><code>    unsafe {</code><code>        let _cms = cvt_p(CMS_sign(p));</code><code>    }</code><code>}

Investigation of 600 unsafe usages showed that 42% were for reusing existing C code or calling C libraries, 22% for performance gains, and 14% for bypassing compiler checks to implement functionality. Unsafe code can be up to 4–5× faster than safe indexed access for certain memory operations.

Some unsafe annotations do not correspond to actual unsafe operations; for example, String::from_utf8_unchecked() is marked unsafe because it trusts the caller to provide valid UTF‑8, even though the function body contains no unsafe code.

Thread Safety Analysis

The study identified 100 thread‑safety bugs: 59 blocking (deadlock) and 41 non‑blocking (data race). Most deadlocks involved misuse of synchronization primitives such as Mutex and Condvar.

An example deadlock occurs when a read lock is held longer than expected, causing a subsequent write lock attempt to block:

fn do_request() {</code><code>    // client: Arc<RwLock<Inner>></code><code>    match connect(client.read().unwrap().m) {</code><code>        Ok(_) => {</code><code>            let mut inner = client.write().unwrap();</code><code>            inner.m = mbrs;</code><code>        }</code><code>        Err(_) => {}</code><code>    };</code><code>}

The fix moves the lock‑release out of the match expression:

fn do_request() {</code><code>    // client: Arc<RwLock<Inner>></code><code>    let result = connect(client.read().unwrap().m);</code><code>    match result {</code><code>        Ok(_) => {</code><code>            let mut inner = client.write().unwrap();</code><code>            inner.m = mbrs;</code><code>        }</code><code>        Err(_) => {}</code><code>    };</code><code>}

Among the 41 non‑blocking bugs, 38 were due to improper protection of shared resources; 23 occurred in unsafe code and 15 in safe code, demonstrating that safe Rust does not guarantee freedom from data races.

Recommendations for Rust Defect‑Detection Tools

Improve IDEs to automatically display variable lifetimes, especially for objects returned by lock() methods.

Implement static analysis for memory‑safety issues; a prototype scanner discovered four previously unknown memory bugs in the studied projects.

Implement static analysis for double‑locking problems; the same scanner found six previously unknown deadlocks.

Adopt dynamic detection, fuzzing, and other testing techniques as complementary approaches.

Conclusion

Rust’s safe code is highly effective at preventing spatial and temporal memory‑safety bugs; all stable‑release memory bugs involve unsafe code.

Many bugs still stem from unsafe code, but a significant number also originate in safe code due to logical errors or misunderstood lifetimes.

Thread‑safety problems can arise in both safe and unsafe code, even when code follows Rust’s language rules.

Misunderstanding of Rust’s lifetime semantics is a major source of defects.

Developing dedicated static and dynamic defect‑detection tools for Rust is essential.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Rustthread safetyMemory Safetystatic analysisunsafe
Huawei Cloud Developer Alliance
Written by

Huawei Cloud Developer Alliance

The Huawei Cloud Developer Alliance creates a tech sharing platform for developers and partners, gathering Huawei Cloud product knowledge, event updates, expert talks, and more. Together we continuously innovate to build the cloud foundation of an intelligent world.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.