1. Functional language features: iterators and closures
    1. closures
      1. Closure definition
      2. Type inference for closures
      3. Closures with generic parameters
        1. How to make a struct hold a closure
        2. Limitations of the Cacher Implementation
      4. Closures can capture their environment.
        1. How closures capture values from their environment
        2. The move keyword
        3. Best Practices
    2. iterator
      1. Iterator trait
        1. Several iterator methods
      2. Methods that consume the iterator
      3. Methods that Produce Other Iterators
      4. Using Closures to Capture the Environment
      5. Creating Custom Iterators
      6. Improving the I/O Project
        1. Use iterators and remove clone
        2. Use the iterator returned by env::args directly
      7. Performance comparison: loops/iterators
  2. Cargo and crates.io
    1. Customizing builds with release profiles
      1. Custom profiles
    2. Documentation comments
      1. Generate documentation
      2. Generate documentation and browse it
      3. Common sections
      4. Documentation comments as tests
      5. Add documentation comments to the item that contains the comments
    3. Use pub use to export a convenient public API
    4. Publishing a Crate
    5. Publish a new version of an existing crate.
    6. Use cargo yank to yank a version from Crates.io.
    7. Cargo Workspaces
      1. Creating a workspace
    8. Installing binary crates from CRATES.IO
      1. cargo install
    9. Extending cargo with custom commands
  3. smart pointers
    1. Other differences between references and smart pointers
    2. Using BoxPointing to data on the heap.
    3. Using Box to enable recursive types
      1. Cons List
      2. Computing the size of a non-recursive type
      3. Using BoxGiving recursive types a known size
    4. Dref Trait
    5. Dereference operator
    6. Treating Boxas a reference
    7. Define your own smart pointer
    8. Treat a type like a reference by implementing the Deref trait
    9. Implicit deref coercion for functions and methods (Deref Coercion)
    10. Deref and Mutability
    11. Drop Trait
    12. Use std::mem::drop to drop a value early
    13. RcReference Counting Smart Pointer
    14. Cloning Rcincreases the reference count
    15. RefCelland interior mutability
      1. Interior mutability
      2. RefCellUnlike Boxthe difference
      3. Comparison of borrowing rules checked at different stages
      4. Choosing Box,Rc,RefCellthe basis for
      5. Interior mutability: mutably borrowing an immutable value.
      6. Using RefCellRecording borrow information at runtime
      7. Combining Rc and RefCell to have multiple owners of mutable data
    16. Other types that can implement interior mutability
    17. Reference cycles lead to memory leaks
      1. Avoid reference cycles: turn Rc into Weak
      2. Strong VS Weak
        1. Creating a tree data structure: a Node with children
        2. Adding a reference from child to parent
  4. Fearless Concurrency
    1. Using Threads to Run Code Simultaneously
    2. Creating a new thread with spawn
    3. Using the join Handle to wait for all threads to complete.
    4. Using move closures
    5. Using Message Passing to Transfer Data Between Threads
      1. Channel
      2. Creating a Channel
      3. Channels and Ownership Transference
      4. Sending Multiple Values and Seeing the Receiver Waiting
      5. Create multiple producers by cloning the sender
    6. Shared-state concurrency
      1. A mutex allows only one thread to access data at a time.
      2. The Mutex API
      3. Sharing a Mutex Between Threads
      4. Multiple Threads and Multiple Ownership
      5. Atomically Reference Counted Arc
      6. The Similarity Between RefCell/Rc and Mutex/Arc
    7. Scalable Concurrency with Sync and Send Traits
      1. Allow transferring ownership between threads via Send
      2. Sync allows access from multiple threads.
      3. Manually implementing Send and Sync is unsafe
  5. Rust’s object-oriented features
    1. Characteristics of object-oriented languages
    2. Trait objects for values of different types
    3. Defining a trait for common behavior
    4. Trait objects perform dynamic dispatch.
    5. Trait objects require type safety.
    6. Implementation of an object-oriented design pattern
  6. Patterns and Pattern Matching
    1. Match arms
    2. Conditional if let expressions
    3. while let conditional loops
    4. Pattern matching in for loops
    5. Pattern matching in let statements
    6. Function parameters
    7. Refutability: whether a pattern can fail to match
    8. Matching literals
    9. Matching named variables
    10. Matching a mutable reference
    11. Multiple patterns
    12. Use …= to match a range of values.
    13. Destructuring to decompose values.
      1. Destructuring assignment
      2. Destructuring tuples
      3. Destructuring structs
      4. Destructuring enums
      5. Destructuring nested structs and enums
      6. Destructuring structs and tuples
    14. Ignoring values in patterns
      1. _ ignores the entire value
      2. Use nested _ to ignore part of a value.
      3. Use names starting with _ to ignore unused variables.
      4. … (ignore the rest of the value)
    15. Use match guards to provide additional conditions.
    16. @ bindings
  7. unsafe Rust
    1. unsafe superpowers
      1. Dereference raw pointers.
      2. Calling an unsafe function or method
      3. Creating safe abstractions around unsafe code.
      4. Using extern functions to call external code
    2. Calling Rust functions from other languages
    3. Accessing or modifying a mutable static variable
    4. Implementing unsafe traits
    5. Accessing fields of a union
    6. When to Use Unsafe Code
  8. Advanced Traits
    1. Using Associated Types to Specify Placeholder Types in Trait Definitions
    2. Differences Between Associated Types and Generics
    3. Default Generic Parameters and Operator Overloading
    4. Fully Qualified Syntax
    5. Use supertraits to require a trait to include the functionality of other traits.
    6. Using the newtype pattern to implement external traits on external types.
  9. Advanced Types
    1. Using the newtype pattern for type safety and abstraction.
    2. Using type aliases to create type synonyms.
    3. The never type
    4. Dynamically Sized Types and the Sized Trait
    5. Rust’s general way of using dynamically sized types.
    6. Another dynamically sized type: trait.
    7. Sized trait
    8. ?Sized trait constraint
  10. Advanced Functions and Closures
    1. Function pointer
    2. Differences between function pointers and closures
    3. Returning Closures
  11. Macro
  12. Asynchronous Programming
  13. Formatting output
    1. Positional argument
    2. Named argument
    3. String alignment
    4. Precision
    5. Binary, octal, hexadecimal
    6. Capture values from the environment
    7. Exponent, pointer address, escape
  14. Other
    1. Error handling:
      1. unreachable!()
    2. misconception corollaries
      1. if T: 'static then T must be valid for the entire program
Cover image for rust language advanced

rust language advanced

Words 30k
Views
Visitors

Timeline

Timeline

2025-10-22

init

This article introduces advanced functional features of the Rust language, focusing on closures and iterators. Closures are anonymous functions that can be stored in variables or passed as arguments, and can capture values from their environment. The article explains closure type inference, comparison with functions, and how to build caching structs (such as Cacher) using generics and the Fn trait to achieve lazy evaluation. It also details the three ways closures capture the environment: FnOnce (takes ownership), FnMut (mutable borrow), and Fn (immutable borrow), and introduces the move keyword to force ownership transfer. In the iterator section, it points out the lazy nature of iterators, meaning no effect occurs until a consuming method is called.

Functional language features: iterators and closures

closures

Rust’s closures(_closures_are anonymous functions that can be stored in a variable or passed as arguments to other functions

  • Anonymous functions
  • Stored in variables, passed as arguments
  • You can create a closure in one place and then call it in another context to perform the computation.
  • can capture values from the scope in which they are defined.

File name: src/main.rs

A function that stands in for a hypothetical calculation and takes about two seconds to execute.

12345678
use std::thread;use std::time::Duration;fn simulated_expensive_calculation(intensity: u32) -> u32 {    println!("calculating slowly...");    thread::sleep(Duration::from_secs(2));    intensity}

File name: src/main.rs

The program’s business logic, which takes the input and calls the simulated_expensive_calculation function to print out the workout plan.

123456789101112131415161718192021
fn generate_workout(intensity: u32, random_number: u32) {    if intensity < 25 {        println!(            "Today, do {} pushups!",            simulated_expensive_calculation(intensity)        );        println!(            "Next, do {} situps!",            simulated_expensive_calculation(intensity)        );    } else {        if random_number == 3 {            println!("Take a break today! Remember to stay hydrated!");        } else {            println!(                "Today, run for {} minutes!",                simulated_expensive_calculation(intensity)            );        }    }}

File name: src/main.rs

The main function contains simulated user input and simulated random number input for the generate_workout function.

123456
fn main() {    let simulated_user_specified_value = 10;    let simulated_random_number = 7;    generate_workout(simulated_user_specified_value, simulated_random_number);}

Closure definition

12345
let expensive_closure = |num| {       println!("calculating slowly...");       thread::sleep(Duration::from_secs(2));       num   };
  • The closure definition is the part after the = in the expensive_closure assignment. The closure definition begins with a pair of vertical bars (|), and the closure’s parameters are specified between the bars;
  • ifIf there is more than one parameter, they can be separated by commas, for example |param1, param2|
  • After the parameters are the curly braces that hold the closure body — **If the closure body has only one line, the curly braces can be omitted.**After the curly braces, the end of the closure requires a semicolon for the let statement. Because the last line of the closure body has no semicolon (just like a function body), the return value of the last line of the closure body (num) is used as the return value when the closure is called.

Note:

This let statement means expensive_closure contains an anonymous function’s Definition, not the result of calling the anonymous function Return value

Refactoring code

File name: src/main.rs

123456789101112131415161718192021
fn generate_workout(intensity: u32, random_number: u32) {    let expensive_closure = |num| {        println!("calculating slowly...");        thread::sleep(Duration::from_secs(2));        num    };    if intensity < 25 {        println!("Today, do {} pushups!", expensive_closure(intensity));        println!("Next, do {} situps!", expensive_closure(intensity));    } else {        if random_number == 3 {            println!("Take a break today! Remember to stay hydrated!");        } else {            println!(                "Today, run for {} minutes!",                expensive_closure(intensity)            );        }    }}

Type inference for closures

  • Closures do not require type annotations for parameters and return values.
  • Closures are usually short and work in a narrow context, so the compiler can usually infer the types.
  • You can manually add types.
12345
let expensive_closure = |num: u32| -> u32 {        println!("calculating slowly...");        thread::sleep(Duration::from_secs(2));        num    };

Comparison with functions

1234
fn  add_one_v1   (x: u32) -> u32 { x + 1 }//functionlet add_one_v2 = |x: u32| -> u32 { x + 1 };//closureslet add_one_v3 = |x|             { x + 1 };//closureslet add_one_v4 = |x|               x + 1  ;//closures

src/main.rs

123
let example_closure = |x| x;   let s = example_closure(String::from("hello"));   let n = example_closure(5);

For this closure, when the second line of code is executed, the compiler can determine that the closure’s type is String, and when the third line is executed, it will report an error.

123456789101112131415161718192021222324252627
   Compiling rust_programming v0.1.0 (/home/zhaohang/repository/rust_programming)error[E0308]: mismatched types --> src/main.rs:4:29  |4 |     let n = example_closure(5);  |             --------------- ^ expected `String`, found integer  |             |  |             arguments to this function are incorrect  |note: expected because the closure was earlier called with an argument of type `String` --> src/main.rs:3:29  |3 |     let s = example_closure(String::from("hello"));  |             --------------- ^^^^^^^^^^^^^^^^^^^^^ expected because this argument is of type `String`  |             |  |             in this closure callnote: closure parameter defined here --> src/main.rs:2:28  |2 |     let example_closure = |x| x;  |                            ^help: try using a conversion method  |4 |     let n = example_closure(5.to_string());  |                              ++++++++++++For more information about this error, try `rustc --explain E0308`.

Closures with generic parameters

In the above code, the slow computation closure is still called more times than necessary. One way to solve this problem is to save the result into a variable for reuse at every place in the code that needs multiple results from the slow computation closure, so that you can use the variable instead of calling the closure again. However, this would result in many places where the result variable is saved repeatedly.

Fortunately, there is another available solution. You cancreate a struct that stores the closure and the result of calling the closure.. This struct will only execute the closure when the result is needed, and will cache the result value, so the rest of the code does not have to be responsible for saving the result and can reuse the value. You may have seen this pattern called memoization or lazy evaluation(lazy evaluation).

How to make a struct hold a closure

  • A struct definition needs to know the types of all fields, which means it needs to specify the closure’s type.
  • Each closure instance has its own unique anonymous type, even if two closures have exactly the same signature.
  • Therefore, we need to use: generics and trait bounds.

Fn Trait

  • Provided by the standard library.
  • All closures implement at least one of the following traits:
    • Fn
    • FnMut
    • FnOnce

Note:Functions also implement all three of these traits.Fntrait. If you don’t need to capture values from the environment, you can use a function that implements the Fn trait instead of a closure.

1234567891011121314151617181920212223242526272829
struct Cacher<T>where    T: Fn(u32) -> u32,{    calculation: T,    value: Option<u32>,}impl<T> Cacher<T>where    T: Fn(u32) -> u32,{    fn new(calculation: T) -> Cacher<T> {        Cacher {            calculation,            value: None,        }    }    fn value(&mut self, arg: u32) -> u32 {        match self.value {            Some(v) => v,            None => {                let v = (self.calculation)(arg);                self.value = Some(v);                v            }        }    }}

The Cacher struct has a field calculation of generic type T.

The trait bound on T specifies that T is a closure that uses the Fn trait.

Any closure we want to store in the calculation field of a Cacher instance must have one u32 parameter (specified within the parentheses after Fn) and must return a u32 (specified after the ->).

The value field is Option of type. Before executing the closure, value will be None. If code using Cacher requests the result of the closure, the closure will be executed and the result stored in the Some variant of the value field. ThenIf the code requests the result of the closure again, the closure will not be executed again; instead, the result stored in the Some variant will be returned.

Refactoring code

123456789101112131415161718192021
fn generate_workout(intensity: u32, random_number: u32) {    let mut expensive_result = Cacher::new(|num| {        println!("calculating slowly...");        thread::sleep(Duration::from_secs(2));        num    });    if intensity < 25 {        println!("Today, do {} pushups!", expensive_result.value(intensity));        println!("Next, do {} situps!", expensive_result.value(intensity));    } else {        if random_number == 3 {            println!("Take a break today! Remember to stay hydrated!");        } else {            println!(                "Today, run for {} minutes!",                expensive_result.value(intensity)            );        }    }}

Limitations of the Cacher Implementation

  1. The first problem is that a Cacher instance assumes that for any value of the arg parameter to the value method, it will always return the same value.

Solution

You can use a HashMap instead of a single value:

  • key: the arg parameter

  • value: the result of executing the closure

  1. The second problem is that it can only accept one parameter of type u32 and return a value of type u32.

Solution:

Introduce two or more generic parameters.

Closures can capture their environment.

  • They can capture their environment and access variables from the scope in which they are defined, whereas ordinary functions cannot.
123456789
fn main() {    let x = 4;    let equal_to_x = |z| z == x;    let y = 4;    assert!(equal_to_x(y));}
  • closuresThis incurs memory overhead.
    A closure in Rust is a compiler-generated anonymous struct whose fields are the captured variables.
123456789101112
let x = 10;let c = || println!("{}", x);// Internally, the compiler does something like:struct Closure {    x: i32, // Captured variables}impl Fn() for Closure {    fn call(&self) {        println!("{}", self.x);    }}

Capturing variables requires storage space.

  • If what is captured isValue, the closure will store that value in its own struct.
  • If what is captured isReference, what is stored in the closure struct is a pointer (the reference itself also takes up space).

Overhead size

  • Small variables (e.g., i32, bool): almost no extra overhead; it can be stored in the closure’s struct.
  • Large variables (e.g., String, Vec, HashMap)
    • ifmovecapture, the closure will copy or move the entire object (heap memory may be bound to the closure).
    • If it is only reference capture, the closure only stores a pointer internally, but the reference’s lifetime must be guaranteed valid.

How closures capture values from their environment

The same as the three ways functions receive parameters:

  • Taking ownership: FnOnce

    • FnOnce consumes variables captured from the surrounding scope. The scope surrounding the closure is called its Environmentenvironment. To consume the captured variables, the closure must take ownership of them and move them into the closure when defining the closure. The ‘Once’ part of its name represents the fact that the closure cannot take ownership of the same variables more than once, so it can only be called once.
  • Mutable borrowing: FnMut

    • FnMut takes mutable borrowed values so it can change its environment.
  • Immutable borrowing: Fn

    • Fn takes immutable borrowed values from its environment.

When creating a closure, Rust infers which trait to use based on how the closure uses the environment values:

  • All closures implement FnOnce
  • Closures that do not move captured variables implement FnMut
  • Closures that do not require mutable access to captured variables implement Fn

In fact, there is a hierarchy: all closures that implement Fn also implement FnMut, and all that implement FnMut also implement FnOnce.

The move keyword

Before the parameter listUsing the move keyword, you can force a closure to take ownership of the environment values it uses.

  • when**Pass the closure to a new thread to move data so that it is owned by the new thread.**At this time, this technology is most useful.

Example

1234567891011
fn main() {    let x = vec![1, 2, 3];    let equal_to_x = move |z| z == x;    println!("can't use x here: {:?}", x); // error    let y = vec![1, 2, 3];    assert!(equal_to_x(y));}

x was moved into the closure because the closure is defined with the move keyword. The closure then takes ownership of x, and main is no longer allowed to use x in the println! statement. Removing the println! will fix the problem.

Best Practices

When specifying one of the Fn trait bounds, start with Fn; based on what the closure body does, the compiler will tell you if FnOnce or FnMut is needed.

iterator

The iterator pattern allows you to perform some processing on the items of a sequence.iteratoriterator)responsible for the logic of traversing each item in the sequence and determining when the sequence ends. When using iterators, we don’t need to reimplement this logic.

In Rust,Iterators are lazy (lazy), which means it will have no effect until the method is called to use the iterator.

123
let v1 = vec![1, 2, 3];   let v1_iter = v1.iter();

Iterator trait

  • All iterators implement this trait.
  • defined in the standard library

The definition of this trait looks like this:

1234567
pub trait Iterator {    type Item;    fn next(&mut self) -> Option<Self::Item>;    // The default implementation of the method is omitted here.}

type Item and Self::Item, they define the trait’s Associated Typesassociated type)。

This code shows that implementing the Iterator trait requires bothdefine aItemType, this Item type is used as nextThe return type of a methodIn other words, the Item type will be the type of element returned by the iterator.

The Iterator trait only requires implementing one method: next

next:

  • Return one item from the iteration each time.
  • The return value is wrapped in Some
  • When iteration ends, it returns None

You can call the next method directly on the iterator

1234567891011
#[test]    fn iterator_demonstration() {        let v1 = vec![1, 2, 3];        let mut v1_iter = v1.iter();        assert_eq!(v1_iter.next(), Some(&1));        assert_eq!(v1_iter.next(), Some(&2));        assert_eq!(v1_iter.next(), Some(&3));        assert_eq!(v1_iter.next(), None);    }
  • v1_iter needs to be mutable: calling the next method on an iterator changes the state used to record the position in the sequence. In other words, the code consumeconsumed, or used, the iterator. Each call to next consumes an item from the iterator.
  • When using a for loop, there is no need to make v1_iter mutable because the for loop takes ownership of v1_iter’s ownership and makes v1_iter mutable in the background.

Several iterator methods

  • iter Method: oncreates an iterator over immutable references(immutable references to elements)
  • into_iter Method: createdthe iterator takes ownership
  • iter_mut Method: iterates over mutable references

Methods that consume the iterator

  • In the standard library, the Iterator trait is made up of some methods with default implementations
  • Some of these methods call the next method

one of the reasons why you must implement the next method when implementing the Iterator trait

  • Those that call next are called "consuming adaptor

because calling them exhausts the iterator

An example of a consuming adapter is the sum method. This method takes ownership of the iterator and repeatedly calls next to traverse the iterator, thus consuming it. As it iterates over each item, it adds each item to a running total and returns the total when iteration is complete.

Filename: src/lib.rs

12345678910
#[test]    fn iterator_sum() {        let v1 = vec![1, 2, 3];        let v1_iter = v1.iter();        let total: i32 = v1_iter.sum();        assert_eq!(total, 6);    }

Methods that Produce Other Iterators

The Iterator trait defines another kind of method, called iterator adaptorsiterator adaptors),

  • They allow you to change the current iterator into a different type of iterator.
  • You can chain multiple iterator adaptors.
  • However, because all iterators are lazy, you must call a consuming adaptor method to get the results of the iterator adaptor calls.

