finished ch20.1
All checks were successful
Test Gitea Actions / first (push) Successful in 19s
Test Gitea Actions / check-code (push) Successful in 15s
Test Gitea Actions / test (push) Successful in 16s
Test Gitea Actions / documentation-check (push) Successful in 15s

This commit is contained in:
darkicewolf50 2025-04-15 12:33:04 -06:00
parent 6aa4deab15
commit 5da2a5c2b5
4 changed files with 152 additions and 4 deletions

View File

@ -63,6 +63,20 @@
"title": "Advanced Features"
}
},
{
"id": "8efdb57e394f2650",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Advanced Traits.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Advanced Traits"
}
},
{
"id": "8cdde5c4be386d20",
"type": "leaf",
@ -130,7 +144,7 @@
}
}
],
"currentTab": 4
"currentTab": 3
}
],
"direction": "vertical"
@ -273,8 +287,9 @@
"command-palette:Open command palette": false
}
},
"active": "8cdde5c4be386d20",
"active": "de2ac5df5b921166",
"lastOpenFiles": [
"Advanced Traits.md",
"Advanced Features.md",
"Unsafe Rust.md",
"Pattern Matching.md",
@ -300,7 +315,6 @@
"Passing Data Between Threads.md",
"Leaky Reference Cycles.md",
"Test Controls.md",
"Ref Cell Mutability.md",
"minigrep/src/lib.rs",
"does_not_compile.svg",
"Untitled.canvas",

View File

@ -9,7 +9,7 @@ The features covered here are useful in very specific situations.
In this chapter we will cover
- [Unsafe Rust](./Unsafe%20Rust.md): How to opt out of some of Rust's guarantees and take responsibility for manually upholding those guarantees
- [Advanced traits](): associated types, default type parameters, fully qualified syntax, supertraits, and the new type pattern in relation to traits
- [Advanced traits](./Advanced%20Traits.md): associated types, default type parameters, fully qualified syntax, supertraits, and the new type pattern in relation to traits
- [Advanced types](): more about the newtype pattern, type aliases, the never type, and dynamically sized types
- [Advanced functions and closures](): function pointers and returning closures
- [Macros](): ways to define code that defines more code at compile time

1
Advanced Traits.md Normal file
View File

@ -0,0 +1 @@
# Advanced Traits

View File

@ -450,3 +450,136 @@ fn main() {
}
}
```
Just like with regular variables, we specify mutability using the `mut` keyword.
Any code that reads or writes from `COUNTER` must be within an `unsafe` block.
The code above compiles and prints `COUNTER: 3` as expected because it is single threaded.
Having multiple thread access `COUNTER` would likely result in data races, so it is undefined behavior.
Due to this we need to mark the entire function as `unsafe` and document the safety limitation, so anyone calling this function knows what they are and are not allowed to do safely.
Whenever we write an unsafe function, it is idiomatic to write a comment starting with `SAFETY` and explaining what the caller needs to do to call the function safely.
Also whenever we perform an unsafe operation it is idiomatic to write a comment starting with `SAFETY` to explain how the safety rules are upheld.
As well, the compiler will not allow you to create references to a mutable static variable.
You can only access it via a raw pointer, created with one of the raw borrow operators.
This includes in cases where the reference is created invisibly as when it is used in the `println!` in this code listing.
The requirement that references to static mutable variables can only be created via raw pointers helps make the safety requirements for using them more obvious.
With mutable data that is globally accessible, it is difficult to ensure that there are no data races, which is why Rust considers mutable static variables to be unsafe.
Where it is possible it is preferred to use concurrency techniques and thread-safe smart pointers, so the compiler checks that data accessed from different threads is done safely.
## Implementing an Unsafe Trait
Additionally `unsafe` to implement an unsafe trait.
A trait is unsafe when at least one of its methods has some invariant that the compiler can't verify.
We declare that a trait is `unsafe` by adding the `unsafe` keyword before `trait` and marking the implementation of the trait as `unsafe` too.
Here is an example of this
```rust
unsafe trait Foo {
// methods go here
}
unsafe impl Foo for i32 {
// method implementations go here
}
fn main() {}
```
By using `unsafe impl`, we are promising that we will uphold the invariants that the compiler can't verify.
As an example, recall the `Sync` and `Send` marker traits we discussed in ["Extensible Concurrency with the `Sync` and `Send` Traits"]() section.
The compiler implements these traits automatically if our types are composed entirely of `Send` and `Sync` types.
If we implement a type that contains a type that is not `Send` or `Sync`, such as raw pointers, and we want to mark that type as `Send` or `Sync`, we must use `unsafe`.
Rust can't verify that our type upholds the guarantees that it can be safely sent across threads or accessed form multiple threads.
We need to do these checks manually and indicate as such with `unsafe`.
## Accessing Fields of a Union
The last superpower that works on with `unsafe` is accessing fields of a *union*.
A `union` is similar to a `struct`, but only one declared field is used in a particular instance at one time.
Unions are primarily used to interface with unions in C code.
Accessing union fields is unsafe because Rust can't guarantee the type of data currently being stored in the union instance.
You can learn more about unions in [the Rust Reference](https://doc.rust-lang.org/reference/items/unions.html).
## Using Miri to check unsafe code
When writing unsafe code, you may want to check that you have written actually is safe and correct.
Using [Miri](https://github.com/rust-lang/miri), an official Rust tool for detecting undefined behavior is one of the best ways to do it.
While the borrow checker is a *static* tool which works at compile time, Miri is a *dynamic* tool which works at runtime.
It checks your code by running your program or its test suite and detecting when you violate the rules it understands about how Rust should work.
Miri requires a nightly build of Rust (discussed in [Appendix G: How Rust is Made and "Nightly Rust"](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html)).
You can install both a nightly version of Rust and the Miri tool by typing `rustup +nightly component add miri`.
This does not change what version of Rust your project uses.
This only adds the tool to your system so you can use it when you want to.
You can run Miri on a project by typing cargo `+nightly miri run` or `cargo +nightly miri test`.
Here is an example of how useful when we run it on a previous example
```
$ cargo +nightly miri run
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `/Users/chris/.rustup/toolchains/nightly-aarch64-apple-darwin/bin/cargo-miri runner target/miri/aarch64-apple-darwin/debug/unsafe-example`
warning: creating a shared reference to mutable static is discouraged
--> src/main.rs:14:33
|
14 | println!("COUNTER: {}", COUNTER);
| ^^^^^^^ shared reference to mutable static
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/static-mut-references.html>
= note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
= note: `#[warn(static_mut_refs)]` on by default
COUNTER: 3
```
It helpfully and correctly notices that we have shared references to mutable data and warns about it.
Here it does not tell us how to fix the problem, but it mean that we know there is a possible issue and can think about how to make sure it is safe.
In other cases, it can actually tell us that some code is *sure* to be wrong and make recommendations about how to fix it.
Miri will not catch *everything* you may get wrong when writing unsafe code.
Since it is a dynamic check, it only catches problems with code that actually gets run.
This means you will need to use it with good testing techniques to increase your confidence about the unsafe code you have written.
Additionally it does not cover every possible way your code can be unsound.
If Miri *does* catch a problem, you know that there is a bug, but just because Miri *doesn't* catch a bug doesn't mean there isn't a problem.
Miri can catch a lot despite this.
## When to Use Unsafe Code
Using `unsfe` to take one of the five superpowers, isn't wrong or frowned upon.
But it is trickier to get `unsafe` code correct because the compiler can't help uphold memory safety.
When you have a reason to use `unsafe` code, you can do so and having the explicit `unsafe` annotation makes it easier to tack down the source of problems when they occur.
When you write unsafe code, you can use Miri to help to be more confident that the code you wrote upholds Rust's rules.
For a deeper exploration of how to work effectively with unsafe Rust, read Rust's official guide on the subject the [Rustonomicon](https://doc.rust-lang.org/nomicon/).