From 8419b3095ec0f9ec8a17e0dd79faf7a152331c1e Mon Sep 17 00:00:00 2001 From: Frances Wingerter <91758128+fw-immunant@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:46:42 +0000 Subject: [PATCH] A few fixes for the unsafe chapter (#2805) See individual commits in https://github.com/google/comprehensive-rust/pull/2805. Fixes #2734. --- src/unsafe-rust/dereferencing.md | 44 ++++++++++++-------- src/unsafe-rust/mutable-static.md | 17 ++++---- src/unsafe-rust/unsafe-functions.md | 6 +-- src/unsafe-rust/unsafe-functions/extern-c.md | 5 ++- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/unsafe-rust/dereferencing.md b/src/unsafe-rust/dereferencing.md index 7e50e3de..54427dde 100644 --- a/src/unsafe-rust/dereferencing.md +++ b/src/unsafe-rust/dereferencing.md @@ -8,27 +8,32 @@ Creating pointers is safe, but dereferencing them requires `unsafe`: ```rust,editable fn main() { - let mut s = String::from("careful!"); + let mut x = 10; - let r1 = &raw mut s; - let r2 = r1 as *const String; + let p1: *mut i32 = &raw mut x; + let p2 = p1 as *const i32; - // SAFETY: r1 and r2 were obtained from references and so are guaranteed to - // be non-null and properly aligned, the objects underlying the references - // from which they were obtained are live throughout the whole unsafe - // block, and they are not accessed either through the references or - // concurrently through any other pointers. + // SAFETY: p1 and p2 were created by taking raw pointers to a local, so they + // are guaranteed to be non-null, aligned, and point into a single (stack-) + // allocated object. + // + // The object underlying the raw pointers lives for the entire function, so + // it is not deallocated while the raw pointers still exist. It is not + // accessed through references while the raw pointers exist, nor is it + // accessed from other threads concurrently. unsafe { - println!("r1 is: {}", *r1); - *r1 = String::from("uhoh"); - println!("r2 is: {}", *r2); + dbg!(*p1); + *p1 = 6; + // Mutation may soundly be observed through a raw pointer, like in C. + dbg!(*p2); } - // NOT SAFE. DO NOT DO THIS. + // UNSOUND. DO NOT DO THIS. /* - let r3: &String = unsafe { &*r1 }; - drop(s); - println!("r3 is: {}", *r3); + let r: &i32 = unsafe { &*p1 }; + dbg!(r); + x = 50; + dbg!(r); // Object underlying the reference has been mutated. This is UB. */ } ``` @@ -52,8 +57,11 @@ In the case of pointer dereferences, this means that the pointers must be In most cases the pointer must also be properly aligned. -The "NOT SAFE" section gives an example of a common kind of UB bug: `*r1` has -the `'static` lifetime, so `r3` has type `&'static String`, and thus outlives -`s`. Creating a reference from a pointer requires _great care_. +The "UNSOUND" section gives an example of a common kind of UB bug: naïvely +taking a reference to the dereference of a raw pointer sidesteps the compiler's +knowledge of what object the reference is actually pointing to. As such, the +borrow checker does not freeze `x` and so we are able to modify it despite the +existence of a reference to it. Creating a reference from a pointer requires +_great care_. diff --git a/src/unsafe-rust/mutable-static.md b/src/unsafe-rust/mutable-static.md index d39046d6..a02a8f82 100644 --- a/src/unsafe-rust/mutable-static.md +++ b/src/unsafe-rust/mutable-static.md @@ -14,15 +14,18 @@ fn main() { } ``` -However, since data races can occur, it is unsafe to read and write mutable -static variables: +However, mutable static variables are unsafe to read and write because multiple +threads could do so concurrently without synchronization, constituting a data +race. + +Using mutable statics soundly requires reasoning about concurrency without the +compiler's help: ```rust,editable static mut COUNTER: u32 = 0; fn add_to_counter(inc: u32) { // SAFETY: There are no other threads which could be accessing `COUNTER`. - #[allow(static_mut_refs)] unsafe { COUNTER += inc; } @@ -32,7 +35,6 @@ fn main() { add_to_counter(42); // SAFETY: There are no other threads which could be accessing `COUNTER`. - #[allow(static_mut_refs)] unsafe { dbg!(COUNTER); } @@ -41,13 +43,12 @@ fn main() {
-- The program here is safe because it is single-threaded. However, the Rust +- The program here is sound because it is single-threaded. However, the Rust compiler reasons about functions individually so can't assume that. Try removing the `unsafe` and see how the compiler explains that it is undefined behavior to access a mutable static from multiple threads. -- Rust 2024 edition goes further and makes accessing a mutable static by - reference an error by default. We work around this in the example with - `#[allow(static_mut_refs)]`. Don't do this. +- The 2024 Rust edition goes further and makes accessing a mutable static by + reference an error by default. - Using a mutable static is almost always a bad idea, you should use interior mutability instead. - There are some cases where it might be necessary in low-level `no_std` code, diff --git a/src/unsafe-rust/unsafe-functions.md b/src/unsafe-rust/unsafe-functions.md index 1025cee2..4520af14 100644 --- a/src/unsafe-rust/unsafe-functions.md +++ b/src/unsafe-rust/unsafe-functions.md @@ -7,10 +7,10 @@ minutes: 15 A function or method can be marked `unsafe` if it has extra preconditions you must uphold to avoid undefined behaviour. -There are two main categories: +Unsafe functions may come from two places: -- Rust functions declared unsafe with `unsafe fn`. -- Foreign functions in `extern "C"` blocks. +- Rust functions declared unsafe. +- Unsafe foreign functions in `extern "C"` blocks.
diff --git a/src/unsafe-rust/unsafe-functions/extern-c.md b/src/unsafe-rust/unsafe-functions/extern-c.md index 1af7d435..34edd8fa 100644 --- a/src/unsafe-rust/unsafe-functions/extern-c.md +++ b/src/unsafe-rust/unsafe-functions/extern-c.md @@ -1,6 +1,9 @@ # Unsafe External Functions -Functions in a foreign language may also be unsafe: +You can declare foreign functions for access from Rust with `unsafe extern`. +This is unsafe because the compiler has to way to reason about their behavior. +Functions declared in an `extern` block must be marked as `safe` or `unsafe`, +depending on whether they have preconditions for safe use: ```rust,editable use std::ffi::c_char;