File name: src/main.rs

123
let v1: Vec<i32> = vec![1, 2, 3];    v1.iter().map(|x| x + 1);

The map method uses a closure to call each element to generate a new iterator. The closure here creates a new iterator, in which each element of the vector is incremented by 1.

However, this code will produce a warning:

= note: iterators are lazy and do nothing unless consumed

The code doesn’t actually do anything; the specified closure is never called. The warning reminds us why:Iterator adaptors are lazy, and here we need to consume the iterator.

File name: src/main.rs

12345
let v1: Vec<i32> = vec![1, 2, 3];    let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();    assert_eq!(v2, vec![2, 3, 4]);

The underscore in the second line of code is actually letting the compiler infer its type.

The collect method is aconsuming adaptor, which collects the results into a collection type.

Because map takes a closure, you can specify any operation you want to perform on each element as you iterate. This is a great example of how to use closures to customize behavior while reusing the iteration behavior provided by the Iterator trait.

Using Closures to Capture the Environment

The filter method is a filter

  • The filter method of an iterator takes a closure that uses each item of the iterator and returns a Boolean.
  • If the closure returns true, the value will be included in the new iterator provided by filter.
  • If the closure returns false, the value will not be included in the resulting iterator.

Filename: src/lib.rs

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
#[derive(PartialEq, Debug)]struct Shoe {    size: u32,    style: String,}fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {    shoes.into_iter().filter(|s| s.size == shoe_size).collect()    //The closure captures from the environment `shoe_size` the variable and uses its value to compare with each shoe's size}#[cfg(test)]mod tests {    use super::*;    #[test]    fn filters_by_size() {        let shoes = vec![            Shoe {                size: 10,                style: String::from("sneaker"),            },            Shoe {                size: 13,                style: String::from("sandal"),            },            Shoe {                size: 10,                style: String::from("boot"),            },        ];        let in_my_size = shoes_in_size(shoes, 10);        assert_eq!(            in_my_size,            vec![                Shoe {                    size: 10,                    style: String::from("sneaker")                },                Shoe {                    size: 10,                    style: String::from("boot")                },            ]        );    }}

shoes_in_The my_size function takes ownership of a vector of shoes and a shoe size as parameters. It returns a vector containing only shoes of the specified size.

shoes_in_my_In the size function body, it calls into_iter to create an iterator that takes ownership of the vector. Then it calls filter to adapt this iterator into a new iterator that only contains elements for which the closure returns true.

The closure captures the shoe_size variable from the environment and uses its value to compare with each shoe’s size, keeping only shoes of the specified size. Finally, it calls collect to gather the values returned by the iterator adapters into a vector and returns it.

Creating Custom Iterators

The only method required to be provided in the Iterator trait definition is the next method. Once it is defined, you can use all other methods provided by the Iterator trait that have default implementations to create custom iterators!

12345678910111213141516171819202122232425262728293031323334353637
struct Counter {    count: u32,}impl Counter {    fn new() -> Counter {        Counter { count: 0 }    }}impl Iterator for Counter {    type Item = u32;    //Here, the iterator's associated type Item is set to u32, meaning the iterator will return a collection of u32 values.    fn next(&mut self) -> Option<Self::Item> {        if self.count < 5 {            self.count += 1;            Some(self.count)        } else {            None        }        //If the count value is less than 5, next returns the current value wrapped in Some,        //However, if count is greater than or equal to 5, the iterator returns None.    }}  #[test]    fn calling_next_directly() {        let mut counter = Counter::new();        assert_eq!(counter.next(), Some(1));        assert_eq!(counter.next(), Some(2));        assert_eq!(counter.next(), Some(3));        assert_eq!(counter.next(), Some(4));        assert_eq!(counter.next(), Some(5));        assert_eq!(counter.next(), None);    }

By defining the next method to implement the Iterator trait, we can now use any standard library-defined Iterator trait methods that have default implementations, since they all use the functionality of the next method.

For example, for some reason we want to take the values produced by a Counter instance, and pair these values with another Counter instanceomitting the first valueafter that, pair the values produced,multiply each pair of valueskeep only those results that are divisible by threethen add all the retained results, which can be done as in the test in Listing 13-23:

Filename: src/lib.rs

123456789
#[test]   fn using_other_iterator_trait_methods() {       let sum: u32 = Counter::new()           .zip(Counter::new().skip(1))           .map(|(a, b)| a * b)           .filter(|x| x % 3 == 0)           .sum();       assert_eq!(18, sum);   }

Listing 13-23: Using a variety of methods on the custom Counter iterator, note that Counter itself is an Iterator.

Note that zip only produces four pairs of values; theoretically, the fifth pair (5, None) is never produced because zip returns None when either input iterator returns None.

All these method calls are possible because we specified how the next method works, and the standard library provides default implementations of other methods that call next.

Improving the I/O Project

Use iterators and remove clone

Filename: src/lib.rs

123456789101112131415161718
impl Config {    pub fn new(args: &[String]) -> Result<Config, &'static str> {        if args.len() < 3 {            return Err("not enough arguments");        }        let query = args[1].clone();        let filename = args[2].clone();        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();        Ok(Config {            query,            filename,            case_sensitive,        })    }}

Use the iterator returned by env::args directly

File name: src/main.rs

12345678910
fn main() {    //let args: Vec<String> = env::args().collect();    let config = Config::new(env::args()).unwrap_or_else(|err| {        eprintln!("Problem parsing arguments: {}", err);        process::exit(1);    });    // --snip--}

src/lib.rs

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
use std::env;use std::error::Error;use std::fs;pub fn run(config: Config) -> Result<(), Box<dyn Error>> {    //Reading a file    let contents = fs::read_to_string(&config.filename)?;    // println!("With text:\n{}", contents);    let results = if config.case_sensitive {        search(&config.query, &contents)    } else {        search_case_insensitive(&config.query, &contents)    };    for line in results {        println!("{}", line);    }    Ok(())}pub struct Config {    pub query: String,    pub filename: String,    pub case_sensitive: bool,}impl Config {    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {        // if args.len() < 3 {        //     return Err("not enough arguments");        // }        args.next();        let query = match args.next() {            Some(arg) => arg,            None => return Err("Didn't get a query string"),        };        let filename = match args.next() {            Some(arg) => arg,            None => return Err("Didn't get a file name"),        };        // println!("Search for {}", query);        // println!("In file {}", filename);        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();        Ok(Config {            query,            filename,            case_sensitive,        })    }}pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {    // let mut result = Vec::new();    // for line in contents.lines() {    //     if line.contains(query) {    //         result.push(line);    //     }    // }    // result    contents        .lines()        .filter(|line| line.contains(query))        .collect()}pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {    // let mut result = Vec::new();    // let query = query.to_lowercase();    // for line in contents.lines() {    //     if line.to_lowercase().contains(&query) {    //         result.push(line);    //     }    // }    // result    contents        .lines()        .filter(|line| line.to_lowercase().contains(query.to_lowercase().as_str()))        .collect()}#[cfg(test)]mod tests {    use super::*;    #[test]    fn case_sensitive() {        let query = "duct";        let contents = "\Rust:safe,fast,productive.Pick three.";        assert_eq!(vec!["safe,fast,productive."], search(query, contents));    }    #[test]    fn case_insensitive() {        let query = "duct";        let contents = "\Rust:safe,fast,productive.Pick three.";        assert_eq!(            vec!["safe,fast,productive."],            search_case_insensitive(query, contents)        );    }}

src/main.rs

123456789101112131415
use std::env;use std::process;use minigrep::Config;fn main() {    // println!("{:?}",args);    let config = Config::new(env::args()).unwrap_or_else(|err| {        eprintln!("Problem parsing arguments:{}", err);        process::exit(1);    });    if let Err(e) = minigrep::run(config){        eprintln!("Application error: {}",e);        process::exit(1);    };}

Performance comparison: loops/iterators

Iterators are Rust’s zero-cost abstractionzero-cost abstractions) one of, meaning that abstractions do not introduce runtime overhead, and it is exactly as defined by Bjarne Stroustrup (the designer and implementer of C++) in “Foundations of C++” (2012) zero overheadzero-overhead) is exactly the same:

In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.

  • Bjarne Stroustrup “Foundations of C++”

Overall, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand-code any better.

  • Bjarne Stroustrup “Foundations of C++”

Cargo and crates.io

Customizing builds with release profiles

release profile

  • are predefined
  • customizable
  • Each profile configuration is independent of other profiles

Cargo’s two main profiles

  • dev profile: for development, cargo build
  • release profile: for release, cargo build --release

Custom profiles

Add a [profile.xxxx] section in Cargo.toml, and override a subset of the configuration in it.

Filename: Cargo.toml

12345
[profile.dev]opt-level = 0[profile.release]opt-level = 3

The opt-level setting controls how much optimization Rust applies to your code. The value of this setting ranges from 0 to 3. Higher optimization levels require more compilation time, so if you are developing and compiling frequently, you may want to compile faster at the expense of some code performance. This is why the dev opt-level defaults to 0.

Documentation comments

  • Generate HTML documentation
  • Documentation comments for the public API: how to use the API
  • Use ///
  • Supports Markdown
  • Placed before the item

Generate documentation

Run the rustdoc tool

1
cargo doc

Place the generated documentation under target/doc

Generate documentation and browse it

1
cargo doc --open

Common sections

#Examples

Other common sections

123
Panics: 函数可能发生panic的场景Errors: 如果函数返回Result,描述可能的错误种类,以及可导致错误的条件Safety: 如果函数处于unsafe调用,就应该解释函数unsafe的原因,以及调用者确保的使用前提

Documentation comments as tests

Run cargo test: run the example code in documentation comments as tests

Filename: src/lib.rs

12345678910111213
/// Adds one to the number given.////// # Examples////// ```/// let arg = 5;/// let answer = my_crate::add_one(arg);////// assert_eq!(6, answer);/// ```pub fn add_one(x: i32) -> i32 {    x + 1}

Try running cargo test on the documentation for the add_one function in the example; you should see a section like this in the test results:

123456
Doc-tests my_craterunning 1 testtest src/lib.rs - add_one (line 5) ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s

Now try changing the function or the example so that the assert_eq! in the example panics. Run cargo test again, and we will see that the doc test catches that the example and code are no longer in sync!

Add documentation comments to the item that contains the comments

  • Symbol: //!
  • This type of comment usually describes crates and modules

crate root (by convention src/lib.rs)

Within a module, document the crate or module as a whole

Example:

Filename: src/lib.rs

1234567
//! # My Crate//!//! `my_crate` is a collection of utilities to make performing certain//! calculations more convenient./// Adds one to the number given.// --snip--

Use pub use to export a convenient public API

The file structure you use during development may not be convenient for users. Your structure may be a hierarchical structure with multiple levels, but this is not convenient for users. This is because people who want to use types defined deep in the hierarchy may find it hard to discover that these types exist. They may also be annoyed at having to useuse my_crate::some_module::another_module::UsefulType; instead of use my_crate::UsefulType; to use the type.

usepub useRe-export items to make the public structure different from the private structure.

src/lib.rs

123456789101112131415161718192021222324252627282930
//! # Art//!//! A library for modeling artistic concepts.pub mod kinds {    /// The primary colors according to the RYB color model.    pub enum PrimaryColor {        Red,        Yellow,        Blue,    }    /// The secondary colors according to the RYB color model.    pub enum SecondaryColor {        Orange,        Green,        Purple,    }}pub mod utils {    use crate::kinds::*;    /// Combines two primary colors in equal amounts to create    /// a secondary color.    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {        // --snip--        SecondaryColor::Green    }}

File name: src/main.rs

12345678
use art::kinds::PrimaryColor;use art::utils::mix;fn main() {    let red = PrimaryColor::Red;    let yellow = PrimaryColor::Yellow;    mix(red, yellow);}

To remove the crate’s internal organization from the public API, we can take the art crate from the example and add pub use statements to re-export items to the top-level structure, as shown in Listing 14-5:

Filename: src/lib.rs

123456789101112131415
//! # Art//!//! A library for modeling artistic concepts.pub use self::kinds::PrimaryColor;pub use self::kinds::SecondaryColor;pub use self::utils::mix;pub mod kinds {    // --snip--}pub mod utils {    // --snip--}

use

File name: src/main.rs

123456
use art::mix;use art::PrimaryColor;fn main() {    // --snip--}

Publishing a Crate

With a unique name, version number, author information added by cargo new when creating a new project, description, and chosen license, the project ready to publish’s Cargo.toml The file might look like this:

Filename: Cargo.toml

123456789
[package]name = "guessing_game"version = "0.1.0"edition = "2021"description = "A fun game where you guess what number the computer has chosen."license = "MIT OR Apache-2.0"author = "even629"[dependencies]

Cargo’s documentation describes other metadata that can be specified, which can help your crate be discovered and used more easily!

Publishing:

1
$ cargo publish

Once a crate is published, it is permanent: the version cannot be overwritten, and the code cannot be deleted.

  • Purpose: projects that depend on that version can continue to work normally.

Publish a new version of an existing crate.

Modify the version and republish.

Use cargo yank to yank a version from Crates.io.

  • You cannot delete previous versions of a crate.

Yanking a version prevents new projects from starting to depend on that version, but all existing projects that depend on it can still download and depend on that version. Essentially, yanking means that all projects with Cargo.lock the dependencies of projects will not be broken, and any newly generated Cargo.lock will not be able to use the yanked version.

To yank a crate, run cargo yank and specify the version you want to yank:

1
$ cargo yank --vers 1.0.1

You can also undo a yank and allow projects to start depending on a version again by adding --undo to the command:

1
$ cargo yank --vers 1.0.1 --undo

Yank does not Delete any code. For example, the recall feature is not intended to delete accidentally uploaded secret information. If this happens, immediately reset these secrets.

Cargo Workspaces

  • cargo workspaces: help manage multiple interrelated crates that need to be developed together
  • A cargo workspace is a set of packages that share the same Cargo.lock and output folder.

Creating a workspace

In order to at the top level add directory run the binary crate, you can use the -p parameter and package name to run cargo run to specify the package in the workspace that we want to use:

1234
$ cargo run -p adder    Finished dev [unoptimized + debuginfo] target(s) in 0.0s     Running `target/debug/adder`Hello, world! 10 plus one is 11!

This will run adder/src/main.rs the code in, which depends on the add_one crate

Installing binary crates from CRATES.IO

  • Command: cargo install
  • Source https://crates.io
  • Limitation: only crates with a binary target can be installed

Binary target: a runnable program

  • generated by a crate that has src/main.rs or is otherwise designated as a binary

Usually: the README contains a description of the crate:

  • Has a library target
  • Has a library target
  • Has both

cargo install

The binaries installed by cargo install are stored in the bin folder in the root directory

Extending cargo with custom commands

  • cargo is designed to be extended with subcommands
  • Example: if a binary in $PATH is cargo-something, you can run it like a subcommand:
1
$ cargo something
  • Custom commands like this can be listed with the command: cargo --list
  • Advantages: You can use cargo install to install extensions and run them like built-in tools.

smart pointers

  • pointerpointer) is a general concept for a variable that contains a memory address.

This address references, or “points at,” some other data.

  • The most common pointers in Rust are Referencereference)。

References are marked by the & symbol and borrow the value they point to. They have no other special features besides referring to data. They alsohave no extra overhead., so they are used the most.

  • smart pointerssmart pointers) are a class of data structures that behave like pointers but also have additional metadata and functionality.

Other differences between references and smart pointers

  • References: only borrow data
  • Smart pointers: often own the data they point to

Examples of smart pointers:

  • String and Vec
  • both own a region of memory and allow users to operate on it
  • also have metadata (such as capacity, etc.)
  • provide additional functionality or guarantees (String guarantees its data is valid UTF-8).

Implementing smart pointers

  • **Smart pointers are usually implemented using structs,**and implement the Deref and Drop traits.

    • The Deref trait allows instances of a smart pointer struct to be used like references.

    • The Drop trait allows you to customize the code that runs when a smart pointer instance goes out of scope.

Using BoxPointing to data on the heap.

  • Boxis the simplest smart pointer:

    • allows you to store data on the heap (rather than the stack)
    • on the stack is a pointer to the heap data
    • No performance overhead.
    • It has no other extra features.
  • BoxImplements the Deref trait and the Drop trait.

mostly used in the following scenarios:

  • when you have a type whose size is unknown at compile time and you want to use a value of that type in a context that requires an exact size
  • when you have a large amount of data and want to transfer ownership while ensuring the data is not copied
  • when you want to own a value and only care that it implements a specific trait, rather than its concrete type
1234
fn main() {    let b = Box::new(5);    println!("b = {}",b);}

Using Box to enable recursive types

