You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-07-17 11:27:57 +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
|
```rust,editable
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut s = String::from("careful!");
|
let mut x = 10;
|
||||||
|
|
||||||
let r1 = &raw mut s;
|
let p1: *mut i32 = &raw mut x;
|
||||||
let r2 = r1 as *const String;
|
let p2 = p1 as *const i32;
|
||||||
|
|
||||||
// SAFETY: r1 and r2 were obtained from references and so are guaranteed to
|
// SAFETY: p1 and p2 were created by taking raw pointers to a local, so they
|
||||||
// be non-null and properly aligned, the objects underlying the references
|
// are guaranteed to be non-null, aligned, and point into a single (stack-)
|
||||||
// from which they were obtained are live throughout the whole unsafe
|
// allocated object.
|
||||||
// block, and they are not accessed either through the references or
|
//
|
||||||
// concurrently through any other pointers.
|
// 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 {
|
unsafe {
|
||||||
println!("r1 is: {}", *r1);
|
dbg!(*p1);
|
||||||
*r1 = String::from("uhoh");
|
*p1 = 6;
|
||||||
println!("r2 is: {}", *r2);
|
// 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 };
|
let r: &i32 = unsafe { &*p1 };
|
||||||
drop(s);
|
dbg!(r);
|
||||||
println!("r3 is: {}", *r3);
|
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.
|
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 "UNSOUND" section gives an example of a common kind of UB bug: naïvely
|
||||||
the `'static` lifetime, so `r3` has type `&'static String`, and thus outlives
|
taking a reference to the dereference of a raw pointer sidesteps the compiler's
|
||||||
`s`. Creating a reference from a pointer requires _great care_.
|
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>
|
</details>
|
||||||
|
@ -14,15 +14,18 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
However, since data races can occur, it is unsafe to read and write mutable
|
However, mutable static variables are unsafe to read and write because multiple
|
||||||
static variables:
|
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
|
```rust,editable
|
||||||
static mut COUNTER: u32 = 0;
|
static mut COUNTER: u32 = 0;
|
||||||
|
|
||||||
fn add_to_counter(inc: u32) {
|
fn add_to_counter(inc: u32) {
|
||||||
// SAFETY: There are no other threads which could be accessing `COUNTER`.
|
// SAFETY: There are no other threads which could be accessing `COUNTER`.
|
||||||
#[allow(static_mut_refs)]
|
|
||||||
unsafe {
|
unsafe {
|
||||||
COUNTER += inc;
|
COUNTER += inc;
|
||||||
}
|
}
|
||||||
@ -32,7 +35,6 @@ fn main() {
|
|||||||
add_to_counter(42);
|
add_to_counter(42);
|
||||||
|
|
||||||
// SAFETY: There are no other threads which could be accessing `COUNTER`.
|
// SAFETY: There are no other threads which could be accessing `COUNTER`.
|
||||||
#[allow(static_mut_refs)]
|
|
||||||
unsafe {
|
unsafe {
|
||||||
dbg!(COUNTER);
|
dbg!(COUNTER);
|
||||||
}
|
}
|
||||||
@ -41,13 +43,12 @@ fn main() {
|
|||||||
|
|
||||||
<details>
|
<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
|
compiler reasons about functions individually so can't assume that. Try
|
||||||
removing the `unsafe` and see how the compiler explains that it is undefined
|
removing the `unsafe` and see how the compiler explains that it is undefined
|
||||||
behavior to access a mutable static from multiple threads.
|
behavior to access a mutable static from multiple threads.
|
||||||
- Rust 2024 edition goes further and makes accessing a mutable static by
|
- The 2024 Rust edition goes further and makes accessing a mutable static by
|
||||||
reference an error by default. We work around this in the example with
|
reference an error by default.
|
||||||
`#[allow(static_mut_refs)]`. Don't do this.
|
|
||||||
- Using a mutable static is almost always a bad idea, you should use interior
|
- Using a mutable static is almost always a bad idea, you should use interior
|
||||||
mutability instead.
|
mutability instead.
|
||||||
- There are some cases where it might be necessary in low-level `no_std` code,
|
- 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
|
A function or method can be marked `unsafe` if it has extra preconditions you
|
||||||
must uphold to avoid undefined behaviour.
|
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`.
|
- Rust functions declared unsafe.
|
||||||
- Foreign functions in `extern "C"` blocks.
|
- Unsafe foreign functions in `extern "C"` blocks.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
# Unsafe External Functions
|
# 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
|
```rust,editable
|
||||||
use std::ffi::c_char;
|
use std::ffi::c_char;
|
||||||
|
Reference in New Issue
Block a user