diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 6d5c1b5..7f677ac 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -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", diff --git a/Advanced Features.md b/Advanced Features.md index ad1d426..a88f7d6 100644 --- a/Advanced Features.md +++ b/Advanced Features.md @@ -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 diff --git a/Advanced Traits.md b/Advanced Traits.md new file mode 100644 index 0000000..e562a65 --- /dev/null +++ b/Advanced Traits.md @@ -0,0 +1 @@ +# Advanced Traits \ No newline at end of file diff --git a/Unsafe Rust.md b/Unsafe Rust.md index f65d198..65128f9 100644 --- a/Unsafe Rust.md +++ b/Unsafe Rust.md @@ -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 + = 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/). \ No newline at end of file