  • At compile time, Rust needs to know how much space a type occupies
  • But the size of a recursive type cannot be determined at compile time
  • But the size of a Box type is fixed
  • Using Box in recursive types can solve the above problem
  • Cons List in functional languages

Cons List

cons list is a data structure originating from the Lisp programming language and its dialects. In Lisp, the cons function (short for “construct function”) uses two arguments to construct a new list, usually a single value and another list.

The concept of the cons function involves more common functional programming terminology; “to x and y cons” usually means constructing a new container and placing x the elements of at the beginning of the new container, followed by the container y the elements of.

Each item in a cons list contains two elements: the value of the current item and the next item. Its last item contains a value called Nil and has no next item. A cons list is produced by recursively calling the cons function. The canonical name representing the termination condition (base case) of recursion is Nil, which announces the termination of the list. Note that this is different from the concept of “null” or “nil”, which represent invalid or missing values.

Cons List is not a commonly used collection in Rust

123456789
use crate::List::{Cons,Nil};fn main() {    let list = Cons(1, Cons(2, Cons(3, Nil)));}enum List {    Cons(i32,List),    Nil,}

Runtime error

12345678910111213141516
Compiling my_box v0.1.0 (C:\Users\cauchy\Desktop\rust\my_box)error[E0072]: recursive type `List` has infinite size --> src\main.rs:6:1  |6 | enum List {  | ^^^^^^^^^7 |     Cons(i32,List),  |              ---- recursive without indirection  |help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle  |7 |     Cons(i32,Box<List>),  |              ++++    +For more information about this error, try `rustc --explain E0072`.error: could not compile `my_box` due to previous error

Computing the size of a non-recursive type

Message enum:

123456
enum Message {    Quit,    Move { x: i32, y: i32 },    Write(String),    ChangeColor(i32, i32, i32),}

When Rust needs to know how much space to allocate for a Message value, it can check each variant and find that

  • Message::Quit does not need any space,
  • Message::Move needs enough space to store two i32 values, and so on.
  • Because an enum only actually uses one of its variants, the space required for a Message value is equal to the size of its largest variant.

In contrast, what happens when the Rust compiler checks a recursive type like the List from the previous example? The compiler tries to figure out how much memory is needed to store a List enum, and starts by examining the Cons variant. The Cons variant needs space equal to the size of an i32 plus the size of a List. To calculate how much memory List needs, it checks its variants, starting with the Cons variant. The Cons variant stores an i32 value and a List value, so this calculation would continue indefinitely.

Using BoxGiving recursive types a known size

  • Because Box is a pointer, we always know how much space it needs

The size of a pointer does not change based on the amount of data it points to.

  • Box
  1. It only provides indirection and heap memory allocation.
  2. It has no other extra features.
  3. No performance overhead.
  4. Suitable for scenarios that require indirect storage, such as a Cons List.
  5. Implements the Deref trait and the Drop trait.

Dref Trait

  • Implementing the Deref trait allows us tocustomize the behavior of the dereference operator*
  • By implementing Deref, smart pointers canbe treated like references.

Dereference operator

File name: src/main.rs

1234567
fn main() {    let x = 5;    let y = &x;    assert_eq!(5, x);    assert_eq!(5, *y);}

Treating Boxas a reference

File name: src/main.rs

1234567
fn main() {    let x = 5;    let y = Box::new(x);    assert_eq!(5, x);    assert_eq!(5, *y);}

Define your own smart pointer

File name: src/main.rs

123456789101112131415
struct MyBox<T>(T);impl<T> MyBox<T> {    fn new(x: T) -> MyBox<T> {        MyBox(x)    }}fn main() {    let x = 5;    let y = MyBox::new(x);    assert_eq!(5, x);    assert_eq!(5, *y);}

The compilation error we get is:

12345678910
$ cargo run   Compiling deref-example v0.1.0 (file:///projects/deref-example)error[E0614]: type `MyBox<{integer}>` cannot be dereferenced  --> src/main.rs:14:19   |14 |     assert_eq!(5, *y);   |                   ^^For more information about this error, try `rustc --explain E0614`.error: could not compile `deref-example` due to previous error

MyBox The type cannot be dereferenced because we haven’t implemented this functionality for the type. To enable the dereferencing functionality of the * operator, we need to implement the Deref trait.

Treat a type like a reference by implementing the Deref trait

  • The Deref trait in the standard library requires us to implement a deref method:

    • This method borrows self

    • Returns a reference to the inner data

File name: src/main.rs

123456789
use std::ops::Deref;impl<T> Deref for MyBox<T> {    type Target = T;    fn deref(&self) -> &Self::Target {        &self.0    }}

When we, in the example code,

1234567
fn main() {    let x = 5;    let y = MyBox::new(x);    assert_eq!(5, x);    assert_eq!(5, *y);}

enter *y, Rust actually runs the following code under the hood:

*(y.deref())

Implicit deref coercion for functions and methods (Deref Coercion)

  • Deref Coercion isfor functions and methodsa convenience feature provided
  • Suppose T implements the Deref trait: Deref Coercion can convert a reference to T into a reference produced by applying Deref to T.
  • When a reference of a type is passed to a function or method, but its type does not match the defined parameter type:
  1. Deref Coercion will automatically occur
  2. The compiler makes a series of deref calls to convert it into the required parameter type.
  3. It is done at compile time, with no extra performance overhead.

File name: src/main.rs

12345678910
fn hello(name: &str) {    println!("Hello, {}!", name);}fn main() {    let m = MyBox::new(String::from("Rust"));    //&m &MyBox<String>    //deref &String    //&String &str    hello(&m);}
  1. Here we use &m to call the hello function, which is a MyBox value’s reference
  2. Because in the example, on MyBox the Deref trait is implemented, Rust can convert &MyBox through deref calls becomes &String.
  3. The standard library provides a Deref implementation on String that returns a string slice, as can be seen in the Deref API documentation. Rust calls deref again to turn &String into &str, which matches the definition of the hello function.

If Rust did not implement deref coercion, in order to use &MyBox a value of the type to call hello, you would have to write the following code:

File name: src/main.rs

1234
fn main() {    let m = MyBox::new(String::from("Rust"));    hello(&(*m)[..]);}

Deref and Mutability

  • You can use the DerefMut trait to overload the * operator for mutable references.
  • When the type and trait are in the following three situations, Rust performs deref coercion:
  1. When T: Deref<Target=U>, allow &T to convert to &U
  2. When T: DerefMut<Target=U>, allow &mut T to convert to &mut U
  3. When T: Deref<Target=U>, allow &mut T to convert to &U
CaseConditionConversion
1T: Deref<Target=U>&Tautomatically becomes&U
2T: DerefMut<Target=U>&mut Tautomatically becomes&mut U
3T: Deref<Target=U>&mut Tmutable referencedowngradeto an immutable reference&U

Example:

12345678
fn greet(name: &str) {    println!("Hello {name}");}let s = String::from("Rust");greet(&s);  // Normally, &String cannot be passed to &str,// but because String implements Deref<Target=str>, it automatically becomes &str

Drop Trait

  • Implementing the Drop trait allows us to customizethe action that occurs when a value is about to go out of scope

    1. For example: file, network resource release, etc.

    2. Any type can implement the Drop trait

  • The Drop trait only requires you to implement the drop method

    1. Parameter: a mutable reference to self
  • The Drop trait is in the prelude

File name: src/main.rs

12345678910111213141516171819
struct CustomSmartPointer {    data: String,}impl Drop for CustomSmartPointer {    fn drop(&mut self) {        println!("Dropping CustomSmartPointer with data `{}`!", self.data);    }}fn main() {    let c = CustomSmartPointer {        data: String::from("my stuff"),    };    let d = CustomSmartPointer {        data: String::from("other stuff"),    };    println!("CustomSmartPointers created.");}

When you run this program, the following output appears:

1234567
$ cargo run   Compiling drop-example v0.1.0 (file:///projects/drop-example)    Finished dev [unoptimized + debuginfo] target(s) in 0.60s     Running `target/debug/drop-example`CustomSmartPointers created.Dropping CustomSmartPointer with data `other stuff`!Dropping CustomSmartPointer with data `my stuff`!

Use std::mem::drop to drop a value early

  • It is difficult to directly disable automatic drop, and there is no need to do so.

    1. The purpose of the Drop trait is to perform automatic cleanup logic.
  • Rust does not allow manually calling the drop method of the Drop trait.

  • ButYou can call the std::mem::drop function from the standard library (prelude) to drop a value early.

File name: src/main.rs

12345678
fn main() {    let c = CustomSmartPointer {        data: String::from("some data"),    };    println!("CustomSmartPointer created.");    drop(c);    println!("CustomSmartPointer dropped before the end of main.");}

Running this code will print the following:

1234567
$ cargo run   Compiling drop-example v0.1.0 (file:///projects/drop-example)    Finished dev [unoptimized + debuginfo] target(s) in 0.73s     Running `target/debug/drop-example`CustomSmartPointer created.Dropping CustomSmartPointer with data `some data`!CustomSmartPointer dropped before the end of main.

We also don’t need to worry about accidentally cleaning up values that are still in use; this would cause a compiler error. The ownership system ensures that references are always valid, and also ensures that drop is called only once, when the value is no longer used.

RcReference Counting Smart Pointer

  • Sometimes a value has multiple owners.

  • To support multiple ownership: Rc

    1. reference counting

    2. track references to a value

    3. 0 references: the value can be cleaned up.

  • Data needs to be allocated on the heap, and this datais read by multiple parts of the program (read-only), but atcompile time, it is impossible to determine which part will finish using this data last.

  • Note Rc Can only be used in single-threaded scenarios.

We want to create two lists that share ownership of a third list. Conceptually, this will look like the figure:

File name: src/main.rs

Cannot use two Boxes the list attempts to share ownership of the third list

123456789101112
enum List {    Cons(i32, Box<List>),    Nil,}use crate::List::{Cons, Nil};fn main() {    let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));    let b = Cons(3, Box::new(a));    let c = Cons(4, Box::new(a));}

Error:

123456789
error[E0382]: use of moved value: `a`  --> src/main.rs:11:30   | 9 |     let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));   |         - move occurs because `a` has type `List`, which does not implement the `Copy` trait10 |     let b = Cons(3, Box::new(a));   |                              - value moved here11 |     let c = Cons(4, Box::new(a));   |                              ^ value used here after move

We modify the definition of List to use Rc instead of Box, as shown in the listing. Now each Cons variant contains a value and an Rc pointing to a ListWhen creating b, instead of taking ownership of a, it clones the Rc contained in a., which increases the reference count from 1 to 2 and allows a and b to share the Rc ownership of the data in the Rc. When creating c, it also clones a, which increases the reference count from 2 to 3. Each time Rc::clone is called, the Rc the reference count of the data in the Rc will increase, and the data will not be cleaned up until there are zero references.

File name: src/main.rs

12345678910111213
enum List {    Cons(i32, Rc<List>),    Nil,}use crate::List::{Cons, Nil};use std::rc::Rc;fn main() {    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));    let b = Cons(3, Rc::clone(&a));    let c = Cons(4, Rc::clone(&a));}

Data structure relationship:

1234567891011121314151617
        a (Rc)      Cons(5)      Cons(10)        Nilb ---> Cons(3) ───┐                  a (共享)c ---> Cons(4) ───┘

You could also call a.clone() instead of Rc::clone(&a), but Rust’s convention here is to use Rc::clone.

  • Rc::clone’s implementation does not make a deep copy of all the data like most types’ clone implementations do.
  • Rc::clone only increases the reference count, which doesn’t take much time. A deep copy can take a long time.

Cloning Rcincreases the reference count

File name: src/main.rs

Rc::strong_count gets the reference count

1234567891011
fn main() {    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));    println!("count after creating a = {}", Rc::strong_count(&a));    let b = Cons(3, Rc::clone(&a));    println!("count after creating b = {}", Rc::strong_count(&a));    {        let c = Cons(4, Rc::clone(&a));        println!("count after creating c = {}", Rc::strong_count(&a));    }    println!("count after c goes out of scope = {}", Rc::strong_count(&a));}

This code will print:

12345678
$ cargo run   Compiling cons-list v0.1.0 (file:///projects/cons-list)    Finished dev [unoptimized + debuginfo] target(s) in 0.45s     Running `target/debug/cons-list`count after creating a = 1count after creating b = 2count after creating c = 3count after c goes out of scope = 2

We can see that the Rc in a has an initial reference count of 1, and each time clone is called, the count increases by 1. When c goes out of scope, the count decreases by 1. There is no need to call a function to decrease the count like calling Rc::clone to increase it; the implementation of the Drop trait, when the Rc value goes out of scope, automatically decreases the reference count.

What we cannot see from this example is that at the end of main, when b and then a go out of scope, the count here will be 0, and the Rc is completely cleaned up. Using Rc allows a value to have multiple owners, and the reference count ensures that the value remains valid as long as any owner still exists.

  • RcThrough theImmutable reference, Rc Allows read-only sharing of data between multiple parts of the program.
  • If Rc also allowed multiple mutable references, it would violate one of the borrowing rules discussed in Chapter 4: multiple mutable borrows to the same location could cause data races and inconsistency.

RefCelland interior mutability

Interior mutability

  • Interior mutability is one of Rust’s design patterns.

  • It allows you to modify data while holding an immutable reference.

  • Data structures that implement interior mutability use unsafe code internally to bypass Rust’s normal mutability and borrowing rules.

  • Similar to Rc RefCell type represents the sole ownership of the data it holds.

Recall the borrowing rules:

  1. At any given time, you can have either one mutable reference or any number of immutable references.
  2. References are always valid.

RefCellUnlike Boxthe difference

BoxRefCell
compilation phaseenforce borrowing rules on codeonly atRuntimecheck borrowing rules
otherwise an error occursotherwise panic

Comparison of borrowing rules checked at different stages

compilation phaseRuntime
expose problems as early as possibleProblems are exposed late, even in production.
No runtime overhead.Slight performance loss due to reference counting.
Best choice for most scenarios.Implements certain specific memory safety scenarios (modifying own data in an immutable environment).
Is Rust’s default behavior.
  • Similar to Rccan only be used forsingle-threadedScenario

Choosing Box,Rc,RefCellthe basis for

BoxRcRefCell
owner of the same dataAmultipleA
Mutability, borrow checkingMutable and immutable borrows (compile-time check)Immutable borrows (compile-time check)Mutable and immutable borrows (runtime check)

Interior mutability: mutably borrowing an immutable value.

A corollary of the borrowing rules is that when you have an immutable value, you cannot mutably borrow it. For example, the following code will not compile:

1234
fn main() {    let x = 5;    let y = &mut x;}

If you try to compile it, you will get the following error:

1234
$ cargo run   Compiling borrowing v0.1.0 (file:///projects/borrowing)error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable --> src/main.rs:3:13

The following is a scenario we want to test:

We are writing a library that records the difference between a value and a maximum value, and sends messages based on the difference between the current value and the maximum value. For example, this library can be used to track the API call quota allowed for a user.

The library only provides functionality for recording the gap from the maximum value and for deciding what message to send in which situation. Programs that use this library are expected to provide the actual message-sending mechanism: the program can choose to log a message, send an email, send a text message, and so on. The library itself doesn’t need to know these details; it just needs to implement the Messenger trait it provides. Listing 15-20 shows the library code:

Filename: src/lib.rs

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
pub trait Messenger {    fn send(&self, msg: &str);}pub struct LimitTracker<'a, T: Messenger> {    messenger: &'a T,    value: usize,    max: usize,}impl<'a, T> LimitTracker<'a, T>where    T: Messenger,{    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {        LimitTracker {            messenger,            value: 0,            max,        }    }    pub fn set_value(&mut self, value: usize) {        self.value = value;        let percentage_of_max = self.value as f64 / self.max as f64;        if percentage_of_max >= 1.0 {            self.messenger.send("Error: You are over your quota!");        } else if percentage_of_max >= 0.9 {            self.messenger                .send("Urgent warning: You've used up over 90% of your quota!");        } else if percentage_of_max >= 0.75 {            self.messenger                .send("Warning: You've used up over 75% of your quota!");        }    }}#[cfg(test)]mod tests {    use super::*;    struct MockMessenger {        sent_messages: Vec<String>,    }    impl MockMessenger {        fn new() -> MockMessenger {            MockMessenger {                sent_messages: vec![],            }        }    }    impl Messenger for MockMessenger {        fn send(&self, message: &str) {            self.sent_messages.push(String::from(message));        }    }    #[test]    fn it_sends_an_over_75_percent_warning_message() {        let mock_messenger = MockMessenger::new();        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);        limit_tracker.set_value(80);        assert_eq!(mock_messenger.sent_messages.len(), 1);    }}

An important part of this code is the Messenger trait, which has a method send that takes a self’sImmutable referenceand text messages. This trait is the interface that mock objects need to implement, so that the mock can be used like a real object. Another important part is that we need to test LimitTracker’s set_value method. We can change the value of the passed-in value parameter, but set_value does not return any value that can be asserted. That is, if we create a LimitTracker with a value that implements the Messenger trait and a specific max, when different value values are passed, the message sender should be told to send appropriate messages.

The mock object we need is one that, when send is called, does not actually send an email or message, but only records that the message was notified to be sent. We can create a new mock object instance, use it to create a LimitTracker, call LimitTracker’s set_value method, and then check whether the mock object has the messages we expect. Listing 15-21 shows an attempted mock object implementation, but the borrow checker does not allow it:

However, this test has a problem:

123456789101112131415
$ cargo test   Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference  --> src/lib.rs:58:13   |2  |     fn send(&self, msg: &str);   |             ----- help: consider changing that to be a mutable reference: `&mut self`...58 |             self.sent_messages.push(String::from(message));   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutableFor more information about this error, try `rustc --explain E0596`.error: could not compile `limit-tracker` due to previous errorwarning: build failed, waiting for other jobs to finish...error: build failed

We cannot modify MockMessenger to record messages because the send method takes an immutable reference to self. We also cannot use &mut self instead as suggested by the error text, because then send’s signature would not match the signature in the Messenger trait definition (you can try changing it to see what error message appears).

This is exactly where interior mutability comes in! We will use RefCell to store sent_messages, and then send will be able to modify sent_messages and store the messages.

Filename: src/lib.rs

123456789101112131415161718192021222324252627282930
#[cfg(test)]mod tests {    use super::*;    use std::cell::RefCell;    struct MockMessenger {        sent_messages: RefCell<Vec<String>>,    }    impl MockMessenger {        fn new() -> MockMessenger {            MockMessenger {                sent_messages: RefCell::new(vec![]),            }        }    }    impl Messenger for MockMessenger {        fn send(&self, message: &str) {            self.sent_messages.borrow_mut().push(String::from(message));        }    }    #[test]    fn it_sends_an_over_75_percent_warning_message() {        // --snip--        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);    }}

Using RefCellRecording borrow information at runtime

  • Two methods (safe interface)

    1. borrow method: returns the smart pointer Ref, which implements Deref

    2. borrow_mut method: returns RefMut, which implements Deref

  • RefCellIt records how many active Refand RefMutsmart pointers

    1. Each call to borrow increments the immutable borrow count by 1.

    2. any RefWhen the value goes out of scope and is dropped: immutable borrow count -1

    3. Each call to borrow_mut: mutable borrow count +1

    4. Any RefMutWhen the value goes out of scope and is dropped: mutable borrow count -1

  • Rust uses this count to enforce the borrow checking rules: at any given time, only multiple immutable borrows or one mutable borrow are allowed.

Combining Rc and RefCell to have multiple owners of mutable data

File name: src/main.rs

123456789101112131415161718192021222324
#[derive(Debug)]enum List {    Cons(Rc<RefCell<i32>>, Rc<List>),    Nil,}use crate::List::{Cons, Nil};use std::cell::RefCell;use std::rc::Rc;fn main() {    let value = Rc::new(RefCell::new(5));    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));    let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));    let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));    *value.borrow_mut() += 10;    println!("a after = {:?}", a);    println!("b after = {:?}", b);    println!("c after = {:?}", c);}

When we print a, b, and c, we can see that they all have the modified value 15 instead of 5:

1234567
$ cargo run   Compiling cons-list v0.1.0 (file:///projects/cons-list)    Finished dev [unoptimized + debuginfo] target(s) in 0.63s     Running `target/debug/cons-list`a after = Cons(RefCell { value: 15 }, Nil)b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))

Other types that can implement interior mutability

  • Cell: access data by copying
  • Mutex: used to implement the interior mutability pattern in cross-thread situations

Reference cycles lead to memory leaks

Rust’s memory safety guarantees make it difficult to accidentally create memory that will never be cleaned up (called memory leakmemory leak)), but it is not impossible. Unlike rejecting data races at compile time, Rust does not guarantee complete avoidance of memory leaks, meaning memory leaks are considered memory-safe in Rust. This can be seen through Rc and RefCell It can be seen that the possibility of creating reference cycles exists. This causes memory leaks, because the reference count of each item can never reach 0, and its value will never be dropped.

File name: src/main.rs

12345678910111213141516171819202122232425262728293031323334353637383940414243
use crate::List::{Cons, Nil};use std::cell::RefCell;use std::rc::Rc;// List is a linked list, where each node is Cons(value, next) or Nil#[derive(Debug)]enum List {    Cons(i32, RefCell<Rc<List>>),    Nil,}// impl List {    fn tail(&self) -> Option<&RefCell<Rc<List>>> {        match self {            Cons(_, item) => Some(item),            Nil => None,        }    }}fn main() {    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));    println!("a initial rc count = {}", Rc::strong_count(&a));    println!("a next item = {:?}", a.tail());    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));    println!("a rc count after b creation = {}", Rc::strong_count(&a));    println!("b initial rc count = {}", Rc::strong_count(&b));    println!("b next item = {:?}", b.tail());    if let Some(link) = a.tail() {        *link.borrow_mut() = Rc::clone(&b);    }    println!("b rc count after changing a = {}", Rc::strong_count(&b));    println!("a rc count after changing a = {}", Rc::strong_count(&a));    // Uncomment the next line to see that we have a cycle;    // it will overflow the stack    // println!("a next item = {:?}", a.tail());}

If you keep the last println! line commented out and run the code, you will get the following output:

1234567891011
$ cargo run   Compiling cons-list v0.1.0 (file:///projects/cons-list)    Finished dev [unoptimized + debuginfo] target(s) in 0.53s     Running `target/debug/cons-list`a initial rc count = 1a next item = Some(RefCell { value: Nil })a rc count after b creation = 2b initial rc count = 1b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })b rc count after changing a = 2a rc count after changing a = 2

If you uncomment the last println! and run the program, Rust will try to print a cycle like a pointing to b pointing to a until the stack overflows. This is because:

In Rust,#[derive(Debug)]for enums (such as linked lists) generates a recursive printing logic: note:nextwill also callDebug→ then prints itsnext→ Recursive call, and this recursive call has no termination condition, so it will eventually cause a stack overflow.

1234567891011
impl Debug for List {    fn fmt(&self, f: &mut Formatter) -> fmt::Result {        match self {            Cons(v, next) => {                write!(f, "Cons({:?}, {:?})", v, next)            }            Nil => write!(f, "Nil")        }    }}

Solutions to prevent memory leaks

  • It relies on the developer to ensure, not on Rust.

  • Reorganize the data structure: some references express ownership, some references do not express ownership.

    1. Part of the circular reference has an ownership relationship, and the other part does not involve an ownership relationship.

    2. And only ownership relationships affect the cleanup of values.

Avoid reference cycles: turn Rc into Weak

  • Rc::clone increments the Rcinstance’s strong_count by 1, Rcinstance is only cleaned up when strong_count is 0

  • RcAn instance can create a Weak Reference (weak reference) to the value by calling the Rc::downgrade method.

    1. The return type is Weak(smart pointer)

    2. Calling Rc::downgrade increments weak_count by 1.

  • RcUse weak_count to track how many Weak references exist.

  • weak_count not being 0 does not affect Rcthe cleanup of the instance

Strong VS Weak

  • Strong Reference is about how to share Rcinstance ownership
  • Weak Reference does not express the above meaning.
  • Using Weak Reference does not create reference cycles:

When the number of Strong References is 0, the Weak Reference automatically disconnects.

  • Before using Weak, you need to ensure that the value it points to still exists:

On a Weakinstance, calling the upgrade method returns Option<Rc>

Creating a tree data structure: a Node with children

File name: src/main.rs

12345678910111213141516171819
use std::cell::RefCell;use std::rc::Rc;#[derive(Debug)]struct Node {    value: i32,    children: RefCell<Vec<Rc<Node>>>,}fn main() {    let leaf = Rc::new(Node {        value: 3,        children: RefCell::new(vec![]),    });    let branch = Rc::new(Node {        value: 5,        children: RefCell::new(vec![Rc::clone(&leaf)]),    });}

Here we clone the Rc in leaf and store it in branch, which means the Node in leaf now has two owners: leaf and branch. We can get leaf from branch via branch.children, but we cannot go from leaf to branch. leaf has no reference to branch and doesn’t know they are related. We want leaf to know that branch is its parent. We’ll do that later.

Adding a reference from child to parent

To make a child node know its parent, we need to add a parent field to the Node struct definition. The question is what the type of parent should be. We know it cannot contain Rc, because then leaf.parent would point to branch and branch.children would contain a pointer to leaf, which would form a reference cycle and cause its strong_count to never become 0.

Now let’s think about this relationship in a different way:

  • A parent node should own its child nodes.
  • If a parent node is dropped, its child nodes should also be dropped.
  • However,A child node should not own its parent node.
  • If a child node is dropped, its parent node should still exist.

This is exactly an example of weak references!

So parent uses Weak type instead of Rc, specifically RefCell<Weak>. Now the Node struct definition looks like this:

File name: src/main.rs

12345678910111213141516171819202122232425262728
use std::cell::RefCell;use std::rc::{Rc, Weak};#[derive(Debug)]struct Node {    value: i32,    parent: RefCell<Weak<Node>>,    children: RefCell<Vec<Rc<Node>>>,}fn main() {    let leaf = Rc::new(Node {        value: 3,        parent: RefCell::new(Weak::new()),        children: RefCell::new(vec![]),    });    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());    let branch = Rc::new(Node {        value: 5,        parent: RefCell::new(Weak::new()),        children: RefCell::new(vec![Rc::clone(&leaf)]),    });    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());}

Creating the leaf node is similar to how we created the leaf node in Listing 15-27, except the parent field is different: leaf starts with no parent, so we create a new empty Weak reference instance.

At this point, when we try to use the upgrade method to get a reference to leaf’s parent, we get a None value. As shown in the first println! output:

1
leaf parent = None

When creating a branch node, it also creates a new Weak reference, because branch does not have a parent node. leaf is still a child node of branch. Once there is a Node instance in branch, you can modify leaf to have a Weak pointing to its parent reference. Here, the RefCell<Weak in leaf’s parent field> borrow_mut method is used, and then the Rc::downgrade function is used to create, from the Rc value in branch, a Weak pointing to branch reference.

When printing leaf’s parent again, this time you will get a Some value containing branch: now leaf can access its parent! When printing leaf, we also avoid the cycle that would eventually cause a stack overflow as in Listing 15-26: Weak references are printed as (Weak):

123
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },children: RefCell { value: [] } }] } })

