Explicit annotation The borrow checker uses explicit lifetime annotations to determine how long references should be valid. In cases where lifetimes are not elided[^1], Rust requires explicit annotations to determine what the lifetime of a...
Functions Ignoring elision, function signatures with lifetimes have a few constraints: • any reference must have an annotated lifetime. • any reference being returned must have the same lifetime as an input or be static. Additionally, note...
Methods Methods are annotated similarly to functions: struct Owner(i32); impl Owner { // Annotate lifetimes as in a standalone function. fn add_one<'a>(&'a mut self) { self.0 += 1; } fn print<'a>(&'a self) { println!("`print`: {}", self.0);...
Structs Annotation of lifetimes in structures are also similar to functions: // A type `Borrowed` which houses a reference to an // `i32`. The reference to `i32` must outlive `Borrowed`. #[derive(Debug)] struct Borrowed<'a>(&'a i32); // Sim...
Traits Annotation of lifetimes in trait methods basically are similar to functions. Note that impl may have annotation of lifetimes too. // A struct with annotation of lifetimes. #[derive(Debug)] struct Borrowed<'a> { x: &'a i32, } // Annot...
Bounds Just like generic types can be bounded, lifetimes (themselves generic) use bounds as well. The : character has a slightly different meaning here, but + is the same. Note how the following read: 1. T: 'a: All references in T must outl...
Coercion A longer lifetime can be coerced into a shorter one so that it works inside a scope it normally wouldn't work in. This comes in the form of inferred coercion by the Rust compiler, and also in the form of declaring a lifetime differ...
Static Rust has a few reserved lifetime names. One of those is 'static. You might encounter it in two situations: // A reference with 'static lifetime: let s: &'static str = "hello world"; // 'static as part of a trait bound: fn generic<T>(...
Elision Some lifetime patterns are overwhelmingly common and so the borrow checker will allow you to omit them to save typing and to improve readability. This is known as elision. Elision exists in Rust solely because these patterns are com...
Traits A trait is a collection of methods defined for an unknown type: Self. They can access other methods declared in the same trait. Traits can be implemented for any data type. In the example below, we define Animal, a group of methods....
Derive The compiler is capable of providing basic implementations for some traits via the #[derive] attribute. These traits can still be manually implemented if a more complex behavior is required. The following is a list of derivable trait...
Returning Traits with dyn The Rust compiler needs to know how much space every function's return type requires. This means all your functions have to return a concrete type. Unlike other languages, if you have a trait like Animal, you can't...
Operator Overloading In Rust, many of the operators can be overloaded via traits. That is, some operators can be used to accomplish different tasks based on their input arguments. This is possible because operators are syntactic sugar for m...
Drop The Drop trait only has one method: drop, which is called automatically when an object goes out of scope. The main use of the Drop trait is to free the resources that the implementor instance owns. Box, Vec, String, File, and Process a...
Iterators The Iterator trait is used to implement iterators over collections such as arrays. The trait requires only a method to be defined for the next element, which may be manually defined in an impl block or automatically defined (as in...
impl Trait impl Trait can be used in two locations: 1. as an argument type 2. as a return type As an argument type If your function is generic over a trait but you don't mind the specific type, you can simplify the function declaration usin...
Clone and Copy When dealing with resources, the default behavior is to transfer them during assignments or function calls. However, sometimes we need to make a copy of the resource as well. The Clone trait helps us do exactly this. Most com...
Supertraits Rust doesn't have "inheritance", but you can define a trait as being a superset of another trait. For example: trait Person { fn name(&self) -> String; } // Person is a supertrait of Student. // Implementing Student requires you...
Disambiguating overlapping traits A type can implement many different traits. What if two traits both require the same name for a function? For example, many traits might have a method named get(). They might even have different return type...
macro_rules! Rust provides a powerful macro system that allows metaprogramming. As you've seen in previous chapters, macros look like functions, except that their name ends with a bang !, but instead of generating a function call, macros ar...