%title: Myth %author: Ivan Tham %date: 2022-01-12 %usage: mdp slide.md -> Rust Myth <- =============== --- Inspired by https://www.unwoundstack.com/blog/rust-error-handling.html --- -> Rust Myth <- =============== 1. error handling (library) 2. mutability 3. unsafe --- -> Recap on std error handling <- ================================= - unrecoverable errors like `panic!` ```rust panic!("dead"); ``` - recoverable errors with `Result`, `?` ```rust use std::fs::File; use std::io; use std::io::Read; fn read_username_from_file() -> Result { // equivalent to // let mut f = match f { // Ok(file) => file, // Err(e) => return Err(e), // }; let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) } ``` --- -> But what if we need multiple errors? <- ========================================== ```rust #[derive(Debug)] pub enum SubscribeError { ValidationError(String), DatabaseError(sqlx::Error), StoreTokenError(StoreTokenError), SendEmailError(reqwest::Error), } impl From for SubscribeError { fn from(e: reqwest::Error) -> Self { Self::SendEmailError(e) } } impl From for SubscribeError { fn from(e: sqlx::Error) -> Self { Self::DatabaseError(e) } } ... // boilerplates ``` --- ```rust // and this some more in addition to above impl std::fmt::Debug for SubscribeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { error_chain_fmt(self, f) } } impl std::error::Error for SubscribeError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { // &str does not implement `Error` - we consider it the root cause SubscribeError::ValidationError(_) => None, SubscribeError::DatabaseError(e) => Some(e), SubscribeError::StoreTokenError(e) => Some(e), SubscribeError::SendEmailError(e) => Some(e), } } } impl std::fmt::Display for SubscribeError { ... } ``` https://www.lpalmieri.com/posts/error-handling-rust/ --- -> Enter user error handling library <- ======================================= To reduce boilerplates, see `thiserror` ```toml #! Cargo.toml [dependencies] # [...] thiserror = "1" ``` ```rust #[derive(thiserror::Error)] pub enum SubscribeError { #[error("{0}")] ValidationError(String), #[error("Failed to acquire a Postgres connection from the pool")] PoolError(#[source] sqlx::Error), #[error("Failed to insert new subscriber in the database.")] InsertSubscriberError(#[source] sqlx::Error), #[error("Failed to store the confirmation token for a new subscriber.")] StoreTokenError(#[from] StoreTokenError), #[error("Failed to commit SQL transaction to store a new subscriber.")] TransactionCommitError(#[source] sqlx::Error), #[error("Failed to send a confirmation email.")] SendEmailError(#[from] reqwest::Error), } ``` --- -> And with the same author <- ============================== ```toml #! Cargo.toml [dependencies] # [...] thiserror = "1" ``` ```rust use anyhow::Result; // this fn get_cluster_info() -> Result { let config = std::fs::read_to_string("cluster.json")?; let map: ClusterMap = serde_json::from_str(&config)?; Ok(map) } ``` - besides easy error handling for stuff that implements `Error` trait - can also attach context to errors ```rust use anyhow::{Context, Result}; fn main() -> Result<()> { ... it.detach().context("Failed to detach the important thing")?; let content = std::fs::read(path) .with_context(|| format!("Failed to read instrs from {}", path))?; ... } ``` --- -> Ignore various other libraries for now <- ============================================ Note that there are also `eyre`, `snafu`, ... But let's disregard them for now since @dtolnay build good libraries. The myth: > anyhow is for applications, thiserror is for libraries. --- > anyhow is for applications, thiserror is for libraries. - Easiest to explain and not exactly wrong in most cases. - Applications usually need to pass context to error types but library authors cannot make assumption. - And libraries usually just want to work with standard library error type. - But to be more correct, one should reason about intent. - anyhow is aimed for error reporting - thiserror for simplified error construction (no boilerplates) - Which is why some write error themselves without thiserror, - And also why not both? --- -> Mutability <- ================ Basic variable concept in rust. Things are immutable by default. Not to be confused with `const` in rust. --- -> Mutability recap <- ====================== From rust book ```rust fn main() { let mut x = 5; println!("The value of x is: {}", x); x = 6; println!("The value of x is: {}", x); } ``` > But mutability can be very useful. Variables are immutable only by > default; as you did in Chapter 2, you can make them mutable by adding > mut in front of the variable name. In addition to allowing this value to > change, mut conveys intent to future readers of the code by indicating > that other parts of the code will be changing this variable’s value. --- -> The myth <- ============== Note that rust book did not mentioned anything special about mutability as of the time of writing. > mut means mutable and without means immutable > > &mut means immutable reference and & means mutable reference Main reading taken from https://docs.rs/dtolnay/latest/dtolnay/macro._02__reference_types.html --- -> The conflict <- ================== **Reference** types https://docs.rs/dtolnay/latest/dtolnay/macro._02__reference_types.html ```rust fn embiggen_x(pt: &Point) { pt.x = pt.x * 2; } ``` ``` error[E0594]: cannot assign to `pt.x` which is behind a `&` reference --> src/main.rs | 1 | fn embiggen_x(pt: &Point) { | ------ help: consider changing this to be a mutable reference: `&mut Point` 2 | pt.x = pt.x * 2; | ^^^^^^^^^^^^^^^ `pt` is a `&` reference, so the data it refers to cannot be written ``` Interior mutability means able to mutate without being mutable. --- -> But this <- ============== ```rust impl AtomicU32 { pub fn store(&self, val: u32, order: Ordering); } ``` compiles Note the `&self`, it's not `&mut self`. --- ``` ``:::::'' ``` --- ``` ``::::||||||||:'' ``:::::'' ``` --- ``` :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` . `| |':.. . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` _ ~~~ _ . `| |':.. . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` `...::' _ ~~~ _ . `| |':.. . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` | .:| `...::' _ ~~~ _ . `| |':.. . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` | :| | .:| `...::' _ ~~~ _ . `| |':.. . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` | :| | :| | .:| `...::' _ ~~~ _ . `| |':.. . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` | :| | :| | :| | .:| `...::' _ ~~~ _ . `| |':.. . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` | :| | :| | :| | :| | .:| `...::' _ ~~~ _ . `| |':.. . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``:::::'' ``` --- ``` _____ | :| | :| | :| | :| | .:| `...::' _ ~~~ _ . `| |':.. . | | `:::. | | `::: . | | :::: | | ::|:: . `. .' ,::||: ~~~ ::|||: .. .::|||:' ::... ..::||||:' :::::::::::::||||::' ``::::||||||||:'' ``` --- ``` ____ __,-~~/~ `---. _/_,---( , ) __ / < / ) \___ - ------===;;;'====------------------===;;;===----- - - \/ ~"~"~"~"~"~\~"~)~"/ (_ ( \ ( > \) \_( _ < >_>' ~ `-i' ::>|--" I;|.|.| <|i::|i|`. (` ^'"`-' ") ``` --- > **お前はもう死んでいる。** > --- > **お前はもう死んでいる。** > (You are already dead.) --- > **何?** > (What?) --- -> Another example <- | | `Sync` | `!Sync` | +-------------+--------------------+---------------+ | interior | thread-safe | thread-unsafe | | mutability | `AtomicI32` | `Cell` | +-------------+--------------------+---------------+ | no interior | thread-compatible | thread-unsafe | | mutability | `Vec` | `proc_macro`? | Ignore thread-* for now. ```rust impl ThreadSafeCounter { fn increment(&self) { self.count.fetch_add(1, Ordering::SeqCst); } } ``` https://blog.reverberate.org/2021/12/18/thread-safety-cpp-rust.html --- -> The answer <- ================ - `&T` is a shared reference - `&mut T` is an exclusive reference > An exclusive reference means that no other reference to the same value > could possibly exist at the same time. A shared reference means that > other references to the same value *might* exist, possibly on other threads > (if `T` implements `Sync`) or the caller’s stack frame on the current > thread. Guaranteeing that exclusive references really are exclusive is > one of the key roles of the Rust borrow checker. **Note, this is only for reference types.** Book did not explain probably because to make it easier to learn. --- -> Unsafe <- ============ Myth first, explanation later. > Unsafe provides super power and rust safety won't work anymore. Not sure by who but probably by me, at least when I first thought. Similar comments seen online that `unsafe` should not be used in library. Side story, @fafhrd91 actix-web author left actix-web because of it. https://github.com/fafhrd91/actix-web-postmortem --- -> Unsafe example <- ==================== ```rust /// Dereference the given pointer. /// /// # Safety /// /// `ptr` must be aligned and must not be dangling. unsafe fn deref_unchecked(ptr: *const i32) -> i32 { *ptr } let a = 3; let b = &a as *const _; // SAFETY: `a` has not been dropped and references are always aligned, // so `b` is a valid address. unsafe { assert_eq!(*b, deref_unchecked(b)); }; ``` From docs --- -> Terminology <- ================= From cheats.rs - unsafe code - have special permission, marked with `unsafe` - implies special promises to compiler - undefined behavior (UB) - anything can happen - program might still work but contains undefined behaviors - unsound code - any *safe* rust code that could produce undefined behavior - or say safe code that when called in certain way becomes UB https://cheats.rs/#unsafe-unsound-undefined --- From docs: > Code or interfaces whose memory safety cannot be verified by the > type system. - contracts that compiler can't check (`unsafe fn` / `unsafe trait`) - programmer has checked the contracts have been upheld (`unsafe {}` / `unsafe impl`) --- -> Unsafe notes <- ================== These can still happen and is considered safe: - Deadlock - Have a race condition - Leak memory - Fail to call destructors - Overflow integers - Abort the program - Delete the production database --- -> Unsafe superpowers <- ======================== Although it is called superpowers, it still does not bend the compiler. Extra abilities: - Dereference raw pointers - Call unsafe functions (including C functions, compiler intrinsics, and the raw allocator) - Implement unsafe traits - Mutate statics - Access fields of unions https://doc.rust-lang.org/nomicon/what-unsafe-does.html --- -> Summary <- ============= - `unsafe` is not totally bad but you probably don't need it - still required for FFI or low-level stuff - still useful for escape hatch and performance, but should not misuse - limits surface needed to audit code, given functions are sound - always put `// Safety: `, `/// # Safety` comment for unsafe stuff - should still read nomicon book https://doc.rust-lang.org/nomicon/ --- -> Other Myths <- ================= https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/