The lack of infinite output shows that this code did not create a reference cycle. This can also be seen from observing the Rc::strong_count and Rc::weak_count call results.

Visualizing changes to strong_count and weak_countLet’s, by creating a new inner scope and placing the creation of branch inside it, observe the Rc instance’s strong_count and weak_count value changes. This will show what happens when branch is created and dropped when it goes out of scope. These changes are shown in the example:

Example: Creating branch in an inner scope and checking its strong and weak reference counts

File name: src/main.rs

1234567891011121314151617181920212223242526272829303132333435363738394041424344
fn main() {    let leaf = Rc::new(Node {        value: 3,        parent: RefCell::new(Weak::new()),        children: RefCell::new(vec![]),    });// leaf strong = 1, weak = 0    println!(        "leaf strong = {}, weak = {}",        Rc::strong_count(&leaf),        Rc::weak_count(&leaf),    );    {        let branch = Rc::new(Node {            value: 5,            parent: RefCell::new(Weak::new()),            children: RefCell::new(vec![Rc::clone(&leaf)]),        });        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);// branch strong = 1, weak = 1        println!(            "branch strong = {}, weak = {}",            Rc::strong_count(&branch),            Rc::weak_count(&branch),        );// leaf strong = 2, weak = 0        println!(            "leaf strong = {}, weak = {}",            Rc::strong_count(&leaf),            Rc::weak_count(&leaf),        );    }// leaf parent = None    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());// leaf strong = 1, weak = 0    println!(        "leaf strong = {}, weak = {}",        Rc::strong_count(&leaf),        Rc::weak_count(&leaf),    );}

Once leaf is created, its Rc has a strong count of 1 and a weak count of 0. In the inner scope, branch is created and associated with leaf, at which point the Rc in branch has a strong count of 1 and a weak count of 1 (because leaf.parent points to branch via Weak pointing to branch). Here leaf’s strong count is 2, because branch’s branch.children now stores a copy of leaf’s Rc copy, but the weak count is still 0.

When the inner scope ends, branch goes out of scope, and the Rc strong count decreases to 0, so its Node is dropped. The weak count of 1 from leaf.parent is unrelated to whether Node is dropped, so no memory leak occurs!

If you try to access the parent of leaf after the end of the inner scope, you will get None again. At the end of the program, the Rc in leaf has a strong count of 1 and a weak count of 0, because leaf is again the Rc only reference.

All of this logic for managing counts and values is built into Rc and Weak and their Drop trait implementations. By specifying the child-to-parent relationship in the Node definition as a Weakreference, you can have bidirectional references between parent and child nodes without causing reference cycles and memory leaks.

Fearless Concurrency

  • concurrent programmingConcurrent programming), which means different parts of the program execute independently,
  • parallel programmingparallel programming) means different parts of the program execute at the same time

Using Threads to Run Code Simultaneously

In most modern operating systems, the code of an executed program runs in a processprocess), and the operating system manages multiple processes. Inside a program, you can also have multiple independent parts running at the same time. The feature that runs these independent parts is called threadsthreads)。

Splitting the computation in a program into multiple threads can improve performance, because the program can perform multiple tasks at the same time, but it also adds complexity. Because threads run simultaneously, there is no guarantee about the order in which code in different threads executes. This can lead to problems such as:

  • Race conditions, where multiple threads access data or resources in an inconsistent order
  • Deadlocks, where two threads wait for each other to stop using a resource they own, which prevents them from continuing
  • Bugs that occur only in certain situations and are difficult to reproduce and fix reliably

Programming languages have a few different ways to implement threads.

  • Many operating systems provide an API for creating new threads. This model, in which a programming language calls the operating system API to create threads, is sometimes called _1:1_1:1 threading, where one OS thread corresponds to one language thread.Rust’s standard library only provides 1:1 thread implementation.; requires a smaller runtime (i.e., Rust does not need an additional thread scheduler or complex mechanisms; it only needs to track thread handles, call OS APIs when creating and destroying threads, and has no extra user-space scheduling).
  • There are some crates that implement other thread models with different trade-offs, namely language-implemented threads (green threads): the M:N model. Requires a larger runtime.

Creating a new thread with spawn

To create a new thread, you need to call thread::spawn function and pass a closure containing the code you want to run in the new thread

File name: src/main.rs

12345678910111213141516
use std::thread;use std::time::Duration;fn main() {    thread::spawn(|| {        for i in 1..10 {            println!("hi number {} from the spawned thread!", i);            thread::sleep(Duration::from_millis(1));        }    });    for i in 1..5 {        println!("hi number {} from the main thread!", i);        thread::sleep(Duration::from_millis(1));    }}

When the main thread ends, the new thread also ends, regardless of whether it has finished executing.

Using the join Handle to wait for all threads to complete.

  • The return type of thread::spawn is JoinHandle.
  • JoinHandle is an owned value
  • When the join method is called on it, it blocks the currently running thread until the thread represented by the handle terminates.

File name: src/main.rs

123456789101112131415161718
use std::thread;use std::time::Duration;fn main() {    let handle = thread::spawn(|| {        for i in 1..10 {            println!("hi number {} from the spawned thread!", i);            thread::sleep(Duration::from_millis(1));        }    });    for i in 1..5 {        println!("hi number {} from the main thread!", i);        thread::sleep(Duration::from_millis(1));    }    handle.join().unwrap();}

Calling join on the handle blocks the current thread until the thread represented by the handle finishes.BlockingBlocking) Thread means blocking that thread from doing work or exiting. Because we placed the join call after the for loop in the main thread,

Using move closures

  • move closures are often used with the thread::spawn function, allowing you to use data from other threads
  • When creating a thread, transfer the ownership of a value from one thread to another.

Example: Trying to use a vector created in the main thread from another thread

File name: src/main.rs

1234567891011
use std::thread;fn main() {    let v = vec![1, 2, 3];    let handle = thread::spawn(|| {        println!("Here's a vector: {:?}", v);    });    handle.join().unwrap();}

The closure uses v, so it captures v and makes it part of the closure’s environment. Because thread::spawn runs this closure in a new thread, v can be accessed in the new thread. However, when compiling this example, you get the following error:

12345678910111213141516171819202122232425
$ cargo run   Compiling threads v0.1.0 (file:///projects/threads)error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function --> src/main.rs:6:32  |6 |     let handle = thread::spawn(|| {  |                                ^^ may outlive borrowed value `v`7 |         println!("Here's a vector: {:?}", v);  |                                           - `v` is borrowed here  |note: function requires argument type to outlive `'static` --> src/main.rs:6:18  |6 |       let handle = thread::spawn(|| {  |  __________________^7 | |         println!("Here's a vector: {:?}", v);8 | |     });  | |______^help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword  |6 |     let handle = thread::spawn(move || {  |                                ++++For more information about this error, try `rustc --explain E0373`.error: could not compile `threads` due to previous error

Rust will infer how to capture v, because println! only needs a reference to v, so the closure tries to borrow v. However, there is a problem:Rust doesn’t know how long the new thread will run, so it cannot know whether the reference to v will always be valid.

Listing 16-4 shows a scenario where a reference to v is likely to no longer be valid:

File name: src/main.rs

12345678910111213
use std::thread;fn main() {    let v = vec![1, 2, 3];    let handle = thread::spawn(|| {        println!("Here's a vector: {:?}", v);    });    drop(v); // oh no!    handle.join().unwrap();}

By adding the move keyword before the closure, we force the closure to take ownership of the values it uses, rather than letting Rust infer that it should borrow the values. The following shows the modified code, which compiles and runs as we expect:

Listing: Using the move keyword to force ownership of the values it uses

File name: src/main.rs

1234567891011
use std::thread;fn main() {    let v = vec![1, 2, 3];    let handle = thread::spawn(move || {        println!("Here's a vector: {:?}", v);    });    handle.join().unwrap();}

Using Message Passing to Transfer Data Between Threads

One increasingly popular approach to ensuring safe concurrency is message passingmessage passing), where threads or actors communicate by sending messages containing data. This idea comes from the slogan in [Go programming language documentation]: “Do not communicate by sharing memory; instead, share memory by communicating.”

  • Threads (or actors) communicate by sending messages (data) to each other.
  • Rust: Channel (provided by the standard library)

Channel

  • A channel consists of:Transmitting endReceiving end
  • Call the sender’s methods to send data
  • The receiving end checks and receives the arriving data.
  • If either the sending end or the receiving end is dropped, the channel is “closed”.

Creating a Channel

  • usempsc::channel function to create a channel
  1. mpsc stands for multiple producer,single consumer(multiple producers, one consumer)
  2. Returns a tuple: the elements are the sending end and the receiving end, respectively.
1
pub fn channel<T>() -> (Sender<T>, Receiver<T>)

Let’s move the sending end to a new thread and send a string, so the new thread can communicate with the main thread.

Listing: Moving tx to a new thread and sending “hi”

1234567891011121314
use std::sync::mpsc;use std::thread;fn main() {    let (tx, rx) = mpsc::channel();    thread::spawn(move || {        let val = String::from("hi");        tx.send(val).unwrap();    });    // Block the main thread's execution until a value is received from the channel.    let received = rx.recv().unwrap();    println!("Got: {}", received);}

Here we again use thread::spawn to create a new thread and use move to move tx into the closure so that the new thread owns tx. The new thread needs to own the sending end of the channel in order to send messages through the channel.

The sending side of a channel has a send method that takes the value to be put into the channel. The send method returns a Result<T, E> type, so**if the receiving end has already been dropped, there is no target to send the value to, so the send operation will return an error.**In this example, calling unwrap when an error occurs causes a panic. However, for a real program, you need to handle it properly.

The recv method on the receiving end

  • The receiving end of a channel has two useful methods: recv and try_recv.
  • Here, we used recv, which is receive short for receive. This method will**block the main thread’s execution until a value is received from the channel.**Once a value is sent, recv returns it in a Result<T, E>. When the sending end of the channel closes, recv returns an error indicating that no new values will arrive.
  • try_recv does not block, instead it immediately returns a Result<T, E>: an Ok value contains available information, and an Err value means there is no message at this time. If the thread has other work to do while waiting for messages, use try_recv is useful: you can write a loop that frequently calls try_recv, processing messages when available, and otherwise doing some other work for a while until checking again.

Channels and Ownership Transference

Now let’s do an experiment to see how channels and ownership work together to avoid problems: we will try to send the val value through the channel in the new thread after use it. Try compiling the code in the following example and see why this is not allowed:

Example: Attempting to use val after we have sent it through the channel

File name: src/main.rs

123456789101112131415
use std::sync::mpsc;use std::thread;fn main() {    let (tx, rx) = mpsc::channel();    thread::spawn(move || {        let val = String::from("hi");        tx.send(val).unwrap();        println!("val is {}", val);    });    let received = rx.recv().unwrap();    println!("Got: {}", received);}

Here, we try to print val after sending it through the channel via tx.send. Allowing this would be a bad idea:

Once the value is sent to another thread, that thread might modify or drop it before we use it again. Possible modifications to the value by other threads could cause errors or unexpected results due to inconsistent or nonexistent data.

However, when trying to compile the code in Example 16-9, Rust gives an error:

1234567891011121314
$ cargo run   Compiling message-passing v0.1.0 (file:///projects/message-passing)error[E0382]: borrow of moved value: `val`  --> src/main.rs:10:31   |8  |         let val = String::from("hi");   |             --- move occurs because `val` has type `String`, which does not implement the `Copy` trait9  |         tx.send(val).unwrap();   |                 --- value moved here10 |         println!("val is {}", val);   |                               ^^^ value borrowed here after moveFor more information about this error, try `rustc --explain E0382`.error: could not compile `message-passing` due to previous error

Our concurrency error causes a compile-time error. The send function takes ownership of its parameter and moves the value to be owned by the receiver. This prevents accidentally using the value again after sending; the ownership system checks that everything is in order.

Sending Multiple Values and Seeing the Receiver Waiting

Example: Sending multiple messages and pausing for a while after each send

File name: src/main.rs

12345678910111213141516171819202122232425
use std::sync::mpsc;use std::thread;use std::time::Duration;fn main() {    let (tx, rx) = mpsc::channel();    thread::spawn(move || {        let vals = vec![            String::from("hi"),            String::from("from"),            String::from("the"),            String::from("thread"),        ];        for val in vals {            tx.send(val).unwrap();            thread::sleep(Duration::from_secs(1));        }    });    for received in rx {        println!("Got: {}", received);    }}

This time, there is a vector of strings in the newly created thread that we want to send to the main thread. We iterate over them, send each string individually, and call the thread::sleep function with a Duration value to pause for one second.

In the main thread, we no longer explicitly call the recv function: instead, we treat rx as an iterator. For each received value, we print it out. When the channel is closed, the iterator will also end.

When you run the code in Example 16-10, you will see the following output, with each line pausing for one second:

1234
Got: hiGot: fromGot: theGot: thread

Because there is no code in the for loop in the main thread that pauses or waits, it can be said that the main thread is waiting to receive values from the newly created thread.

Create multiple producers by cloning the sender

Example: Sending multiple messages from multiple producers

File name: src/main.rs

1234567891011121314151617181920212223242526272829303132333435363738
// --snip--    let (tx, rx) = mpsc::channel();    let tx1 = tx.clone();    thread::spawn(move || {        let vals = vec![            String::from("hi"),            String::from("from"),            String::from("the"),            String::from("thread"),        ];        for val in vals {            tx1.send(val).unwrap();            thread::sleep(Duration::from_secs(1));        }    });    thread::spawn(move || {        let vals = vec![            String::from("more"),            String::from("messages"),            String::from("for"),            String::from("you"),        ];        for val in vals {            tx.send(val).unwrap();            thread::sleep(Duration::from_secs(1));        }    });    for received in rx {        println!("Got: {}", received);    }    // --snip--

This time, before creating a new thread, we called the clone method on the sending end of the channel. This will give us a**The sender handle that can be passed to the first newly created thread.**We will pass the original channel sender to the second newly created thread. This way there will be two threads, each sending a different message to the channel’s receiver.

If you run these codes, you possible You will see output like this:

12345678
Got: hiGot: moreGot: fromGot: messagesGot: forGot: theGot: threadGot: you

Although you may see these values appear in a different order; this depends on your system. This is why concurrency is both interesting and difficult. If you experiment with thread::sleep, providing different values in different threads, you will find that their execution is more nondeterministic and produces different output each time.

Shared-state concurrency

  • To some extent, channels in any programming language are similar to single ownership, because once a value is sent into a channel, it can no longer be used.
  • Shared memory is similar to multiple ownership: multiple threads can simultaneously access the same memory location.

A mutex allows only one thread to access data at a time.

mutexmutex) are mutual exclusion abbreviation, that is, at any time, it only allows one thread to access certain data. To access the data in the mutex, a thread first needs to acquire the mutex’s locklock) to indicate that it wishes to access the data. A lock is a data structure that is part of a mutex, which records who has exclusive access to the data. Therefore, we describe the mutex as a lock system. protectguarding) its data.

