# Effective Rust ![rw-book-cover](https://m.media-amazon.com/images/I/81CPbjLviYL._SY160.jpg) ## Metadata - Author: David Drysdale - Full Title: Effective Rust - Category: #programming-best-practices #rust ## Highlights - If a Rust program compiles, it will also work safely. ([Location 34](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=34)) - One core part of this is Rust’s enum type, which is considerably more expressive than the enumeration types in other languages and which allows for algebraic data types. ([Location 136](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=136)) - Because each enum definition creates a distinct type, this can be used to improve readability and maintainability of functions that take bool arguments. ([Location 321](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=321)) - Using the newtype pattern—see Item 6—to wrap a bool also achieves type safety and maintainability; it’s generally best to use the newtype pattern if the semantics will always be Boolean, and to use an enum if there’s a chance that a new alternative—e.g., Sides::BothAlternateOrientation—could arise in the future. ([Location 353](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=353)) - The compiler forces the programmer to consider all of the possibilities that are represented by the enum,3 even if the result is just to add a default arm _ => {}. (Note that modern C++ compilers can and do warn about missing switch arms for enums as well.) ([Location 417](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=417)) - The true power of Rust’s enum feature comes from the fact that each variant can have data that comes along with it, making it an aggregate type that acts as an algebraic data type (ADT). ([Location 421](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=421)) - This small example illustrates a key piece of advice: make invalid states inexpressible in your types. ([Location 472](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=472)) - Always use Option for values that can be absent; ([Location 482](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=482)) - There is one subtle point to consider, though. If you’re dealing with a collection of things, you need to decide whether having zero things in the collection is the same as not having a collection. ([Location 485](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=485)) - always encode the result of an operation that might fail as a Result<T, E>. The T type holds the successful result (in the Ok variant), and the E type holds error details (in the Err variant) on failure. ([Location 505](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=505)) - Traits Describe collections of related functionality that all apply to the same underlying item. Traits have rough equivalents in many other languages, including abstract classes in C++ and interfaces in Go and Java. ([Location 526](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=526)) - As with every other programming language, Rust uses functions to organize code into named chunks for reuse, with inputs to the code expressed as parameters. ([Location 535](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=535)) - If a function is intimately involved with a particular data structure, it is expressed as a method. ([Location 557](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=557)) - in Rust, methods can be added to enum types as well as to struct types, in keeping with the pervasive nature of Rust’s enum ([Location 562](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=562)) - A &self parameter indicates that the contents of the data structure may be read from but will not be modified. ([Location 593](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=593)) - A &mut self parameter indicates that the method might modify the contents of the data structure. ([Location 594](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=594)) - A self parameter indicates that the method consumes the data structure. ([Location 595](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=595)) - but what if the code needs to vary at runtime? The simplest behavioral abstraction that allows this is the function pointer: a pointer to (just) some code, with a type that reflects the signature of the function: ([Location 600](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=600)) - explicit coercion to a fn type is needed, because just using the name of a function doesn’t give you something of fn type: ([Location 627](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=627)) - To put it another way, the type of sum encodes both the function’s signature and its location for optimization reasons; this type can be automatically coerced (Item 5) to a fn type. ([Location 650](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=650)) - The error message points to the right tool for the job: a closure. A closure is a chunk of code that looks like the body of a function definition (a lambda expression), ([Location 717](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=717)) - To (roughly) understand how the capture works, imagine that the compiler creates a one-off, internal type that holds all of the parts of the environment that get mentioned in the lambda expression. ([Location 735](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=735)) - FnOnce Describes a closure that can be called only once. If some part of the environment is moved into the closure’s context, and the closure’s body subsequently moves it out of the closure’s context, then those moves can happen only once—there’s no other copy of the source item to move from—and so the closure can be invoked only once. ([Location 802](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=802)) - FnMut Describes a closure that can be called repeatedly and that can make changes to its environment because it mutably borrows from the environment. ([Location 806](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=806)) - Fn Describes a closure that can be called repeatedly and that only borrows values from the environment immutably. ([Location 809](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=809)) - The latter two traits in this list each have a trait bound of the preceding trait, which makes sense when you consider the things that use the closures: If something expects to call a closure only once (indicated by receiving a FnOnce), it’s OK to pass it a closure that’s capable of being repeatedly called (FnMut). If something expects to repeatedly call a closure that might mutate its environment (indicated by receiving a FnMut), it’s OK to pass it a closure that doesn’t need to mutate its environment (Fn). ([Location 820](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=820)) - As a result, when writing code that accepts closures, use the most general Fn* trait that works, to allow the greatest flexibility for callers—for example, accept FnOnce for closures that are used only once. The same reasoning also leads to advice to prefer Fn* trait bounds over bare function pointers (fn). ([Location 829](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=829)) - The Fn* traits are more flexible than bare function pointers, but they can still describe only the behavior of a single function, and even then only in terms of the function’s signature. ([Location 833](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=833)) - A trait defines a set of related functions that some underlying item makes publicly available; moreover, the functions are typically (but don’t have to be) methods, taking some variant of self as their first argument. ([Location 838](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=838)) - Each function in a trait also has a name, providing a label that allows the compiler to disambiguate functions with the same signature, and more importantly, that allows programmers to deduce the intent of the function. ([Location 841](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=841)) - Implementations of the trait must provide all the functions (but note that the trait definition can include a default implementation; Item 13) and can also have associated data that those implementations make use of. This means that code and data gets encapsulated together in a common abstraction, in a somewhat object-oriented (OO) manner. ([Location 844](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=844)) - This leads to the same kind of advice that turns up for other OO-influenced languages:5 prefer accepting trait types over concrete types if future flexibility is anticipated. ([Location 851](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=851)) - A marker trait has no functions, but an implementation still has to declare that it is implementing the trait—which acts as a promise from the implementer: “I solemnly swear that my implementation sorts stably.” ([Location 869](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=869)) - Use marker traits to distinguish behaviors that cannot be expressed in the trait function signatures. ([Location 871](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=871)) - Once behavior has been encapsulated into Rust’s type system as a trait, it can be used in two ways: As a trait bound, which constrains what types are acceptable for a generic data type or function at compile time As a trait object, which constrains what types can be stored or passed to a function at runtime ([Location 872](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=872)) - A trait bound indicates that generic code that is parameterized by some type T can be used only when that type T implements some specific trait. ([Location 879](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=879)) - This check happens at compile time, when the generic is monomorphized—converted from the generic code that deals with an arbitrary type T into specific code that deals with one particular SomeType (what C++ would call template instantiation). ([Location 885](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=885)) - The need for explicit trait bounds also means that a large fraction of generics use trait bounds. ([Location 897](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=897)) - Without a trait bound, the Thing can perform only operations that apply to any type T—basically just moving or dropping the value. This in turn allows for generic containers, collections, and smart pointers, but not much else. Anything that uses the type T is going to need a trait bound: ([Location 901](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=901)) - A trait object is the other way to make use of the encapsulation defined by a trait, but here, different possible implementations of the trait are chosen at runtime rather than compile time. ([Location 924](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=924)) - This dynamic aspect of trait objects also means that they always have to be handled indirectly, via a reference (e.g., &dyn Trait) or a pointer (e.g., Box<dyn Trait>) of some kind. ([Location 929](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=929)) - These two restrictions—no use of Self and no generic functions—are combined in the concept of object safety. Only object-safe traits can be used as trait objects. ([Location 942](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=942)) - However, in many situations, the right decision for error handling is to defer the decision to somebody else. This is particularly true when writing a library, where the code may be used in all sorts of different environments that can’t be foreseen by the library author. To make that somebody else’s job easier, prefer Result to Option for expressing errors, even though this may involve conversions between different error types (Item 4). ([Location 1027](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1027)) - The key ingredient for reducing boilerplate is Rust’s question mark operator, ?. This piece of syntactic sugar takes care of matching the Err arm, transforming the error type if necessary, and building the return Err(...) expression, all in a single character: ([Location 1066](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1066)) - These two factors taken together mean that you should prefer Option and Result transforms over explicit match expressions. ([Location 1086](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1086)) - These kinds of transformations generalize more widely. The question mark operator is a big hammer; use transformation methods on Option and Result types to maneuver them into a position where they can be a nail. ([Location 1131](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1131)) - The right tool for this is the as_ref() method on Option.8 This method converts a reference-to-an-Option into an Option-of-a-reference: ([Location 1183](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1183)) - Get used to the transformations of Option and Result, and prefer Result to Option. Use .as_ref() as needed when transformations involve references. ([Location 1195](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1195)) - The E type parameter for a Result doesn’t have to be a type that implements Error, but it’s a common convention that allows wrappers to express appropriate trait bounds—so prefer to implement Error for your error types. ([Location 1220](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1220)) - The first thing to notice is that the only hard requirement for Error types is the trait bounds: any type that implements Error also has to implement the following traits: The Display trait, meaning that it can be format!ed with {} The Debug trait, meaning that it can be format!ed with {:?} ([Location 1222](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1222)) - Defining a tuple struct that wraps the String type (the “newtype pattern,” Item 6) allows the Error trait to be implemented, provided that Debug and Display are implemented too: ([Location 1294](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1294)) - When it encounters the question mark operator (?), the compiler will automatically apply any relevant From trait implementations that are needed to reach the destination error return type. ([Location 1340](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1340)) - ? makes the compiler look for and use a From implementation that can take it from String to MyError. ([Location 1361](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1361)) - It’s also a good idea to implement the From trait for all of the suberror types ([Location 1477](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1477)) - Better still, implementing From allows for even more concision, because the question mark operator will automatically perform any necessary From conversions, removing the need for .map_err(): ([Location 1501](https://readwise.io/to_kindle?action=open&asin=B0D1DTFPSZ&location=1501))