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...
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...
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...
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...
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...
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...
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...
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....
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...
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>(...
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...
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...
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...
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...
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);...
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...
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...
Lifetimes A lifetime is a construct the compiler (or more specifically, its borrow checker) uses to ensure all borrows are valid. Specifically, a variable's lifetime begins when it is created and ends when it is destroyed. While lifetimes a...
The ref pattern When doing pattern matching or destructuring via the let binding, the ref keyword can be used to take references to the fields of a struct/tuple. The example below shows a few instances where this can be useful: #[derive(Clo...
Aliasing Data can be immutably borrowed any number of times, but while immutably borrowed, the original data can't be mutably borrowed. On the other hand, only one mutable borrow is allowed at a time. The original data can be borrowed again...