Mutexes are notorious for being difficult to use, because you have to remember:

  1. Attempt to acquire the lock before using the data.
  2. After you have finished processing the data protected by the mutex, you must unlock the data so that other threads can acquire the lock.

In Rust, thanks to the type system and ownership, we can’t get locking and unlocking wrong.

The Mutex API

As an example of how to use a mutex, let’s start by using a mutex in a single-threaded context, as shown in the example:

Example: For simplicity, exploring the Mutex in a single-threaded context API

File name: src/main.rs

123456789101112
use std::sync::Mutex;fn main() {    let m = Mutex::new(5);    {        let mut num = m.lock().unwrap();        *num = 6;    }    println!("m = {:?}", m);}

Like many types, we use the associated functionnewto create aMutex.Use thelockmethod to acquire the lock to access the data in the mutex. This call blocks the current thread until we have the lock.

Once the lock is acquired, we can treat the return value (here,num)**as a mutable reference to its inner data.**The type system ensures that we acquire the lock before using the value inm:Mutex is not ani32, so Must You must acquire the lock to use thisi32value. We won’t forget to do this because otherwise the type system won’t allow access to the inneri32value.

Mutex is a smart pointer. More precisely,Thelockcall returns a smart pointer calledMutexGuard.This smart pointer implementsDerefto point to its inner data; it also provides aDropimplementation that automatically releases the lock whenMutexGuardgoes out of scope.which happens at the end of the inner scope in Listing 16-12. For this reason, we don’t risk forgetting to release the lock and blocking the mutex from being used by other threads, becausethe lock is released automatically.

Sharing a Mutex Between Threads

Now let’s try using a Mutex to share a value between multiple threads. We’ll spawn ten threads and increment the same counter value by one in each thread, so the counter will go from 0 to 10. The example in the listing will have compile errors, and we’ll use these errors to learn how to useMutexand how Rust helps us use it correctly.

Example: The program spawns 10 threads, each of which uses a Mutex to increment the value of the counter

File name: src/main.rs

12345678910111213141516171819202122
use std::sync::Mutex;use std::thread;fn main() {    let counter = Mutex::new(0);    let mut handles = vec![];    for _ in 0..10 {        let handle = thread::spawn(move || {            let mut num = counter.lock().unwrap();            *num += 1;        });        handles.push(handle);    }    for handle in handles {        handle.join().unwrap();    }    println!("Result: {}", *counter.lock().unwrap());}

Here we create a counter variable to hold a Mutex containing an i32.similar to Example 16-12. Next, we iterate over the range to create 10 threads. We use thread::spawn and give the same closure to all threads: each of them will call the lock method to acquire the Mutex the lock on the Mutex, then add one to the value in the mutex. When a thread finishes executing, num will go out of scope and release the lock, so another thread can acquire it.

In the main thread, we collect all the join handles and call their join methods to ensure all threads finish. At this point, the main thread acquires the lock and prints the program’s result.

Compilation fails:

123456789101112131415
$ cargo run   Compiling shared-state v0.1.0 (file:///projects/shared-state)error[E0382]: use of moved value: `counter`  --> src/main.rs:9:36   |5  |     let counter = Mutex::new(0);   |         ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait...9  |         let handle = thread::spawn(move || {   |                                    ^^^^^^^ value moved into closure here, in previous iteration of loop10 |             let mut num = counter.lock().unwrap();   |                           ------- use occurs due to use in closureFor more information about this error, try `rustc --explain E0382`.error: could not compile `shared-state` due to previous error

The error message indicates that the counter value was moved in the previous loop iteration. So Rust tells uscannotcountermove the ownership of the lock to multiple threads

Multiple Threads and Multiple Ownership

By using the smart pointer Rc to create a reference-counted value, you can have multiple owners.

Example: Attempting to Use Rc to allow multiple threads to own the Mutex

File name: src/main.rs

123456789101112131415161718192021222324
use std::rc::Rc;use std::sync::Mutex;use std::thread;fn main() {    let counter = Rc::new(Mutex::new(0));    let mut handles = vec![];    for _ in 0..10 {        let counter = Rc::clone(&counter);        let handle = thread::spawn(move || {            let mut num = counter.lock().unwrap();            *num += 1;        });        handles.push(handle);    }    for handle in handles {        handle.join().unwrap();    }    println!("Result: {}", *counter.lock().unwrap());}

Compile again and… a different error appears!

123456789101112131415161718192021
$ cargo run   Compiling shared-state v0.1.0 (file:///projects/shared-state)error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely   --> src/main.rs:11:22    |11  |           let handle = thread::spawn(move || {    |  ______________________^^^^^^^^^^^^^_-    | |                      |    | |                      `Rc<Mutex<i32>>` cannot be sent between threads safely12  | |             let mut num = counter.lock().unwrap();13  | |14  | |             *num += 1;15  | |         });    | |_________- within this `[closure@src/main.rs:11:36: 15:10]`    |    = help: within `[closure@src/main.rs:11:36: 15:10]`, the trait `Send` is not implemented for `Rc<Mutex<i32>>`    = note: required because it appears within the type `[closure@src/main.rs:11:36: 15:10]`note: required by a bound in `spawn`For more information about this error, try `rustc --explain E0277`.error: could not compile `shared-state` due to previous error

The first line of the error indicates Rc<Mutex> cannot be sent between threads safely. The compiler also tells us the reasonthe trait Sendis not implemented forRc<Mutex>. The next section will talk about Send: this is one of the traits that ensures the types used can be used in concurrent environments.

Unfortunately,Rccannot be safely shared between threads. When Rc manages the reference count, it must increment the count on every clone call and decrement the count when each clone is dropped.Rcdoes not use any concurrency primitives to ensure that the operation of changing the count is not interrupted by other threads.. If the count is wrong, it can cause subtle bugs, such as memory leaks or dropping a value before it has finished being used. What we need is a type exactly like Rc, but that changes the reference count in a thread-safe way.

Atomically Reference Counted Arc

Arcis exactly such a type like Rc and can be safely used in concurrent environments. atomicityatomic), so this is a atomically reference countedatomically reference counted) type.

Why aren’t all primitive types atomic? Why aren’t all types in the standard library implemented with Arc by default? implemented?

The reason is thatthread safety comes with a performance penalty, and we want to pay that cost only when necessary. If you’re only operating on values within a single thread, the guarantees provided by atomicity are unnecessary, and the code can run faster.

Example: Using Arc to wrap a Mutex to share ownership across multiple threads

File name: src/main.rs

1234567891011121314151617181920212223
use std::sync::{Arc, Mutex};use std::thread;fn main() {    let counter = Arc::new(Mutex::new(0));    let mut handles = vec![];    for _ in 0..10 {        let counter = Arc::clone(&counter);        let handle = thread::spawn(move || {            let mut num = counter.lock().unwrap();            *num += 1;        });        handles.push(handle);    }    for handle in handles {        handle.join().unwrap();    }    println!("Result: {}", *counter.lock().unwrap());}

This prints:

1
Result: 10

The Similarity Between RefCell/Rc and Mutex/Arc

  • Because counter is immutable, but we can get a mutable reference to its inner value; this means Mutex provides interior mutability, just like the Cell family of types. Just as you can use RefCell to change the contents of Rc in the same way, you can use Mutex To change Arc The content within.
  • Rust cannot avoid using Mutex The entire logic error. Recall using Rc. there is a risk of causing a reference cycle, where two Rc Mutual references between values cause memory leaks. Similarly, Mutex also causes deadlockdeadlock) risk. This occurs when an operation needs to lock two resources and two threads each hold one lock, causing them to wait for each other forever.

Scalable Concurrency with Sync and Send Traits

An interesting aspect of Rust’s concurrency model is that the language itself knows very little about concurrency. very fewAlmost everything we discussed earlier belongs to the standard library, not the language itself. Since the language does not need to provide concurrency-related infrastructure, concurrency solutions are not limited by the standard library or the language: we can write our own or use concurrency features written by others.

However, there are two concurrency concepts:embedded in the language:The Sync and Send traits in std::marker.

Allow transferring ownership between threads via Send

  • The Send marker trait indicates that ownership of values of types that implement Send can be transferred between threads.
  • Almost all Rust types are Send,
  • However, there are some exceptions, including Rc: This cannot be sent,

Because if Rc is cloned the value and try to transfer ownership of the clone to another thread, both threads may update the reference count simultaneously. For this reason, Rc It is implemented for single-threaded scenarios, where there is no need to pay the performance cost of thread-safe reference counting.

  • Rust’s type system and trait bounds ensure that the unsafe Rc will never be accidentally… Sending between threads. When trying to do so, you get the error the trait Send is not implemented for Rc<Mutex>. But using Arc, which is marked as Send, there is no problem.
  • Any type that is entirely composed of Send types is automatically marked as Send as well.Almost all primitive types are Send, except for raw pointers.

Sync allows access from multiple threads.

  • The Sync marker trait indicates that a type that implements Sync cansafely have references to its value in multiple threads.
  • In other words,**for any type T, T is Sync if &T (an immutable reference to T) is Send,**which means that references to it can be safely sent to another thread.
  • Similar to Send, primitive types are Sync, and types composed entirely of Sync types are also Sync.
  • The smart pointer Rc is also not Sync, for the same reason it is not Send. RefCelland Cell series types are not Sync. RefCell The borrow checking performed at runtime is also not thread-safe.
  • Mutex is Sync, as described in the “Sharing a Mutex Between Threads” section, it can be used to share access across multiple threads.

Manually implementing Send and Sync is unsafe

  • It is usually not necessary to manually implement the Send and Sync traits, because types composed of Send and Sync types are automatically Send and Sync.
  • Because they are marker traits, they don’t even need any methods to be implemented. They are only used to enforce invariants related to concurrency.
  • Manually implementing these marker traits involves writing unsafe Rust code,

What is important now is that when creating a new concurrent type made up of parts that are not Send and Sync, you need to be careful to ensure that its safety guarantees are maintained.“The Rustonomicon” contains more information about these guarantees and how to maintain them.

Rust’s object-oriented features

Characteristics of object-oriented languages

  • Objects contain data and behavior

Under this definition, Rust is object-oriented: structs and enums contain data, and impl blocks provide methods on structs and enums. Although structs and enums with methods are not called objects, they provide the same functionality as objects,

  • Encapsulation hides implementation details

Wrapper(_encapsulation_The idea: the implementation details of an object cannot be accessed by code using the object. Therefore, the only way to interact with an object is through the public API it provides; code using the object cannot reach into the object’s internals and directly change data or behavior. Encapsulation makes it possible to change and refactor an object’s internals without changing the code that uses the object.

In Rust, the pub keyword can be used to make modules, types, functions, and methods public, while by default everything else is private.

Example:

For example, we can define a struct AveragedCollection that contains a vector of i32 values. The struct can also have a field that holds the average of all values in the vector. This way, anyone who wants to know the average of the vector in the struct can get it at any time without having to calculate it themselves. In other words, AveragedCollection will cache the average result for us. The following example shows the definition of the AveragedCollection struct:

Example: The AveragedCollection struct maintains a list of integers and the average of all elements in the collection.

Filename: src/lib.rs

1234
pub struct AveragedCollection {    list: Vec<i32>,    average: f64,}

Note thatthe struct itself is marked as pub, so other code can use this struct, but the fields inside the struct remain private. This is very important, because we want to ensure that when a value is added to the list or removed from the list, the average is updated at the same time. This can be done by implementing add, remove, and average methods on the struct, as shown in the example:

Example: The add, remove, and average public methods are implemented on the AveragedCollection struct

Filename: src/lib.rs

1234567891011121314151617181920212223242526
impl AveragedCollection {    pub fn add(&mut self, value: i32) {        self.list.push(value);        self.update_average();    }    pub fn remove(&mut self) -> Option<i32> {        let result = self.list.pop();        match result {            Some(value) => {                self.update_average();                Some(value)            }            None => None,        }    }    pub fn average(&self) -> f64 {        self.average    }    fn update_average(&mut self) {        let total: i32 = self.list.iter().sum();        self.average = total as f64 / self.list.len() as f64;    }}

The public methods add, remove, and average are the only ways to modify an instance of AveragedCollection. When the add method is used to add an element to list, or the remove method is used to delete one, the implementations of these methods also call the private update_average method to update the average field.

list and average are private, so there is no other way for external code to directly add or remove elements from list; otherwise, when list changes, the average field might become out of sync. The average method returns the value of the average field, which allows external code to only read average but not modify it.

Because we have encapsulated the implementation details of AveragedCollection, we can easily change aspects such as the data structure in the future. For example, we can use HashSet instead of Vec as the type of the list field. As long as the signatures of the public functions add, remove, and average remain unchanged, code using AveragedCollection does not need to change. Conversely, if list were made public, that would not necessarily be the case: HashSet and Vec use different methods to add or remove items, so if external code wanted to modify list directly, it might have to be changed.

If encapsulation is a necessary aspect for a language to be considered object-oriented, then Rust satisfies this requirement. Using pub or not in different parts of the code can encapsulate implementation details.

  • Inheritance as a type system and code sharing

inheritInheritance) is a mechanism provided by many programming languages, where an object can be defined to inherit the definition of another object, allowing it to obtain the parent object’s data and behavior without redefining them.

**If a language must have inheritance to be called an object-oriented language, then Rust is not object-oriented.**It is not possible to define a struct that inherits the members and methods of a parent struct. However, if you have often used inheritance in your programming toolbox, Rust also provides other solutions, depending on the reason you originally considered inheritance.

There are two main reasons for choosing inheritance.

  1. The first is to reuse code: once a particular behavior is implemented for a type, inheritance can reuse that implementation for a different type. In contrast, Rust code can use default trait method implementations for sharing,
  2. The second reason for using inheritance relates to the type system: it manifests as subtypes being usable where the parent type is used. This is also known as polymorphismpolymorphism), which means that if multiple objects share specific properties, they can be used interchangeably.

Recently, inheritance as a language design solution has fallen out of favor in many languages, as it often carries the risk of sharing more code than necessary.Subclasses should not always share all the characteristics of their parent class, but inheritance always does so. This makes program design more inflexible and introduces meaningless subclass method calls., or the possibility of errors due to methods not actually being applicable to subclasses. Some languages also only allow subclasses to inherit from one parent class, further limiting the flexibility of program design.

Trait objects for values of different types

The limitation that a vector can only store elements of the same type. In our earlier example, we provided an alternative by defining a SpreadsheetCell enum to store integer, floating-point, and text members. This means you can store different types of data in each cell while still having a vector that represents a row of cells. This works perfectly when you know at compile time the fixed set of types you want to be able to use interchangeably.

However, sometimes we want library users to be able to extend the set of valid types in specific situations.

To demonstrate how to implement this, here we will create an example of a graphical user interface (GUI) tool that draws items to the screen by iterating over a list and calling the draw method on each item — a common technique for GUI tools. We will create a library crate called gui that contains the structure of a GUI library. This GUI library includes some types that developers can use, such as Button or TextField. On top of this, users of gui want to create custom types that can be drawn on the screen: for example, one programmer might add an Image, and another might add a SelectBox.

This example won’t implement a fully functional GUI library, but it will show how the various parts fit together. When writing a library, we cannot know and define all the types that other programmers might want to create. What we do know is that the GUI needs to keep track of a series of values of different types, and needs to be able to call the draw method on each of them. We don’t need to know exactly what happens when the draw method is called, as long as the value has that method available for us to call.

In languages with inheritance, you can define a class named Component that has a draw method. Other classes such as Button, Image, and SelectBox derive from Component and thus inherit the draw method. Each of them can override the draw method to define its own behavior, but the framework treats all these types as instances of Component and calls draw on them.

However, Rust does not have inheritance, so we have to find another way.

Defining a trait for common behavior

To achieve the behavior expected by the GUI, let’s define a Draw trait that contains a method named draw. Then we can define a storage trait object The vector. A trait object points to an instance of a type that implements the trait we specify, as well as a table used to look up the trait’s methods for that type at runtime. We create trait objects by specifying some kind of pointer, such as a & reference or Box. Smart pointers, along with the dyn keyword, and specifying the relevant trait (the [“Dynamic Sized Types and the Sized Trait”] section will explain why trait objects must use pointers). We can use trait objects in place of generics or concrete types. Anywhere a trait object is used, Rust’s type system will ensure at compile time that any value used in this context implements the trait of the trait object. This way, there is no need to know all possible types at compile time.

Rust deliberately does not refer to structs and enums as “objects” to distinguish them from objects in other languages. In a struct or enum, the data in the struct fields and the behavior in impl blocks are separated, unlike in other languages where data and behavior are combined into a concept called an object.

Trait objects combine both data and behavior; in this sense, then they are more similar to objects in other languages. However, trait objects differ from traditional objects because you cannot add data to a trait object. Trait objects are not as general-purpose as objects in other languages: their (the trait object’s) specific role is to allow abstraction over common behavior.

The following example shows how to define a trait named Draw with a draw method:

Filename: src/lib.rs

123
pub trait Draw {    fn draw(&self);}

Below, we define a struct named Screen that holds a vector named components. The type of this vector is Box, which is a trait object: it is a stand-in for any type in a Box that implements the Draw trait.

Filename: src/lib.rs

123
pub struct Screen {    pub components: Vec<Box<dyn Draw>>,}

Example: A definition of a Screen struct that has a field named components, which contains a vector of trait objects that implement the Draw trait

On the Screen struct, we will define a run method that calls the draw method on each component in its components, as shown in the example:

Filename: src/lib.rs

1234567
impl Screen {    pub fn run(&self) {        for component in self.components.iter() {            component.draw();        }    }}

This is different from defining a struct that uses generic type parameters with trait bounds.A generic type parameter can only be substituted with one concrete type at a time, and Trait objects, on the other hand, allow multiple concrete types to be substituted at runtime. For example, you could define the Screen struct to use generics and trait bounds, as shown in the example:

Example: An alternative implementation of the Screen struct whose run method uses generics and trait bounds

