You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-07-16 19:14:20 +02:00
A few fixes for the unsafe chapter (#2805)
See individual commits in https://github.com/google/comprehensive-rust/pull/2805. Fixes #2734.
This commit is contained in:
committed by
GitHub
parent
7c23d3bcf8
commit
8419b3095e
@ -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_.
|
||||
|
||||
</details>
|
||||
|
@ -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() {
|
||||
|
||||
<details>
|
||||
|
||||
- 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,
|
||||
|
@ -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.
|
||||
|
||||
<details>
|
||||
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user