Filename: src/lib.rs

1234567891011121314151617
pub trait Draw {    fn draw(&self);}pub struct Screen<T: Draw> {    pub components: Vec<T>,}impl<T> Screen<T>where    T: Draw,{    pub fn run(&self) {        for component in self.components.iter() {            component.draw();        }    }}

Thisrestrictsthe elements stored in the Vec in a Screen instance must be of the same type, meaning you must have a list of components that are all Button types or all TextField types. If you only needhomogeneous (same-type) collections, then you would prefer to use generics and trait bounds, because their definitions are monomorphized with concrete types at compile time.

12345678910111213
pub trait Draw {    fn draw(&self);}pub struct Screen {    pub components: Vec<Box<dyn Draw>>,}impl Screen {    pub fn run(&self) {        for component in self.components.iter() {            component.draw();        }    }}

On the other hand, by using trait objects, a Screen instance can hold a Vec of smart pointers that can contain either Button or TextField.

Implementing the Trait

Now let’s add some types that implement the Draw trait. We will provide the Button type. Actually implementing a GUI library is out of scope, so the draw method body won’t have any meaningful implementation. To imagine what this implementation might look like, a Button struct might have width, height, and label fields, as shown in the example:

Filename: src/lib.rs

Example: A Button struct that implements the Draw trait

1234567891011
pub struct Button {    pub width: u32,    pub height: u32,    pub label: String,}impl Draw for Button {    fn draw(&self) {        // code to actually draw a button    }}

The width, height, and label fields on Button will differ from other components; for example, TextField might have width, height, label, and placeholder fields. Each type we want to draw on the screen will use different code to implement the draw method of the Draw trait to define how to draw that particular type, like the Button type here (it doesn’t contain any actual GUI code, which is beyond the scope of this chapter). In addition to implementing the Draw trait, Button might also have another impl block containing methods for how the button responds to clicks. Such methods are not applicable to types like TextField.

If a user of the library decides to implement a SelectBox struct with width, height, and options fields, and also implements the Draw trait for it, as shown in the example:

Example: Implementing the Draw trait on a SelectBox struct in another crate that uses gui

File name: src/main.rs

12345678910111213
use gui::Draw;struct SelectBox {    width: u32,    height: u32,    options: Vec<String>,}impl Draw for SelectBox {    fn draw(&self) {        // code to actually draw a select box    }}

The library user can now create a Screen instance in their main function. At this point, they can add components by putting SelectBox and Button into Box turning them into trait objects. Then they can call Screen’s run method, which will call the draw method on each component. The following example shows this implementation:

Example: Using trait objects to store values of different types that implement the same trait

File name: src/main.rs

123456789101112131415161718192021222324
use gui::{Button, Screen};fn main() {    let screen = Screen {        components: vec![            Box::new(SelectBox {                width: 75,                height: 10,                options: vec![                    String::from("Yes"),                    String::from("Maybe"),                    String::from("No"),                ],            }),            Box::new(Button {                width: 50,                height: 10,                label: String::from("OK"),            }),        ],    };    screen.run();}

When writing a library, we don’t know who might add a SelectBox type at some point, but the Screen implementation can operate on and draw this new type because SelectBox implements the Draw trait, which means it implements the draw method.

This concept — caring only about the information a value reflects rather than its concrete type — is similar to what is called duck typingduck typing) concept: if it walks like a duck and quacks like a duck, then it’s a duck! In the run implementation on Screen in the example, run doesn’t need to know what the concrete types of the components are.It doesn’t check whether a component isButtonorSelectBoxan instance ofBy specifyingBoxascomponentsthe type of the values in the vector, we define Screen as requiring values on which the draw method can be called.

The advantage of using trait objects and Rust’s type system to perform duck-typing-like operations is that you don’t need to check at runtime whether a value implements a particular method or worry about errors when calling it because the value doesn’t implement the method. If a value doesn’t implement the trait required by the trait object, Rust won’t compile the code.

For example, the example shows what happens when creating a Screen that uses String as its component:

Example: Attempting to use a type that doesn’t implement the trait required by the trait object

File name: src/main.rs

123456789
use gui::Screen;fn main() {    let screen = Screen {        components: vec![Box::new(String::from("Hi"))],    };    screen.run();}

We’ll get this error because String doesn’t implement the rust_gui::Draw trait:

123456789101112
$ cargo run   Compiling gui v0.1.0 (file:///projects/gui)error[E0277]: the trait bound `String: Draw` is not satisfied --> src/main.rs:5:26  |5 |         components: vec![Box::new(String::from("Hi"))],  |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not implemented for `String`  |  = note: required for the cast to the object type `dyn Draw`For more information about this error, try `rustc --explain E0277`.error: could not compile `gui` due to previous error

This tells us that either we are passing a type to Screen that we didn’t intend to pass and should provide a different type, or we should implement Draw on String so that Screen can call draw on it.

Trait objects perform dynamic dispatch.

  • The monomorphization the compiler performs when using trait bounds on generics: the compiler generates non-generic implementations of functions and methods for each concrete type that replaces the generic type parameter. The code produced by monomorphization is executed Static dispatchstatic dispatch)。
  • Static dispatch occurs when the compiler knows at compile time which method is being called.
  • dynamic dispatch.dynamic dispatch) The compiler cannot know at compile time which method is being called.
  • In the case of dynamic dispatch, the code generated by the compiler can only determine at runtime which method is being called.

When using trait objects, Rust must use dynamic dispatch.. The compiler cannot know all the types that might be used with trait object code, so it also does not know which method implementation of which type to call. For this reason, Rust uses the pointers in the trait object at runtime to know which method needs to be called.Dynamic dispatch also prevents the compiler from selectively inlining method code., which correspondinglydisables some optimizations. Although the extra flexibility is indeed gained in the process of writing examples and the code that supports the examples, there are still trade-offs to be made.

Trait objects require type safety.

Only object-safe traits can be implemented as trait objects.

There are some complex rules for achieving object safety for traits, but in practice, only two rules are relevant.

A trait is object-safe if all the methods defined in the trait satisfy the following rules:

  • The return type is not Self.
  • There are no generic type parameters.

When we use trait objects, we are actually doing dynamic dispatch.. Rust at runtime uses a so-called vtable method table to find the corresponding method implementation. To achieve this, the trait’s methods must be able to at compile time fully determine the method signature.(including type information of return values, etc.), cannot rely on unknown type information.

But some trait methods depend onSelfor generics, so the compiler cannot guarantee that dynamic dispatch is safe.

An example of a non-object-safe trait is the standard library’sClonetrait. The declaration of the clone method in the Clone trait is as follows:

123
pub trait Clone {    fn clone(&self) -> Self;}

The String type implements the Clone trait. When we call the clone method on a String instance, we get a String instance. Similarly, if we call Vec instance’s clone method, we get a Vec type instance. The signature of the clone method needs to know which type is Self, because Self is its return type.

When we try to compile code that violates the object safety rules of trait objects, we will receive a compiler hint. For example, we want to implement a Screen struct to hold a type that implements the Clone trait instead of the Draw trait, as shown below

123
pub struct Screen {    pub components: Vec<Box<dyn Clone>>,}

We will receive the following error:

12345678910111213
$ cargo build   Compiling gui v0.1.0 (file:///projects/gui)error[E0038]: the trait `Clone` cannot be made into an object --> src/lib.rs:2:29  |2 |     pub components: Vec<Box<dyn Clone>>,  |                             ^^^^^^^^^ `Clone` cannot be made into an object  |  = note: the trait cannot be made into an object because it requires `Self: Sized`  = note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>For more information about this error, try `rustc --explain E0038`.error: could not compile `gui` due to previous error

This error means we cannot use this trait for trait objects.

123456789101112131415161718192021222324
// Use at least two ways to make the code work// Do not add/delete any lines of codetrait MyTrait {    fn f(&self) -> Self;}impl MyTrait for u32 {    fn f(&self) -> Self { 42 }}impl MyTrait for String {    fn f(&self) -> Self { self.clone() }}fn my_function(x: Box<dyn MyTrait>)  {    x.f()}fn main() {    my_function(Box::new(13_u32));    my_function(Box::new(String::from("abc")));    println!("Success!")}

First way to modify

1234567891011121314151617181920
trait MyTrait {    fn f(&self) -> Self;}impl MyTrait for u32 {    fn f(&self) -> u32 { 42 }}impl MyTrait for String {    fn f(&self) -> String { self.clone() }}fn my_function(x: impl MyTrait) -> impl MyTrait  {    x.f()}fn main() {    my_function(13_u32);    my_function(String::from("abc"));}

Second way to modify

1234567891011121314151617181920
trait MyTrait {    fn f(&self) -> Box<dyn MyTrait>;}impl MyTrait for u32 {    fn f(&self) -> Box<dyn MyTrait> { Box::new(42) }}impl MyTrait for String {    fn f(&self) -> Box<dyn MyTrait> { Box::new(self.clone()) }}fn my_function(x: Box<dyn MyTrait>) -> Box<dyn MyTrait> {    x.f()}fn main() {    my_function(Box::new(13_u32));    my_function(Box::new(String::from("abc")));}

Implementation of an object-oriented design pattern

  • State patternstate pattern) is an object-oriented design pattern. The key to this pattern is that a value has some internal state, represented by a series of state objects, and the value’s behavior changes with its internal state
  • State objects share functionality: in Rust, structs and traits are used instead of objects and inheritance. Each state object is responsible for its own behavior and when it should transition to another state. The value holding a state object knows nothing about the behavior of different states or when state transitions occur.
  • Using the state pattern means that when the business requirements of the program change, there is no need to change the code that holds the state or uses the value. We only need to update the code in a state object to change its rules, or add more state objects.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
trait State {    fn request_review(self: Box<Self>) -> Box<dyn State>;    fn approve(self: Box<Self>) -> Box<dyn State>;    fn content<'a>(&self, _post: &'a Post) -> &'a str {        ""    }}pub struct Post {    state: Option<Box<dyn State>>,    content: String,}impl Post {    pub fn new() -> Post {        Post {            state: Some(Box::new(Draft {})),            content: String::new(),        }    }    pub fn add_text(&mut self, text: &str) {        self.content.push_str(text);    }    pub fn content(&self) -> &str {        // Delegate to the state object to decide whether to return content        self.state.as_ref().unwrap().content(self)    }    pub fn request_review(&mut self) {        if let Some(s) = self.state.take() {            self.state = Some(s.request_review());        }    }    pub fn approve(&mut self) {        if let Some(s) = self.state.take() {            self.state = Some(s.approve());        }    }}// ===== State Implementation =====struct Draft {}impl State for Draft {    fn request_review(self: Box<Self>) -> Box<dyn State> {        Box::new(PendingReview {})    }    fn approve(self: Box<Self>) -> Box<dyn State> {        self // ignore    }}struct PendingReview {}impl State for PendingReview {    fn approve(self: Box<Self>) -> Box<dyn State> {        Box::new(Published {})    }    fn request_review(self: Box<Self>) -> Box<dyn State> {        self // ignore    }}struct Published {}impl State for Published {    fn request_review(self: Box<Self>) -> Box<dyn State> {        self // ignore    }    fn approve(self: Box<Self>) -> Box<dyn State> {        self // ignore    }    fn content<'a>(&self, post: &'a Post) -> &'a str {        &post.content    }}// ===== Testing =====fn main() {    let mut post = Post::new();    post.add_text("Rust makes systems programming safe!");    assert_eq!("", post.content()); // Still in draft, cannot view content    post.request_review();    assert_eq!("", post.content()); // Pending review, still cannot view content    post.approve();    assert_eq!("Rust makes systems programming safe!", post.content()); // Published!    println!("Published content: {}", post.content());}

Patterns and Pattern Matching

Pattern:

  • Patterns are a special syntax in Rust for matching the structure of complex and simple types.
  • Combining patterns with match expressions and other constructs gives you more control over the program’s control flow.
  • Patterns consist of some combination of the following elements:
    • Literals
    • Destructured arrays, enums, structs, and tuples
    • variable
    • Wildcard
    • Placeholders

Match arms

  • match VALUE

  • Requirement for expressions: exhaustive (include all possibilities)

  • A special pattern: _ (underscore): it does not match anything, does not bind to a variable, and is usually used for the last arm of a match, or to ignore certain values.

Conditional if let expressions

  • The if let expression is mainly a concise way to replace a match with only one arm.

  • if let can optionally have an else, including:

    • else if
    • else if let
  • But if let does not check exhaustiveness, for example:

12345678910111213141516171819
fn main() {    let favorite_color: Option<&str> = None;    let is_tuesday = false;    let age: Result<u8,_> = "34".parse();    if let Some(color) = favorite_color{        print!("Using your favorite color,{},as the background",color);    } else if is_tuesday{        println!("Tuesday is green day!");    } else if let Ok(age) = age {        if age > 30 {            println!("Using purple as the background color");        } else {            println!("Using orange as the background color");        }    } else {        println!("Using blue as the background color");    }}

while let conditional loops

  • As long as the pattern continues to match, it allows the while loop to keep running.
12345678910
fn main() {    let mut stack = Vec::new();    stack.push(1);    stack.push(2);    stack.push(3);    while let Some(top) = stack.pop() {        println!("{}",top);    }}

Pattern matching in for loops

  • The for loop is the most common loop in Rust.
  • In a for loop, the pattern is the value immediately following the for keyword.
123456
fn main() {   let v = vec!['a','b','c'];   for (index,value) in v.iter().enumerate(){        println!("{} is at index {}",value,index);   }}

iter().enumerate() returns a tuple.

Pattern matching in let statements

  • let statements are also patterns
  • let PARTTERN = EXPRESSION
12
let a = 5;let (x,y,z) = (1,2,3);

Function parameters

  • Function parameters can also be patterns
1234567
fn print_coordinates(&(x,y): &(i32,i32)) {    println!("Current location: ({},{})",x,y);}fn main() {    let point = (3,5);    print_coordinates(&point);}

Refutability: whether a pattern can fail to match

  • The two forms of patterns: refutable and irrefutable
  • A pattern that can match any possible value is irrefutable, for examplelet x = 4;
  • A pattern that cannot match some possible values is refutable, for exampleif let Some(x) = a_value
  • Function parameters, let statements, and for loops only accept irrefutable patterns
  • if let and while let accept both refutable and irrefutable patterns
1234
fn main() {    let a: Option<i32> = Some(5);    let Some(x) = a;}

output

12345678
error[E0005]: refutable pattern in local binding: `None` not covered   --> src\main.rs:3:9    |3   |     let Some(x) = a;    |         ^^^^^^^ pattern `None` not covered    |    = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant    = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html

Cannot match because the pattern does not cover the None case

Modify

123456
fn main() {    let a: Option<i32> = Some(5);    if let Some(x) = a{    };}

In match, all arms except the last are refutable; the last arm must be irrefutable because it needs to match all remaining cases

Matching literals

123456789
fn main(){    let x = 1;    match x {        1 => println!("one"),        2 => println!("two"),        3 => println!("three"),        _ => println!("anything"),    }}

Matching named variables

  • A named variable is an irrefutable pattern that matches any value
12345678910
fn main(){    let x = Some(5);    let y = 10;    match x {        Some(50) => println!("Got 50"),        Some(y) => println!("Matched,y={:?}",y),        _ => println!("Default Case,x={:?}",x),    }    println!("at the end: x={:?},y={:?}",x,y);}

Here, in the second arm of the matchy is a new variablethat exists in the scope of that arm

Matching a mutable reference

Using patterns&mut VWhen matching a mutable reference, note that if the patternnotWrite&mut(for example, directly usingvalue), Rust’s match ergonomics will automatically make the binding a mutable reference; if you explicitly write&mut V, thenVit will be the destructured value (which triggers a move).

123456789
fn main() {    let mut v = String::from("hello,");    let r = &mut v;    match r {        // match ergonomics: value is automatically bound as &mut String        value => value.push_str(" world!")    }}

Multiple patterns

  • In a match expression, using the | syntax (meaning ‘or’) can match multiple patterns.
12345678
fn main(){    let x= 1;    match x {        1 | 2 => println!("one or two"),        3 => println!("three"),        _ => println!("anything"),    }}

Use …= to match a range of values.

12345678910111213
fn main(){    let x= 5;    match x{        1..=5 => println!("one through five"),        _ => println!("something else"),    }    let x = 'c';    match x {        'a'..='j' => println!("early ASCII letter"),        'k'..='z' => println!("late ASCII letter"),        _ => println!("something else"),    }}

Numbers or characters are both acceptable.

Destructuring to decompose values.

  • You can use patterns to destructure structs, enums, and tuples, thereby referencing different parts of these types’ values.

Destructuring assignment

123456
fn main() {    let (x, y);    (x,..) = (3, 4);    [.., y] = [1, 2];    assert_eq!([x,y],[3,2]);}

Destructuring tuples

1234567
fn main() {    let (mut x, y) = (1, 2);    x += 2;    assert_eq!(x, 3);    assert_eq!(y, 2);}

Destructuring structs

12345678910111213141516171819202122
#[derive(Debug)]struct Point{    x: i32,    y: i32,}fn main(){    let p = Point{x:0,y:7};    let Point {x:a,y:b}=p;    assert_eq!(0,a);    assert_eq!(7,b);    //Shorthand form    let Point{x,y} = p;    assert_eq!(0,x);    assert_eq!(7,y);    match p {        Point {x,y:0}=> println!("On the x axis at {}",x),//Requires y to be 0.        Point {x:0,y} => println!("On the y axis at {}",y),//Requires x to be 0.        Point {x,y} => println!("On neither axis:({},{})",x,y),    }}

Destructuring enums

Note: The following code triggers a copy or move.

1234567891011121314151617181920212223
enum Message{    Quit,    Move{x:i32,y:i32},    Write(String),    ChangeColor(i32,i32,i32),}fn main(){    let msg = Message::ChangeColor(0, 160, 255);    match msg {        Message::Quit => {            println!("The Quit variant has no data to destructure");        }        Message::Move { x, y }=>{            println!("Move in the x direction {} and in the y direction {}",x,y);        }        Message::Write(text)=>{            println!("Text message:{}",text);        }        Message::ChangeColor(r, g, b)=>{            println!("rgb is ({},{},{})",r,g,b);        }    }}

Run the following code:

1234567891011121314151617181920212223242526
#[derive(Debug)]enum Message {    Quit,    Move { x: i32, y: i32 },    Write(String),    ChangeColor(i32, i32, i32),}fn main() {    let msg = Message::Write(String::from("Hello World\n"));    match msg {        Message::Quit => {            println!("The Quit variant has no data to destructure");        }        Message::Move { x, y } => {            println!("Move in the x direction {} and in the y direction {}", x, y);        }        Message::Write(text) => {            println!("Text message:{}", text);        }        Message::ChangeColor(r, g, b) => {            println!("rgb is ({},{},{})", r, g, b);        }    }    println!("{:?}", msg);}

Error:

123456789101112131415161718
error[E0382]: borrow of partially moved value: `msg`  --> src/main.rs:24:22   |17 |         Message::Write(text) => {   |                        ---- value partially moved here...24 |     println!("{:?}", msg);   |                      ^^^ value borrowed here after partial move   |   = note: partial move occurs because value has type `String`, which does not implement the `Copy` trait   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)help: borrow this binding in the pattern to avoid moving the value   |17 |         Message::Write(ref text) => {   |                        +++For more information about this error, try `rustc --explain E0382`.error: could not compile `rust_programming` (bin "rust_programming") due to 1 previous error

Can match references.

1234567891011121314151617181920212223
enum Message {    Quit,    Move { x: i32, y: i32 },    Write(String),    ChangeColor(i32, i32, i32),}fn main() {    let msg = Message::ChangeColor(0, 160, 255);    match &msg {        Message::Quit => {            println!("The Quit variant has no data to destructure");        }        Message::Move { x, y } => {            println!("Move in the x direction {} and in the y direction {}", x, y);        }        Message::Write(text) => {            println!("Text message:{}", text);        }        Message::ChangeColor(r, g, b) => {            println!("rgb is ({},{},{})", r, g, b);        }    }}

Destructuring nested structs and enums

12345678910111213141516171819202122
enum Color{    Rgb(i32,i32,i32),    Hsv(i32,i32,i32),}enum Message{    Quit,    Move{x:i32,y:i32},    Write(String),    ChangeColor(Color),}fn main(){    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));    match msg {        Message::ChangeColor(Color::Rgb(r, g, b))=>{            println!("Change the color to red {},green {},and blue {}",r,g,b);        }        Message::ChangeColor(Color::Hsv(h, s, v))=>{            println!("Change the color to hue {},saturation {},and value {}",h,s,v);        }        _ => (),    }}

Destructuring structs and tuples

1234567
struct Point{    x: i32,    y: i32,}fn main(){    let ((feet,inches),Point{x,y}) = ((3,10),Point{x:3,y:-10});}

Ignoring values in patterns

  • There are several ways to ignore entire values or parts of values in patterns:

_ ignores the entire value

123456
fn foo(_:i32,y:i32){    println!("y is {}",y);}fn main(){    foo(3, 4);}

Use nested _ to ignore part of a value.

1234567891011121314151617181920
fn main(){    let mut setting_value = Some(5);    let new_setting_value = Some(10);    match (setting_value,new_setting_value) {        (Some(_),Some(_))=>{            println!("Can't overwrite an existing customized value");        }        _ =>{            setting_value = new_setting_value;        }    }    println!("setting is {:?}",setting_value);    let numbers = (3,4,8,16,32);    match numbers {        (first,_,third,_,fifth)=>{            println!("Some numbers:{},{},{}",first,third,fifth);        }    }}

Use names starting with _ to ignore unused variables.

12345678
fn main(){    let s = Some(String::from("Hello!"));    if let Some(_s) = s {        println!("found a string");    }    println!("{:?}",s);}

In pattern matching_s is a new variable, pattern matching moves ownership of s to_s, accessing s later will cause an error.

123456789101112131415161718
error[E0382]: borrow of partially moved value: `s` --> src/main.rs:6:22  |3 |     if let Some(_s) = s {  |                 -- value partially moved here...6 |     println!("{:?}", s);  |                      ^ value borrowed here after partial move  |  = note: partial move occurs because value has type `String`, which does not implement the `Copy` trait  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)help: borrow this binding in the pattern to avoid moving the value  |3 |     if let Some(ref _s) = s {  |                 +++For more information about this error, try `rustc --explain E0382`.error: could not compile `rust_programming` (bin "rust_programming") due to 1 previous error

Use _

1234567
fn main(){    let s = Some(String::from("Hello!"));    if let Some(_) = s {        println!("found a string");    }    println!("{:?}",s);}

_, no binding occurs, no ownership is moved.

… (ignore the rest of the value)

123456789101112131415161718
struct Point{    x: i32,    y: i32,    z: i32,}fn main(){    let origin = Point{x:0,y:0,z:0};    match origin {        Point {x,..} => println!("x is {}",x),    }    let numbers = (2,4,8,16,32);    match numbers {        (first,..,last)=>{            println!("Some numbers: {},{}",first,last)        }    }}

Need to add a comma,

12345678910
fn main() {    let numbers = (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048);    match numbers {        (first,..,last) => {           assert_eq!(first, 2);           assert_eq!(last, 2048);        }    }}

Use match guards to provide additional conditions.

  • A match guard is an additional if condition after the match arm pattern; it must also be satisfied for the pattern to match.
  • Match guards are suitable for more complex scenarios.
123456789101112131415161718192021222324252627
fn main(){    let num = Some(4);    match num {        Some(x) if x < 5 => println!("less than five:{}",x),        Some(x) => println!("{}",x),        None => (),    }}fn main(){    let x = Some(5);    let y = 10;    match x {        Some(50) => println!("Got 50"),        Some(n) if n==y => println!("Matched,n = {:?}",n),//Here, if n == y is not a pattern, it does not bind a new variable.        _ =>println!("Default case,x ={:?}",x),    }    println!("at the end:x={:?},y={:?}",x,y);}fn main(){    let x= 4;    let y = false;    match x {        4 | 5 | 6 if y=> println!("yes"),        _ => println!("no"),    }}

@ bindings

  • The @ symbol lets us create a variable that holds a value while testing whether that value matches a pattern.

It is equivalent to an equals sign.

123456789101112131415161718
enum Message{    Hello {id:i32},}fn main(){    let msg = Message::Hello { id: 5 };    match msg {        Message::Hello { id: id_variable @ 3..=7, }=>{            println!("Found an id in range:{}",id_variable);        }        Message::Hello { id: 10..=12 }=>{            println!("Found an id in another range");        }        Message::Hello { id }=>{            println!("Found some other id:{}",id);        }    }}
12345678910111213141516
struct Point {    x: i32,    y: i32,}fn main() {    // fill in the blank to let p match the second arm    let p = Point { x: 2, y: 20 }; // x can be [0, 5], y can be 10 20 or 30    match p {        Point { x, y: 0 } => println!("On the x axis at {}", x),        // second arm        Point { x: 0..=5, y: y@ (10 | 20 | 30) } => println!("On the y axis at {}", y),        Point { x, y } => println!("On neither axis: ({}, {})", x, y),    }}

Application scenarios:

The following code will cause an error.

1234567891011121314151617
enum Message {    Hello { id: i32 },}fn main() {    let msg = Message::Hello { id: 5 };    match msg {        Message::Hello {            id:  3..=7,        } => println!("id 值的范围在 [3, 7] 之间: {}", id),//Error cannot find value `id` in this scope        Message::Hello { id: newid@10 | 11 | 12 } => {//Error variable `newid` is not bound in all patterns pattern doesn't bind `newid`            println!("id 值的范围在 [10, 12] 之间: {}", newid)        }        Message::Hello { id } => println!("Found some other id: {}", id),    }}

Fix the error.

1234567891011121314151617
enum Message {    Hello { id: i32 },}fn main() {    let msg = Message::Hello { id: 5 };    match msg {        Message::Hello {            id: id @3..=7,        } => println!("id 值的范围在 [3, 7] 之间: {}", id),        Message::Hello { id: newid@(10 | 11 | 12) } => {            println!("id 值的范围在 [10, 12] 之间: {}", newid)        }        Message::Hello { id } => println!("Found some other id: {}", id),    }}

unsafe Rust

  • There is a second language hidden, ithas no enforced memory safety guarantees.: unsafe Rust (unsafe Rust)

Same as regular Rust, but provides extra superpowers.

  • Reasons for Unsafe Rust’s existence:
  1. Static analysis is conservative; using unsafe Rust is like telling the compiler: I know what I’m doing, and I’ll take the corresponding risks.
  2. Computer hardware is inherently unsafe; Rust needs to be able to do low-level system programming.

unsafe superpowers

  • Use the unsafe keyword to switch to unsafe Rust, opening a block that contains unsafe code.
  • Four actions performed in unsafe Rust (unsafe superpowers):
  1. Dereference raw pointers.
  2. Calling an unsafe function or method
  3. Accessing or modifying a mutable static variable
  4. Implementing an unsafe trait
  • Note:

unsafe blockdoes not turn off the borrow checker or disable other safety checks

Any memory safety-related errors must remain inside unsafe blocks

Isolate unsafe code as much as possible, preferably encapsulating it in a safe abstraction that provides a safe API

Dereference raw pointers.

  • Raw pointers

*Mutable: mut T

Immutable: const T, meaningAfter the pointer is dereferenced, it cannot be directly assigned to*

Note: The * here is not a dereference operator; it is part of the type name

  • Unlike references, raw pointers:
  1. Allow ignoring the borrowing rules by having both mutable and immutable pointers, or mutable pointers to the same location
  2. Cannot guarantee that they point to valid memory
  3. Allowed to be null
  4. Do not implement any automatic cleanup
  • Give up guaranteed safety in exchange for better performance or the ability to interface with other languages or hardware
1234567
fn main(){    let mut num=5;    let r1 = &num as *const i32;    let r2 = &mut num as *mut i32;    let address = 0x012345usize;    let r = address as *const i32;}

You can create raw pointers in safe code blocks, but you cannot dereference them

12345678910111213141516
fn main(){    let mut num=5;    let r1 = &num as *const i32;    let r2 = &mut num as *mut i32;    unsafe{        println!("r1:{}",*r1);        println!("r2:{}",*r2);    }    let address = 0x012345usize;    let r = address as *const i32;    unsafe{        println!("r:{}",*r);    }}

Why use raw pointers?

  • Interfacing with C
  • Building safe abstractions that the borrow checker cannot understand

Calling an unsafe function or method

  • Unsafe functions or methods: prefix the definition with the unsafe keyword.
    • Before calling, you must manually satisfy certain conditions (mainly by reading the documentation), because Rust cannot verify these conditions.
    • It must be called within an unsafe block.
123456
unsafe fn dangerous(){}fn main(){    unsafe{        dangerous();    }}

Creating safe abstractions around unsafe code.

  • A function containing unsafe code does not mean the entire function needs to be marked unsafe.
  • Wrapping unsafe code in a safe function is a common abstraction.
1234567891011121314
use std::vec;fn split_at_mut(slice:&mut[i32],mid:usize)->(&mut [i32],&mut[i32]){    let len = slice.len();    assert!(mid<=len);    (&mut slice[..mid],&mut slice[mid..])}fn main(){    let mut v= vec![1,2,3,4,5,6];    let r = &mut v[..];    let (a,b) = r.split_at_mut(3);    assert_eq!(a,&mut [1,2,3]);    assert_eq!(b,&mut [4,5,6]);}

Error

1234567891011121314
error[E0499]: cannot borrow `*slice` as mutable more than once at a time --> src\main.rs:6:29  |3 | fn split_at_mut(slice:&mut[i32],mid:usize)->(&mut [i32],&mut[i32]){  |                       - let's call the lifetime of this reference `'1`...6 |     (&mut slice[..mid],&mut slice[mid..])  |     ------------------------^^^^^--------  |     |     |                 |  |     |     |                 second mutable borrow occurs here  |     |     first mutable borrow occurs here  |     returning this value requires that `*slice` is borrowed for `'1`For more information about this error, try `rustc --explain E0499`.

Using unsafe code

12345678910111213141516171819
use std::slice;fn split_at_mut(slice: &mut [i32],mid: usize)->(&mut [i32],&mut [i32]){    let len = slice.len();    let ptr = slice.as_mut_ptr();    assert!(mid<=len);    unsafe{        (            slice::from_raw_parts_mut(ptr, mid),            slice::from_raw_parts_mut(ptr.add(mid), len-mid)        )    }}fn main(){    let mut v= vec![1,2,3,4,5,6];    let r = &mut v[..];    let (a,b) = r.split_at_mut(3);    assert_eq!(a,&mut [1,2,3]);    assert_eq!(b,&mut [4,5,6]);}

Using extern functions to call external code

  • extern keyword: simplifies the process of creating and using Foreign Function Interfaces (FFI).
  • Foreign Function Interface (FFI): it allows one programming language to define functions that other programming languages can call.
  • Functions declared in an extern block are always unsafe to call from Rust code. Because other languages do not enforce Rust’s rules and Rust cannot check them, ensuring safety is the programmer’s responsibility.
12345678
extern "C"{//"C" specifies the application binary interface (ABI) used by the external function.    fn abs(input: i32) ->i32;//The signature of the external function you want to call.}fn main(){    unsafe{        println!("Absolute value of -3 according to C:{}",abs(-3));    }}
  • Application Binary Interface (ABI): defines how functions are called at the assembly level.
  • The “C” ABI is the most common ABI; it follows the C language’s ABI.

Calling Rust functions from other languages

  • You can use extern to create interfaces through which other languages can call Rust functions.
  • Before Add the extern keyword before fn and specify the ABI.
  • Also need toAdd the #[no_mangle] attribute: prevents Rust from changing its name at compile time.
12345678
#[no_mangle]pub extern "C" fn call_from_c(){    println!("Just called a Rust function from C!");//After compilation and linking, it can be accessed by C; using extern does not require unsafe.}fn main(){}

Accessing or modifying a mutable static variable

  • Rust supports global variables, but the ownership mechanism can cause certain problems, such as data races.
  • In Rust, global variables are called static variables.
12345
static HELLO_WORLD: &str = "Hello, world!";fn main() {    println!("name is: {}", HELLO_WORLD);}
  • Static variables are similar to constants.
  • Usually static variable names use SCREAMING_SNAKE_CASE notation.
  • Static variables can only store references with the 'static lifetime, which means the Rust compiler can figure out their lifetime on its own without needing explicit annotations.
  • AccessImmutable static variablesare safe.

Differences between static variables and constants:

  • The value in a static variablehas a fixed memory addressUsing this value will always access the same address. Constants, however, allow their data to becopied whenever they are used.
  • Static variables can be mutable.Accessing and modifying mutable static variables is unsafe.
123456789101112131415
static mut COUNTER: u32 = 0;fn add_to_count(inc: u32) {    unsafe {        COUNTER += inc;    }}fn main() {    add_to_count(3);    unsafe {        println!("COUNTER: {}", COUNTER);    }}

Any code that reads or writes COUNTER must be within an unsafe block. This code compiles and prints COUNTER: 3 as expected, because it is single-threaded.Having multiple threads access COUNTER could lead to data races.

Having mutable data that is globally accessible makes it difficult to guarantee the absence of data races, which is why Rust considers mutable static variables unsafe. Whenever possible, prefer smart pointers, so that the compiler can detect whether data access between different threads is safe.

Implementing unsafe traits

  • A trait is unsafe when at least one of its methods contains an invariant that the compiler cannot verify.
  • You can declare a trait as unsafe by adding the unsafe keyword before the trait, and implementations of the trait must also be marked as unsafe.
123456789
unsafe trait Foo {    // methods go here}unsafe impl Foo for i32 {    // method implementations go here}fn main() {}

The Sync and Send marker traits are automatically implemented by the compiler for types composed entirely of Send and Sync types. If you implement a type that contains some types that are not Send or Sync, such as raw pointers, and you want to mark this type as Send or Sync, you must use unsafe. Rust cannot verify that our type guarantees it can be safely sent across threads or accessed between threads, so we need to check it ourselves and indicate it with unsafe.

Accessing fields of a union

The last operation that is only applicable to unsafe is accessing union fields. union is similar to struct, but only one declared field can be used at a time in an instance. Unions are mainly used to interact with unions in C code. Accessing union fields is unsafe because **Rust cannot guarantee the type of data currently stored in the union instance.**For more information about unions, see (https://doc.rust-lang.org/reference/items/unions.html).

When to Use Unsafe Code

Using unsafe to perform one of these five operations (superpowers) is fine, and doesn’t even require much deliberation, but making unsafe code correct is not easy because the compiler cannot help guarantee memory safety. When there is a reason to use unsafe code, it is acceptable to do so, and using explicit unsafe annotations makes it easier to trace the source of problems when errors occur.

Advanced Traits

Using Associated Types to Specify Placeholder Types in Trait Definitions

  • An associated type is a type placeholder in a trait that can be used in the trait’s method signatures:

You can define a trait that includes certain types without needing to know what those types are before implementing it.

1234567
pub trait Iterator {    type Item;    fn next(&mut self)->Option<Self::Item>;}fn main(){    println!("Hello World");}

Differences Between Associated Types and Generics

GenericsAssociated Types
Annotate the type each time you implement the traitNo need to annotate types
Can implement a trait multiple times for a type (with different generic parameters)Cannot implement a trait multiple times for a single type
1234567891011121314151617181920212223242526272829303132333435363738
pub trait Iterator {    type Item;    fn next(&mut self) -> Option<Self::Item>;}pub trait Iterator2<T>{    fn next(&mut self) -> Option<T>;}struct Counter{}impl Iterator for Counter{//Can only be implemented once    type Item=u32;    fn next(&mut self) -> Option<Self::Item> {        None    }}// impl Iterator for Counter { // can only be implemented once; implementing it for String a second time will cause an error//     type Item=String;//     fn next(&mut self) -> Option<Self::Item> {//         None//     }// }impl Iterator2<String> for Counter {    fn next(&mut self) -> Option<String> {        None    }}impl Iterator2<u32> for Counter {//Can be implemented multiple times for different types    fn next(&mut self) -> Option<u32> {        None    }}fn main(){}

Associated types are mainly used to improve code readability., for example the following code:

1234
pub trait CacheableItem: Clone + Default + fmt::Debug + Decodable + Encodable {  type Address: AsRef<[u8]> + Clone + fmt::Debug + Eq + Hash;  fn is_null(&self) -> bool;}

Compared to AsRef<[u8]> + Clone + fmt::Debug + Eq + Hash, using Address can greatly reduce the boilerplate code required for other types to implement this trait.

Example: Using Associated Types:

123456789101112131415161718192021222324252627282930313233343536373839404142
struct Container(i32, i32);// Re-implement the following traits using associated types.// trait Contains {//    type A;//    type B;trait Contains<A, B> {    fn contains(&self, _: &A, _: &B) -> bool;    fn first(&self) -> i32;    fn last(&self) -> i32;}impl Contains<i32, i32> for Container {    fn contains(&self, number_1: &i32, number_2: &i32) -> bool {        (&self.0 == number_1) && (&self.1 == number_2)    }    // Grab the first number.    fn first(&self) -> i32 { self.0 }    // Grab the last number.    fn last(&self) -> i32 { self.1 }}fn difference<A, B, C: Contains<A, B>>(container: &C) -> i32 {    container.last() - container.first()}fn main() {    let number_1 = 3;    let number_2 = 10;    let container = Container(number_1, number_2);    println!("Does container contain {} and {}: {}",        &number_1, &number_2,        container.contains(&number_1, &number_2));    println!("First number: {}", container.first());    println!("Last number: {}", container.last());    println!("The difference is: {}", difference(&container));}

Implementation

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
struct Container(i32, i32);// A trait which checks if 2 items are stored inside of container.// Also retrieves first or last value.trait Contains {    // Define generic types here which methods will be able to utilize.    type A;    type B;    fn contains(&self, _: &Self::A, _: &Self::B) -> bool;    fn first(&self) -> i32;    fn last(&self) -> i32;}impl Contains for Container {    // Specify what types `A` and `B` are. If the `input` type    // is `Container(i32, i32)`, the `output` types are determined    // as `i32` and `i32`.    type A = i32;    type B = i32;    // `&Self::A` and `&Self::B` are also valid here.    fn contains(&self, number_1: &i32, number_2: &i32) -> bool {        (&self.0 == number_1) && (&self.1 == number_2)    }    // Grab the first number.    fn first(&self) -> i32 { self.0 }    // Grab the last number.    fn last(&self) -> i32 { self.1 }}fn difference<C: Contains>(container: &C) -> i32 {    container.last() - container.first()}fn main() {    let number_1 = 3;    let number_2 = 10;    let container = Container(number_1, number_2);    println!("Does container contain {} and {}: {}",        &number_1, &number_2,        container.contains(&number_1, &number_2));    println!("First number: {}", container.first());    println!("Last number: {}", container.last());    println!("The difference is: {}", difference(&container));}

Default Generic Parameters and Operator Overloading

  • You can specify a default concrete type for a generic parameter when using generics.
  • Syntax: <PlaceholderType=ConcreteType>
  • This technique is commonly used for operator overloading.
  • Rust does not allow creating your own operators or overloading arbitrary operators.
  • ButYou can overload some corresponding operators by implementing the traits listed in std::ops.
12345678910111213141516171819202122
use std::ops::Add;#[derive(Debug, PartialEq)]struct Point {    x: i32,    y: i32,}impl Add for Point {    type Output = Point;    fn add(self, other: Point) -> Point {        Point {            x: self.x + other.x,            y: self.y + other.y,        }    }}fn main() {    assert_eq!(        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },        Point { x: 3, y: 3 }    );}

Here we useAddthe default generic parameter of the traitRhs = Self(i.e., the right operand type defaults to Self)

123456789101112
use std::ops::Add;struct Millimeters(u32);struct Meter(u32);impl Add<Meter> for Millimeters {    type Output = Millimeters;    fn add(self, rhs: Meter) -> Self::Output {        Millimeters(self.0+(rhs.0*1000))    }}fn main(){}

Here the generic parameter is specified.

Main use cases of default generic parameters

  • Extending a type without breaking existing code.
  • Allowing customization in specific scenarios that most users do not need.

Fully Qualified Syntax

1234567891011121314151617181920212223242526272829
trait Pilot {    fn fly(&self);}trait Wizard {    fn fly(&self);}struct Human;impl Pilot for Human {    fn fly(&self) {        println!("This is your captain speaking");    }}impl Wizard for Human {    fn fly(&self) {        println!("Up!");    }}impl Human {    fn fly(&self){        println!("*waving arms furiously*");    }}fn main(){    let person = Human;    person.fly();//Call the method on itself.    Pilot::fly(&person);//Call the method from the Pilot trait.    Wizard::fly(&person);//Call the method from the Wizard trait.}

The parameterless form.

1234567891011121314151617181920
trait Animal {    fn baby_name()->String;}struct Dog;impl Dog {    fn baby_name()->String{        String::from("Spot")    }}impl Animal for Dog {    fn baby_name()->String {        String::from("puppy")    }}fn main(){    println!("A baby dog is called a{}",Dog::baby_name());    println!("A baby dog is called a{}",Animal::baby_name());//Error}

Here baby_name has no parameters, so the compiler doesn’t know which Dog is calling.

  • Fully qualified syntax:::function(receiver_if_method,netx_arg,…) ;
  • Can be used anywhere you call a function or method.
  • Allows ignoring parts that can be inferred from other context.
  • This syntax is only needed when Rust cannot distinguish which specific implementation you expect to call.
1234567891011121314151617181920
trait Animal {    fn baby_name()->String;}struct Dog;impl Dog {    fn baby_name()->String{        String::from("Spot")    }}impl Animal for Dog {    fn baby_name()->String {        String::from("puppy")    }}fn main(){    println!("A baby dog is called a{}",Dog::baby_name());    println!("A baby dog is called a{}",<Dog as Animal>::baby_name());}

Use supertraits to require a trait to include the functionality of other traits.

  • Need to use the functionality of other traits within a trait.
    • The dependent trait also needs to be implemented.
    • The trait that is indirectly depended upon is the supertrait of the current trait.
123456789101112131415161718192021222324252627
use std::fmt::{self, write};trait OutlinePrint: fmt::Display{    fn outline_print(&self){        let output = self.to_string();        let len = output.len();        println!("{}","*".repeat(len+4));        println!("*{}*"," ".repeat(len+2));        println!("* {} *",output);        println!("*{}*"," ".repeat(len+2));        println!("{}","*".repeat(len+4));    }}struct Point{    x:i32,    y:i32,}impl OutlinePrint for Point {}impl fmt::Display for Point {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        write!(f, "({},{})",self.x,self.y)    }}fn main(){}

Using the newtype pattern to implement external traits on external types.

  • Orphan rule: you can implement a trait for a type only if the trait or the type is defined in the local crate.
  • This rule can be bypassed using the newtype pattern.
    • Create a new type using a tuple struct.

(example)

123456789101112
use std::fmt;struct Wrapper(Vec<String>);impl fmt::Display for Wrapper{    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        write!(f,"[{}]",self.0.join(", "))    }}fn main(){    let w = Wrapper(vec![String::from("hello"),String::from("world")]);    println!("w={}",w);}

Advanced Types

Using the newtype pattern for type safety and abstraction.

  • The newtype pattern can:
    • Statically ensure that values are not confused with one another and indicate the units of a value.
    • Provide abstraction for certain details of a type.
    • Hide internal implementation details through a lightweight wrapper.

Using type aliases to create type synonyms.

  • Rust provides type aliases: — create another name (synonym) for an existing type — not an independent type — use thetypekeyword.
  • Main purpose: reduce code repetition.
  • Similar to C’s typedef.
123456789
fn takes_long_type(f: Box<dyn Fn()+Send+'static>){    //snip}fn returns_long_type()->Box<dyn Fn()+Send+'static>{    Box::new(|| println!("hi"))}fn main(){    let f:Box<dyn Fn()+Send+'static> = Box::new(|| println!("hi"));}

Using type aliases

12345678910111213141516171819202122
type Thunk = Box<dyn Fn()+Send+'static>;fn takes_long_type(f: Thunk){    //snip}fn returns_long_type()->Thunk{    Box::new(|| println!("hi"))}fn main(){    let f:Thunk = Box::new(|| println!("hi"));}use std::io::Error;use std::fmt;pub trait Write {    fn write(&mut self,buf: &[u8])->Result<usize,Error>;    fn flush(&mut self)->Result<(),Error>;    fn write_all(&mut self,buf: &[u8])->Result<(),Error>;    fn write_fmt(&mut self,fmt: fmt::Arguments)->Result<(),Error>;}fn main(){}

Using type aliases

12345678910111213
use std::io::Error;use std::fmt;// type Result<T> = Result<T, std::io::Error>; the standard library defines this.type Result<T> = std::io::Result<T>;pub trait Write {    fn write(&mut self,buf: &[u8])->Result<usize>;    fn flush(&mut self)->Result<()>;    fn write_all(&mut self,buf: &[u8])->Result<()>;    fn write_fmt(&mut self,fmt: fmt::Arguments)->Result<()>;}fn main(){}

The never type

  • There is a special type named!:

    • It has no values, and is referred to as the empty type in jargon.
    • We tend to call it the never type because it serves as the return type for functions that never return.
  • Functions that never return are also called diverging functions.

123456789101112131415
fn bar() -> !{    //`return ()` returns the unit type, but it is impossible to create a function that returns the `!` type.}fn main(){}fn main(){    let guess = "";    loop{        let guess:u32 = match guess.trim().parse() {            Ok(num) => num,            Err(_) => continue,        };    }}

The match expression requires all branches to return the same type, and continue returns the never type, which can be safely coerced to the type corresponding to num.

12345678
impl<T> Option<T>{    pub fn unwrap(self) -> T{        match self{            Some(val) => val,            None=>panic!("called `Option::unwrap()` on a `None` value"),        }    }}

panic! returns the never type.

Dynamically Sized Types and the Sized Trait

  • Rust needs to determine at compile time how much space to allocate for a value of a particular type.

  • The concept of dynamically sized types (DST):

    • Writing code that uses values whose size can only be determined at runtime.
  • str is a dynamically sized type.(Note: not &str): the length of a string can only be determined at runtime.
    The following code does not work:

12
let s1:str = "Hello there";let s2:str = "How is it going";

Because they are all the same type, the space required should be the same, but the common space is not determined at declaration time.

Solution: use the &str string slice type.

Rust’s general way of using dynamically sized types.

  • Attach some extra metadata to store the size of the dynamic information.
    • When using a dynamically sized type, its value is always placed behind some kind of pointer.

Another dynamically sized type: trait.

  • each **Every trait is a dynamically sized type.**It can be referenced by name.
  • To use a trait as a trait object, it must be placed behind some kind of pointer.
    • For example, &dyn Trait or Box (Rcafter )

Sized trait

  • To handle dynamically sized types, Rust provides a Sized trait to determine whether a type’s size is known at compile time—Types whose size can be computed at compile time automatically implement this trait.
  • Rust also implicitly adds a Sized constraint to every generic function.
12345678
fn generic<T>(t:T){}fn generic<T: Sized>(t:T){}fn main(){}
  • By default, generic functions can only be used with types whose size is known at compile time; this restriction can be lifted with special syntax.

?Sized trait constraint

123
fn generic<T: ?Sized>(t:&T){}
  • T may or may not be Sized
  • This syntax can only be used on Sized, not on other traits

Advanced Functions and Closures

Function pointer

  • You can pass functions to other functions
  • Functions are coerced to the fn type when passed
  • The fn type is function pointer
12345678910
fn add_one(x:i32)->i32{    x+1}fn do_twice(f: fn(i32)->i32,arg:i32)->i32{    f(arg) + f(arg)}fn main(){    let answer = do_twice(add_one, 5);    println!("The answer is:{}",answer);}

Differences between function pointers and closures

  • fn is a type, not a trait

    • You can directly specify fn as a parameter type, without declaring a generic parameter constrained by the Fn trait
  • Function pointers implement all three closure traits (Fn, FnMut, FnOnce):

    • You can always pass a function pointer as an argument to a parameter that accepts a closure
    • Therefore, prefer writing functions with generics that use closure traits: this accepts both closures and regular functions
  • In some situations, you only want to accept fn and not closures

    • Interacting with external code that doesn’t support closures: C functions
123456789101112131415161718192021
fn main(){    let list_of_numbers = vec![1,2,3];    let list_of_strings:Vec<String> = list_of_numbers    .iter()    .map(|i| i.to_string())    .collect();    let list_of_numbers = vec![1,2,3];    let list_of_strings: Vec<String> = list_of_numbers    .iter()    .map(ToString::to_string)    .collect();}fn main(){    enum Status{        Value(u32),        Stop,    }    let v = Status::Value(3);    let list_of_statuses:Vec<Status> = (0u32..20).map(Status::Value).collect();}

Returning Closures

  • Closures are expressed using traits, so you cannot return a closure directly from a function. You can return a concrete type that implements the trait
1234567891011
// fn returns_closure()->Fn(i32)->i32{// return type size is not fixed//     |x| x+1// }fn returns_closure() -> Box<dyn Fn(i32)->i32>{    Box::new(|x| x+1)}fn main(){}

Macro

Reference:

Official documentation:

Rust Language Bible

The Little Book of Rust Macros

Asynchronous Programming

Reference:

Asynchronous Programming in Rust

Rust Language Bible

Formatting output

Positional argument

123456
fn main() {    println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");// => Alice, this is Bob. Bob, this is Alice    assert_eq!(format!("{1}{0}", 1, 2), "21");    assert_eq!(format!("{1}{}{0}{}", 1, 2), "2112");    println!("Success!")}

Named argument

1234567891011
fn main() {    println!("{argument}", argument = "test"); // => "test"    assert_eq!(format!("{name}{}", 1, name = 2), "21");    assert_eq!(format!("{a} {c} {b}",a = "a", b = 'b', c = 3 ), "a 3 b");    // named argument must be placed after other arguments    println!("{abc} {0}", 2, abc = "def");    println!("Success!")}

String alignment

By default, strings are padded with spaces.

12345678910
fn main() {    // the following two are padding with 5 spaces    println!("Hello {:5}!", "x"); // =>  "Hello x    !"    println!("Hello {:1$}!", "x", 5); // =>  "Hello x    !"    assert_eq!(format!("Hello {1:0$}!", 5, "x"), "Hello x    !");    assert_eq!(format!("Hello {:width$}!", "x", width = 5), "Hello x    !");    println!("Success!")}

Left-aligned, right-aligned, and filled with a specified character.

12345678910111213
fn main() {    // left align    println!("Hello {:<5}!", "x"); // => Hello x    !    // right align    assert_eq!(format!("Hello {:>5}!", "x"), "Hello     x!");    // center align    assert_eq!(format!("Hello {:^5}!", "x"), "Hello   x  !");    // left align, pad with '&'    assert_eq!(format!("Hello {:&<5}!", "x"), "Hello x&&&&!");    println!("Success!")}

We can also use 0 to pad numbers.

12345678910
fn main() {    println!("Hello {:5}!", 5); // => Hello     5!    println!("Hello {:+}!", 5); // =>  Hello +5!    println!("Hello {:05}!", 5); // => Hello 00005!    println!("Hello {:05}!", -5); // => Hello -0005!    assert!(format!("{number:0>width$}", number=1, width=6) == "000001");    println!("Success!")}

Precision

Floating-point precision

1234567891011
fn main() {    let v = 3.1415926;    println!("{:.1$}", v, 4); // same as {:.4} => 3.1416    assert_eq!(format!("{:.2}", v), "3.14");    assert_eq!(format!("{:+.2}", v), "+3.14");    assert_eq!(format!("{:.0}", v), "3");    println!("Success!")}

String length

123456789
fn main() {    let s = "Hello, world!";    println!("{0:.5}", s); // => Hello    assert_eq!(format!("Hello {1:.0$}!", 3, "abcdefg"), "Hello abc!");    println!("Success!")}

Binary, octal, hexadecimal

123456789101112
fn main() {    assert_eq!(format!("{:#b}", 27), "0b11011");    assert_eq!(format!("{:#o}", 27), "0o33");    assert_eq!(format!("{:#x}", 27), "0x1b");    assert_eq!(format!("{:#X}", 27), "0x1B");    println!("{:x}!", 27); // hex with no prefix => 1b    println!("{:#010b}", 27); // pad binary with 0, width = 10,  => 0b00011011   println!("Success!")}

Capture values from the environment

1234567891011121314151617181920212223
fn get_person() -> String {    String::from("sunface")}fn get_format() -> (usize, usize) {    (4, 1)}fn main() {    let person = get_person();    println!("Hello, {person}!");    let (width, precision) = get_format();    let scores = [("sunface", 99.12), ("jack", 60.34)];    /* Make it print:    sunface:   99.1    jack:   60.3    */    for (name, score) in scores {        println!("{name}: {score:width$.precision$}");    }}

Exponent, pointer address, escape

123456789101112
fn main() {    // index    println!("{:2e}", 1000000000); // => 1e9    println!("{:2E}", 1000000000); // => 1E9    // Pointer address    let v= vec![1, 2, 3];    println!("{:p}", v.as_ptr()); // => 0x600002324050    // Escape    println!("Hello {{}}"); // => Hello {}}

Other

Error handling:

unreachable!()

This is a standard macro that marks paths that the program should not enter. If the program enters these paths, the program will panic and return the error message “‘internal error: entered unreachable code’”.

123456789101112131415
fn main() {    let level = 22;    let stage = match level {        1..=5 => "beginner",        6..=10 => "intermediate",        11..=20 => "expert",        _ => unreachable!(),    };    println!("{}", stage);}// -------------- Compile time error --------------thread 'main' panicked at 'internal error: entered unreachable code', src/main.rs:7:20

We can also set a custom error message for this.

12345678910
// --- with a custom message ---_ => unreachable!("Custom message"),// -------------- Compile time error --------------thread 'main' panicked at 'internal error: entered unreachable code: Custom message', src/main.rs:7:20// --- with debug data ---_ => unreachable!("level is {}", level),// -------------- Compile time error --------------thread 'main' panicked at 'internal error: entered unreachable code: level is 22', src/main.rs:7:14

misconception corollaries

ifT: 'staticthenTmust be valid for the entire program

Misconception Corollaries

  • T: 'staticshould be read as Thas a'staticlifetime”
  • &'static TandT: 'staticare the same thing
  • ifT: 'staticthenTmust be immutable
  • ifT: 'staticthenTcan only be created at compile time

Most Rust beginners get introduced to the'staticlifetime for the first time in a code example that looks something like this:

123
fn main() {    let str_literal: &'static str = "str literal";}

They get told that"str literal"is hardcoded into the compiled binary and is loaded into read-only memory at run-time so it’s immutable and valid for the entire program and that’s what makes it'static. These concepts are further reinforced by the rules surrounding definingstaticvariables using thestatickeyword.

12345678910111213141516
// Note: This example is purely for illustrative purposes.// Never use `static mut`. It's a footgun. There are// safe patterns for global mutable singletons in Rust but// those are outside the scope of this article.static BYTES: [u8; 3] = [1, 2, 3];static mut MUT_BYTES: [u8; 3] = [1, 2, 3];fn main() {   MUT_BYTES[0] = 99; // ❌ - mutating static is unsafe    unsafe {        MUT_BYTES[0] = 99;        assert_eq!(99, MUT_BYTES[0]);    }}

Regardingstaticvariables

  • they can only be created at compile-time
  • they should be immutable, mutating them is unsafe
  • they’re valid for the entire program

The'staticlifetime was probably named after the default lifetime ofstaticvariables, right? So it makes sense that the'staticlifetime has to follow all the same rules, right?

Well yes, but a type with a'staticlifetime is different from a type bounded by a'staticlifetime. The latter can be dynamically allocated at run-time, can be safely and freely mutated, can be dropped, and can live for arbitrary durations.

It’s important at this point to distinguish&'static TfromT: 'static.

&'static Tis an immutable reference to someTthat can be safely held indefinitely long, including up until the end of the program. This is only possible ifTitself is immutable and does not move after the reference was created.Tdoes not need to be created at compile-time. It’s possible to generate random dynamically allocated data at run-time and return'staticreferences to it at the cost of leaking memory, e.g.

1234567
use rand;// generate random 'static str refs at run-timefn rand_str_generator() -> &'static str {    let rand_string = rand::random::<u64>().to_string();    Box::leak(rand_string.into_boxed_str())}

T: 'staticis someTthat can be safely held indefinitely long, including up until the end of the program.T: 'staticincludes all&'static Thowever it also includes all owned types, likeString,Vec, etc. The owner of some data is guaranteed that data will never get invalidated as long as the owner holds onto it, therefore the owner can safely hold onto the data indefinitely long, including up until the end of the program.T: 'staticshould be read as Tis bounded by a'staticlifetime” not Thas a'staticlifetime”. A program to help illustrate these concepts:

12345678910111213141516171819202122232425262728
use rand;fn drop_static<T: 'static>(t: T) {    std::mem::drop(t);}fn main() {    let mut strings: Vec<String> = Vec::new();    for _ in 0..10 {        if rand::random() {            // all the strings are randomly generated            // and dynamically allocated at run-time            let string = rand::random::<u64>().to_string();            strings.push(string);        }    }    // strings are owned types so they're bounded by 'static    for mut string in strings {        // all the strings are mutable        string.push_str("a mutation");        // all the strings are droppable        drop_static(string); // ✅    }    // all the strings have been invalidated before the end of the program    println!("I am the end of the program");}

Key Takeaways

  • T: 'staticshould be read as Tis bounded by a'staticlifetime”

  • ifT: 'staticthenTcan be a borrowed type with a'staticlifetime or an owned type

  • since

    1
    T: 'static

    includes owned types that means

    1
    T
    • can be dynamically allocated at run-time
    • does not have to be valid for the entire program
    • can be safely and freely mutated
    • can be dynamically dropped at run-time
    • can have lifetimes of different durations
Loading comments…