1. Installation and Debugging
    1. Install
    2. Debugging
  2. Cargo
    1. Creating a Project
    2. Building a Cargo project
    3. Building and running a Cargo project
    4. cargo check
    5. Release build
  3. Variables and Mutability
    1. variable
    2. Constant
    3. Shadowing
    4. Allowing unused variables
  • Data Type
    1. Scalar types
      1. Integer types
      2. Floating-point types
      3. Sequence
      4. Boolean type
      5. Character type
      6. Unit type
    2. Compound types
      1. Tuple type
      2. Array type
    3. Type Conversion
      1. Using as for Basic Type Conversion
      2. From/Into
        1. Implement the From trait for a custom type
      3. TryFrom / TryInto
      4. Other conversions
        1. Convert any type to String
        2. Parsing String
        3. Custom implementation of the FromStr trait
        4. transmute
    4. function
      1. Parameters
      2. Statements and Expressions
      3. Return value
        1. The return type is ()
        2. The return type is never
        3. Diverging function
    5. Control flow
      1. if expressions
      2. Using if in a let statement
      3. Loop
        1. loop
          1. Returning Values from Loops
          2. loop label
      4. while
      5. for
        1. Iterating over an array by index and value
  • Ownership, References, and Borrowing
    1. The Stack and the Heap
    2. Ownership Rules
      1. When ownership is transferred, mutability can also change accordingly.
    3. Variables and Scope
    4. str and &str
    5. String type
    6. Memory and Allocation
    7. Ways in which variables and data interact
      1. move
        1. Partial move
      2. clone
      3. copy
    8. Ownership and Functions
      1. Return Values and Scope
    9. References and Borrowing
      1. Rust will automatically dereference in certain cases.
      2. mutable reference
      3. Dangling References
      4. ref
      5. Summary of reference rules (borrowing rules).
      6. None Lexical Lifetimes(NLL)
    10. Slice Type
      1. String slice
      2. String literals are slices.
      3. String Slices as Parameters
      4. slices of other types
  • Struct
    1. Defining a struct
    2. Instantiation
    3. Access
    4. Field Init Shorthand
    5. Struct Update Syntax
    6. Tuple Structs
    7. Unit-like structs with no fields
    8. Ownership in structs
    9. print struct
    10. struct methods
    11. Method invocation operator
    12. correlation function
    13. example
  • Enumeration
    1. Defining an enum
    2. Enum value
    3. Attaching data to the enum variant
    4. Defining methods on enums
    5. The Option Enum
    6. Implementing a Linked List with Enums
  • pattern matching
    1. match control flow construct
      1. matches!
      2. Patterns that bind values
      3. Matching Option
      4. match must be exhaustive
    2. if let concise control flow
    3. Variable shadowing in pattern matching
  • Workspace, Package,Crate,Module
    1. Cargo’s conventions
    2. Define modules to control scope and privacy.
    3. path
    4. Privacy boundary
    5. super
    6. pub struct
    7. pub enum
    8. the use keyword
    9. Idiomatic usage of use
    10. Use the as keyword to provide a new name
    11. pub use re-export
    12. pub(in Crate)
    13. Using external packages
    14. Use nested paths to clean up a large number of use statements.
    15. Wildcard *
    16. Split modules into different files
  • Common collections
    1. Vector
      1. Creating a vector
      2. Adding elements
      3. Dropping a Vector
      4. Reading values from a Vector
      5. Reference borrowing rules
      6. Iterating over a Vector
      7. Extending a Vector
      8. Using an enum to make Vec store multiple data types
      9. Using trait objects to make Vec store multiple data types
      10. Convert type X into a Vec (using the From/Into traits)
      11. Slices
      12. Capacity
    2. String
      1. What is a string?
      2. Other string types
      3. Conversion between String and &str
      4. String escaping
      5. Create a new String
      6. Updating a String
        1. push_str()
        2. push()
        3. Concatenating strings with +
        4. format!
      7. Accessing by indexString
      8. Byte strings
      9. Bytes, scalar values, grapheme clusters
      10. Slicing Strings
      11. Iterating over Strings
    3. HashMap
      1. Creating HashMap<K,V>
      2. HashMap and Ownership
      3. Accessing values in a HashMap
      4. Updating a HashMap
        1. Overwriting a value
        2. Inserting only if the key has no value
        3. Updating a value based on the old value
        4. Hash function
      5. HashMap key restrictions
      6. Capacity
      7. Ownership
      8. Third-party Hash libraries
  • Error handling
    1. Unrecoverable errors and panic!
      1. panic = ‘abort’
      2. Locate the code causing the problem through the backtrace information of the function that called panic!
    2. Result enum and recoverable errors
      1. Matching different errors
      2. unwrap
      3. expect
    3. Propagating errors
    4. The ? operator
    5. ? and the from function
    6. ? and the main function
    7. map,and_then
    8. When to panic!
      1. General Principles
      2. Scenario
      3. Creating custom types for validation
    9. Using Result in fn main
  • Generics
    1. Defining generics in functions
    2. Defining generics in structs
    3. Defining generics in enums
    4. Generics in method definitions
    5. Const generics
    6. Performance of generic code
  • Trait: Defining Shared Behavior
    1. Defining a Trait
    2. Implementing a Trait on a Type
    3. Constraints for Implementing a Trait
    4. Default implementations of functions in a trait
    5. Trait as a parameter
      1. impl Trait syntax:
      2. trait bound syntax:
      3. Use + to specify multiple trait bounds
      4. Use where to simplify trait bounds
    6. Returning a type that implements a trait
    7. Trait objects, using trait objects in arrays
    8. &dyn and Box
    9. Static and Dynamic dispatch
    10. Use trait bounds to fix the largest function
    11. Using trait bounds to conditionally implement methods
    12. Derive macro derived implementations
  • Lifecycle
    1. borrow checker
    2. Generic Lifetimes in Functions
    3. Lifetime annotation syntax
    4. Understanding Lifetimes in Depth
    5. Lifetime Annotations in Struct Definitions
    6. Lifetime Elision
    7. Lifetime annotations in method definitions
    8. Static lifetime
    9. Combining generic type parameters, trait bounds, and lifetimes
  • Writing Automated Tests
    1. The Anatomy of a Test Function
      1. Writing test functions
      2. Running tests
      3. Test failure
    2. Assertions
      1. Use the assert! macro to check test results.
      2. Use assert_eq! and assert_ne! macro to test equality
      3. Custom error message
    3. Verify error handling situations
      1. Making should_panic more precise
    4. Using Result<T, E> in tests
    5. Controlling test execution
      1. Running tests in parallel
        1. –test-threads argument
      2. Showing function output
      3. Running a subset of tests by name
        1. Running a single test
        2. Filtering to Run Multiple Tests
        3. Ignoring Some Tests
    6. Test Organization
      1. Unit Tests
        1. Testing Private Functions
      2. Integration Tests
        1. The tests Directory
        2. Running a Specific Integration Test
        3. Submodule in Integration Testing
        4. Integration Tests for Binary Crates
  • I/O Project: Building a Command Line Program
    1. Accepting Command Line Arguments
    2. Separation of Concerns for Binary Projects
    3. TDD (Test-Driven Development)
    4. Writing minigrep code
  • Cover image for rust language basics

    rust language basics

    Words 35.2k
    Views
    Visitors

    Timeline

    Timeline

    2025-09-11

    init

    2025-10-19

    modify some format errors

    2025-10-22

    move something to another post

    This article introduces the basics of the Rust language, covering installation and debugging, the use of the Cargo project management tool, as well as core concepts such as variables and mutability, constants, and shadowing. The article details Rust's scalar data types, including integers, floating-point numbers, booleans, and characters, and discusses integer overflow and how to handle it. Through this article, readers can grasp the essential syntax and type system points of Rust.

    Installation and Debugging

    Install

    On Linux

    123
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh# Follow the prompts to installrustc --version

    Debugging

    Debugging with rust-gdb on Linux

    1
    rust-gdb target/debug/your_program

    Cargo

    Creating a Project

    cargo new project_name

    Help information

    12345678910111213141516171819202122232425
    Create a new cargo package at <path>Usage: cargo.exe new [OPTIONS] <path>Arguments:  <path>Options:  -q, --quiet                Do not print cargo log messages      --registry <REGISTRY>  Registry to use      --vcs <VCS>            Initialize a new repository for the given version control system (git, hg, pijul, or fossil) or do not initialize any version control at all (none), overriding a global configuration. [possible values: git, hg, pijul, fossil, none]      --bin                  Use a binary (application) template [default]  -v, --verbose...           Use verbose output (-vv very verbose/build.rs output)      --lib                  Use a library template      --color <WHEN>         Coloring: auto, always, never      --edition <YEAR>       Edition to set for the crate generated [possible values: 2015, 2018, 2021]      --frozen               Require Cargo.lock and cache are up to date      --name <NAME>          Set the resulting package name, defaults to the directory name      --locked               Require Cargo.lock is up to date      --offline              Run without accessing the network      --config <KEY=VALUE>   Override a configuration value  -Z <FLAG>                  Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details  -h, --help                 Print help informationRun `cargo help new` for more detailed information.

    Cargo.toml

    TOML (Tom’s Obvious, Minimal Language) format, which is Cargo’s configuration format.

    123456789
    [package]#Section heading, indicating that the following is used to configure the package.name = "hello" #Project nameversion = "0.1.0" #Project versionauthors = ["cauchy <731005515@qq.com>"] #Authoredition = "2021" #The Rust version used# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]#Dependencies

    In Rust, a package of code is calledcrate

    Building a Cargo project

    1
    cargo build

    Creates the executable target/debug/hello_cargo or target\debug\hello_cargo.exe(Windows)

    Run .\target\debug\hello_cargo.exe

    The first run generates the cargo.lock file

    This file is responsible for tracking the exact versions of project dependencies; there is no need to manually modify this file.

    Building and running a Cargo project

    1
    cargo run

    If it has been compiled before and the code has not been modified, it will execute directly.

    cargo check

    1
    cargo check

    Checks the code to ensure it compiles, but does not produce any executable file.

    cargo check is much faster than cargo build

    Release build

    1
    cargo build --release

    It optimizes during compilation, so the code runs faster but compilation takes longer.

    It generates the executable in target/release instead of target/debug.

    Variables and Mutability

    variable

    Declaring and Usingletkeyword

    By default, variables are immutable.

    When declaring a variable, prefix it withmutthe keyword, and the variable becomes mutable.

    let mut x = 3;

    Constant

    Similar to immutable variables,Constants are values bound to a name that are not allowed to change, but there are still some differences between constants and variables.

    1. You cannot use mut with constants. Constants are not just immutable by default; they are always immutable.

    2. Declare constants with the const keyword instead of let, and mustannotate the type of the value.

    3.Constants can be declared in any scope, including the global scope,

    1. The last difference is that constants can only be set toa constant expression, andand cannot be any other value that can only be computed at runtime.
    12
    const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;const MAX_POINTS: u32 = 100_000;

    Naming convention: all uppercase, separated by underscores.

    Shadowing

    123456789101112
    fn main() {    let x = 5;    let x = x + 1;    {        let x = x * 2;        println!("The value of x in the inner scope is: {x}");    }    println!("The value of x is: {x}");}

    We can define a new variable with the same name as a previous variable,A new variable shadows a previously declared variable with the same name.

    Shadowing and marking a variable as mutare different.

    • If you don’t use the let keyword, assigning to a non-mut variable causes a compile-time error.
    • A new variable with the same name declared using let is also immutable.
    • A new variable with the same name declared using let,its type can be different from before.

    Allowing unused variables

    Two ways

    1234567
    fn main() {    let _x = 1;}#[allow(unused_variables)]fn main() {    let x = 1;}

    Data Type

    Rust isa statically typed languagethe types of all variables must be known at compile time.

    Scalar types

    Rust has four basic scalar types:Integer typesFloating-point typesBoolean typeandCharacter type

    Integer types

    If we don’t explicitly give a variable a type, the compiler will automatically infer one for us.

    12345678
    fn main(){    let x = 5;    assert_eq!("i32".to_string(),type_of(&x));}// The following function can get the type of the passed-in parameter and return the type as a string.fn type_of<T>(_: &T) -> String{    format!("{}",std::any::type_name::<T>())}

    If an integer is not given a type, it defaults to i32.

    123
    fn main() {    let v: u16 = 38_u8 as u16;}
    LengthsignedUnsigned
    8-biti8u8
    16-biti16u16
    32-biti32u32
    64-biti64u64
    128-biti128u128
    archisizeusize

    The isize and usize types depend on the computer architecture on which the program is running: on 64-bit architectures they are 64-bit, and on 32-bit architectures they are 32-bit.

    1234
    fn main() {    assert_eq!(i8::MAX, 127);    assert_eq!(u8::MAX, 255);}

    Integer literals

    Numeric literalsExample
    Decimal98_222
    Hex0xff
    Octal0o77
    Binary0b1111_0000
    Byte (single-byte character) (u8 only)b’A’

    The default numeric type in Rust is i32. isize or usize are mainly used as indices for some collections.

    Integer overflow

    For example, there is a u8, which can store values from 0 to 255. So what happens when you change it to 256? This is called “integer overflow” (integer overflow ), which leads to one of the following two behaviors. WhenWhen compiling in debug mode, Rust checks for such problems and causes the program to panic, a term Rust uses to indicate that the program exits due to an error.

    **In release builds, Rust does not detect overflow; instead, it performs an operation known as two’s complement wrapping.**In short, values larger than the maximum value this type can hold wrap around to the minimum value, so 256 becomes 0, 257 becomes 1, and so on. Relying on integer wrapping is considered an error, even though this behavior may occur. If you really need this behavior, the standard library has a type that explicitly provides this functionality, Wrapping. To explicitly handle the possibility of overflow, you can use the following methods provided by the standard library on native numeric types:

    • Available in all modes wrapping_ methods for wrapping*, such as wrapping_add
    • if checked_ methods*If overflow occurs, returns None
    • Use overflowing_ methods*Returns a value and a boolean indicating whether overflow occurred
    • Use saturating_ methods*Saturates at the minimum or maximum value
    123456
    // Handling errors in code and `panic`fn main() {   let v1 = 251_u8 + 8;   let v2 = i8::checked_add(251, 8).unwrap();   println!("{},{}",v1,v2);}

    Modify

    1234567891011
    fn main() {    let v1 = 247_u8 + 8;    let v2 = i8::checked_add(119, 8).unwrap();    println!("{},{}",v1,v2); }#[allow(unused_variables)]fn main() {    let v1 = 251_u16 + 8;    let v2 = u16::checked_add(251, 8).unwrap();    println!("{},{}",v1,v2); }

    Floating-point types

    Rust’s floating-point types are f32 and f64, which take up 32 bits and 64 bits respectively. The default type is f64 because on modern CPUs it is almost the same speed as f32, but with higher precision. All floating-point types are signed.

    12345
    fn main() {    let x = 2.0; // f64    let y: f32 = 3.0; // f32}

    Floating-point numbers are represented using the IEEE-754 standard. f32 is a single-precision floating-point number, and f64 is a double-precision floating-point number.

    12345
    fn main() {    let x = 1_000.000_1; // f64    let y: f32 = 0.12; // f32    let z = 0.01_f64; // f64}

    Numeric operations

    All numeric types in Rust support basic mathematical operations: addition, subtraction, multiplication, division, and remainder. Integer division willtruncate toward zero(discarding the fractional part)

    123456
    fn main() {    assert_eq!(0.1+0.2,0.3);//Error }thread 'main' panicked at 'assertion failed: `(left == right)`  left: `0.30000000000000004`, right: `0.3`', src\main.rs:5:5

    Two ways to modify

    123456
    fn main() {    assert!(0.1+0.2>=0.3);}fn main() {    assert!(0.1_f32+0.2_f32==0.3_f32);}

    Calculate

    12345678910111213141516171819202122232425262728293031323334
    use std::fmt::Display;#[allow(unused_variables)]fn print_something<T >(something:T)where T : Display{    println!("{}",something);}fn main() {    // Integer addition    print_something(1u32 + 2  );    // Integer subtraction    print_something(1i32 - 2  );    print_something(1i8 - 2);    print_something(3 * 50  );    print_something(9 / 3 == 3); // error! Fix it to make the code work    print_something(24 % 5  );    // Logical AND, OR, NOT operations    print_something(true && false  );    print_something(true || false  );    print_something(!true  );    // Bit manipulation    println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101);    println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101);    println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101);    println!("1 << 5 is {}", 1u32 << 5);    println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2);}

    Sequence

    1234567891011121314151617
    fn main() {    let mut sum = 0;    for i in -3..2 {        println!("i is {}",i);//-3 to 1, not including 2    }    for c in 'a'..='z' {        println!("{}",c);//a-z, including z    }}#[allow(unused_variables)]// Handling errors in code and `panic`use std::ops::{Range, RangeInclusive};fn main() {    assert_eq!((1..5), Range{ start: 1, end: 5 });    assert_eq!((1..=5), RangeInclusive::new(1, 5));}

    Boolean type

    The boolean type in Rust is represented by bool

    123456789101112
    fn main() {    let t = true;    let f: bool = false; // with explicit type annotation}fn main() {    let f = true;    let t = true && false || true;//Boolean operations    assert_eq!(t, f);    println!("Success!")}

    Character type

    Rust’s char type is the most native character type in the language.

    12345678910111213
    fn main() {    let c = 'z';    let z: char = 'ℤ'; // with explicit type annotation    let heart_eyed_cat = '😻';}fn main() {    let c1 = '中';    print_char(c1);}fn print_char(c : char) {    println!("{}", c);}

    Char literals are declared with single quotes, whereas string literals are declared with double quotes. The size of Rust’s char type isfour bytes(four bytes) and represents a Unicode Scalar Value, which means it can represent much more than ASCII. In Rust, accented letters, characters such as Chinese, Japanese, and Korean, emoji, and zero-width whitespace characters are all valid char values. Unicode scalar values include values from U+0000 to U+D7FF and U+E000 to U+10FFFF. However, “character” is not a concept in Unicode, so the intuitive “character” may not correspond to Rust’s char.

    Size

    123456789101112
    #[allow(unused_variables)]use std::mem::size_of_val;fn main() {    let c1 = 'a';    assert_eq!(size_of_val(&c1),4); //One character is 4 bytes    let c2 = '中';    assert_eq!(size_of_val(&c2),4);    println!("Success!")}

    Unit type

    123456789101112
    fn main() {    let _v: () = ();    let v = (2, 3);    assert_eq!(_v, implicitly_ret_unit());    println!("Success!")}fn implicitly_ret_unit() {    println!("I will return a ()")}

    The memory occupied by the unit type is 0!!!

    1234567
    use std::mem::size_of_val;fn main() {    let unit: () = ();    assert!(size_of_val(&unit) == 0);    println!("Success!")}

    Compound types

    Compound typesCompound types) can combine multiple values into one type. Rust has two primitive compound types: tuples and arrays.

    Tuple type

    TupleFixed length: once declared, its length cannot grow or shrink

    Use a comma-separated list of values inside parentheses to create a tuple. Each position in the tuple has a type, and the types of these different valuesdo not have to be the same

    123
    fn main() {    let tup: (i32, f64, u8) = (500, 6.4, 1);}

    The tup variable binds to the entire tuple, because a tuple is a single compound element. To get individual values from a tuple, you can usepattern matching(pattern matching) todestructuring(destructure) tuple values, like this:

    1234567891011121314151617
    fn main() {    let tup = (500, 6.4, 1);    let (x, y, z) = tup;    println!("The value of y is: {y}");}fn main() {    let (x, y, z);    // Fill in the blanks    (y,z,x) = (1, 2, 3);    assert_eq!(x, 3);    assert_eq!(y, 1);    assert_eq!(z, 2);}

    The program first creates a tuple and binds it to the tup variable. Then it uses let and a pattern to split tup into three different variables, x, y, and z. This is called destructuringdestructuring), because it splits a tuple into three parts.

    You can alsouse a dot (.) followed by the index of the value to directly access them. The first index of a tuple is 0. For example:

    123456789
    fn main() {    let x: (i32, f64, u8) = (500, 6.4, 1);    let five_hundred = x.0;    let six_point_four = x.1;    let one = x.2;}

    A tuple without any values has a special name, called unit tuple. This value and its corresponding type are both written as (), representing an empty value or an empty return type. If an expression returns no other value, it implicitly returns the unit value.

    Tuples that are too long cannot be printed

    12345678910
    // Fix the code errorfn main() {    let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);    println!("too long tuple: {:?}", too_long_tuple);}//Fixfn main() {    let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);    println!("too long tuple: {:?}", too_long_tuple);}

    Array type

    Unlike tuples, every element in an array must have the same type. Arrays in Rust are different from arrays in some other languages; arrays in Rust have a fixed length.

    The type of an array is [T; Length]The length of an array is part of its type signature, so the length of an array must be known at compile time.

    The vector type is an array-like collection type provided by the standard library that allows growing and shrinking in length. When you’re unsure whether to use an array or a vector, you should probably use a vector.

    You can write the type of an array like this: include the type of each element in square brackets, followed by a semicolon, and then the number of elements in the array.

    let a: [i32; 5] = [1, 2, 3, 4, 5];

    Here, i32 is the type of each element. After the semicolon, the number 5 indicates that the array contains five elements.

    123456789
    fn main() {    // In many cases, we can omit part or all of the array's type and let the compiler help us infer it.    let arr0 = [1, 2, 3];    let arr: [char; 3] = ['a', 'b', 'c'];    // Arrays are allocated on the stack, `std::mem::size_of_val` the function returns the entire memory space occupied by the array.    // Each char element in the array occupies 4 bytes of memory space because in Rust, char is a Unicode character.    assert!(std::mem::size_of_val(&arr) == 12);}

    You can also create one by specifying an initial value in square brackets, followed by a semicolon, and then the number of elements,an array where every element has the same value.

    1
    let a = [3; 5];

    The array named a will contain 5 elements, and the values of these elements will initially all be set to 3. This syntax is equivalent to let a = [3, 3, 3, 3, 3]; but is more concise.

    Accessing Array Elements

    An array is a single memory block of known, fixed size that can be allocated on the stack. You can use indexing to access elements of an array, like this:

    123456789101112131415
    fn main() {    let a = [1, 2, 3, 4, 5];    let first = a[0];    let second = a[1];}fn main() {    let names = [String::from("Sunfei"), "Sunface".to_string()];    // `get` Return `Option<T>` type, so its use is very safe    let name0 = names.get(0).unwrap();    // However, subscript indexing carries the risk of going out of bounds.    let _name1 = &names[1];}

    Invalid Array Access

    If we access an element past the end of the array, the program, when an invalid value is used in an index operation, causes Runtime error. The program exits with an error message. Whenattempting to access an element with an index, Rust checks whether the specified index is less than the length of the array. If the index exceeds the array length, Rust will panic, which is a Rust term used for situations where the program exits because of an error.

    This check must be performed at runtime, especially in certain cases, because the compiler cannot possibly know what value the user will input when running the code later.

    Type Conversion

    Using as for Basic Type Conversion

    1. Rust doesnot provide implicit type conversion (coercion) for primitive types, but we can useasfor explicit conversion.
    1234567891011121314
    #[allow(unused_variables)]fn main() {  let decimal = 97.123_f32;  let integer: u8 = decimal as u8;  let c1: char = decimal as u8 as char;  let c2 = integer as char;  println!("c1 is {}",c1);  assert_eq!(integer, 'b' as u8 - 1);  println!("Success!")}

    2.By default, integer literal overflow is detected at compile time and reported as an error., but we can add a line of global annotation #![allow(overflowing_literals)] to avoid compilation errors (overflow will still occur).

    12345
    #![allow(overflowing_literals)]fn main() {    assert_eq!(u8::MAX, 255);    let v = 1000 as u8;}
    1. When converting any numeric value to an unsigned integer type T, if the current value is not within the range of the new type, we canadd or subtract from the current value (increase or decrease by T::MAX + 1), until the new value is within the range of the new type. Suppose we want to convert 300 to u8. Since the maximum value of u8 is 255, 300 is not within the range of the new type and is greater than the maximum value of the new type, so we need to subtract T::MAX + 1, that is, 300 - 256 = 44.
    12345678910111213141516171819202122232425262728
    #![allow(overflowing_literals)]fn main() {  assert_eq!(1000 as u16, 1000);  assert_eq!(1000 as u8, 232);  // In fact, the rule mentioned earlier for positive integers is the following modulo operation  println!("1000 mod 256 is : {}", 1000 % 256);  assert_eq!(-1_i8 as u8, 255);   // Since Rust 1.45, when a floating-point number exceeds the range of the target integer, the conversion will directly take the maximum or minimum value of the positive integer range.  assert_eq!(300.1_f32 as u8, 255);  assert_eq!(-100.1_f32 as u8, 0);    // The above floating-point conversion has a slight performance cost. If you have extreme performance requirements for a certain piece of code,    // you can consider the following methods, but the results of these methods may overflow and return meaningless values.    // In short, use with caution.  unsafe {      // 300.0 is 44      println!("300.0 is {}", 300.0_f32.to_int_unchecked::<u8>());      // -100.0 as u8 is 156      println!("-100.0 as u8 is {}", (-100.0_f32).to_int_unchecked::<u8>());      // nan as u8 is 0      println!("nan as u8 is {}", f32::NAN.to_int_unchecked::<u8>());  }}
    1. Raw pointers can be converted to and from integers representing memory addresses.
    12345678910111213141516171819202122
    fn main() {    let mut values: [i32; 2] = [1, 2];    let p1: *mut i32 = values.as_mut_ptr();    let first_address = p1 as usize;    let second_address = first_address + 4; // 4 == std::mem::size_of::<i32>()    let p2 = second_address as *mut i32;    unsafe {        *p2 += 1;    }    assert_eq!(values[1], 3);    println!("Success!")}fn main() {    let arr :[u64; 13] = [0; 13];    assert_eq!(std::mem::size_of_val(&arr), 8 * 13);    let a: *const [u64] = &arr;    let b = a as *const [u8];    unsafe {        assert_eq!(std::mem::size_of_val(&*b), 13)    }}

    From/Into

    1. TheFromtrait allows a typeto define how to create itself based on another type., so it provides a very convenient way of type conversion.
    2. FromandIntoare paired. As long as we implement the former, the latter will be automatically implemented.: as long as you implementimpl From for U, you can use the following two methods:let u: U = U::from(T)andlet u: U = T.into(). The former is provided by theFromtrait, while the latter is provided by the automatically implementedIntotrait.
    3. Note that when using theintomethod, you need to explicitly annotate the type, because the compiler is likely unable to infer the required type for us.

    Example

    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
    fn main() {    let my_str = "hello";    // The following three conversions all rely on the fact that `String` implements the `From<&str>` trait.    let string1 = String::from(my_str);    let string2 = my_str.to_string();    // Explicit type annotation is required here.    let string3: String = my_str.into();}fn main() {     // impl From<bool> for i32    let i1:i32 = false.into();    let i2:i32 = i32::from(false);    assert_eq!(i1, i2);    assert_eq!(i1, 0);    // Fix the following errors in two ways.    // 1. Which type implements the From trait: impl From<char> for ?, we can look at the documentation mentioned earlier to find the appropriate type    // 2. A keyword introduced in the previous chapter    let i3: i32 = 'a'.into();    // Use two methods to solve the error    let s: String = 'a' as String;    println!("Success!")}//The first methodfn main() {  // impl From<bool> for i32 let i1:i32 = false.into(); let i2:i32 = i32::from(false); assert_eq!(i1, i2); assert_eq!(i1, 0); let i3:u32 = 'a'.into(); let s: String = 'a'.into(); println!("Success!")}//The second methodfn main() {     // impl From<bool> for i32    let i1:i32 = false.into();    let i2:i32 = i32::from(false);    assert_eq!(i1, i2);    assert_eq!(i1, 0);    let i3: u32 = 'a' as u32 ;    let s: String = String::from('a');}

    Implement the From trait for a custom type

    12345678910111213141516171819202122232425
    // From is included in `std::prelude` so we don't need to manually bring it into the current scope// use std::convert::From;#[derive(Debug)]struct Number {    value: i32,}impl From<i32> for Number {    // Implementation `from` Method    fn from(item: i32) -> Self {        Number { value: item }    }}// Fill in the blanksfn main() {    let num = Number::from(30);    assert_eq!(num.value, 30);    let num: Number = 30.into();    assert_eq!(num.value, 30);    println!("Success!")}

    When doing error handling, implementing the From trait for our custom error type is very useful. This way we canautomatically convert an error type to our custom error type via ?

    1234567891011121314151617181920212223242526272829303132
    use std::fs;use std::io;use std::num;enum CliError {    IoError(io::Error),    ParseError(num::ParseIntError),}impl From<io::Error> for CliError {    fn from(error: io::Error) -> Self {        CliError::IoError(error)    }}impl From<num::ParseIntError> for CliError {    fn from(error: num::ParseIntError) -> Self {        CliError::ParseError(error)    }}fn open_and_parse_file(file_name: &str) -> Result<i32, CliError> {    // ? automatically converts io::Error to CliError    let contents = fs::read_to_string(&file_name)?;    // num::ParseIntError -> CliError    let num: i32 = contents.trim().parse()?;    Ok(num)}fn main() {    println!("Success!")}

    TryFrom / TryInto

    Similar to From and Into, TryFrom and TryInto are also generic traits for type conversion.

    But unlike From/Into, TryFrom and TryInto can handle conversion failures and return a Result

    1234567891011121314151617
    fn main() {  let n: i16 = 256;  // The Into trait has a method`into`,  // Therefore TryInto has a method ?  let n: u8 = match n.try_into() {      Ok(n) => n,      Err(e) => {          println!("there is an error when converting: {:?}, but we catch it", e.to_string());          0      }  };  assert_eq!(n, 0);  println!("Success!")}

    Custom implementation

    12345678910111213141516171819202122232425262728
    #[derive(Debug, PartialEq)]struct EvenNum(i32);impl TryFrom<i32> for EvenNum {    type Error = ();    // Implementation `try_from`    fn try_from(value: i32) -> Result<Self, Self::Error> {        if value % 2 == 0 {            Ok(EvenNum(value))        } else {            Err(())        }    }}fn main() {    assert_eq!(EvenNum::try_from(8), Ok(EvenNum(8)));    assert_eq!(EvenNum::try_from(5), Err(()));    // Fill in the blanks    let result: Result<EvenNum, ()> = 8i32.try_into();    assert_eq!(result, Ok(EvenNum(8)));    let result: Result<EvenNum, ()> = 5i32.try_into();    assert_eq!(result,Err(()));    println!("Success!")}

    Other conversions

    Convert any type to String

    As long as a type implements ToString, any type can be converted to String. In fact, this approach is not the best; you can use the fmt::Display trait? It can control how a type is printed, and implementing it also automatically implements ToString. Because to_string is implemented based on fmt::Display.

    1234567891011121314151617181920
    use std::fmt;struct Point {    x: i32,    y: i32,}impl fmt::Display for Point {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        write!(f, "The point is ({}, {})", self.x, self.y)    }}fn main() {    let origin = Point { x: 0, y: 0 };    assert_eq!(origin.to_string(), "The point is (0, 0)");    assert_eq!(format!("{}", origin), "The point is (0, 0)");    println!("Success!")}

    Parsing String

    Using the parse method, a String can be converted to an i32 number, because the standard library implements FromStr for i32: impl FromStr for i32

    1234567891011
    // To use `from_str` method, you needs to introduce this trait into the current scope.use std::str::FromStr;fn main() {    let parsed: i32 = "5".parse().unwrap();    let turbo_parsed = "10".parse::<i32>().unwrap();    let from_str = i32::from_str("20").unwrap();    let sum = parsed + turbo_parsed + from_str;    assert_eq!(sum, 35);    println!("Success!")}

    Custom implementation of the FromStr trait

    12345678910111213141516171819202122232425262728
    use std::str::FromStr;use std::num::ParseIntError;#[derive(Debug, PartialEq)]struct Point {    x: i32,    y: i32}impl FromStr for Point {    // Associated type: a type parameter defined in a trait and "bound" to that trait.    type Err = ParseIntError;    fn from_str(s: &str) -> Result<Self, Self::Err> {        let coords: Vec<&str> = s.trim_matches(|p| p == '(' || p == ')' )                                 .split(',')                                 .collect();        let x_fromstr = coords[0].parse::<i32>()?;        let y_fromstr = coords[1].parse::<i32>()?;        Ok(Point { x: x_fromstr, y: y_fromstr })    }}fn main() {    let p = "(3,4)".parse::<Point>();    assert_eq!(p.unwrap(), Point{ x: 3, y: 4} )}

    transmute

    std::mem::transmute is an unsafe function that caninterpret one type as another type bitwise, where the two typesmust have the same size

    transmute is equivalent to reinterpreting the bit pattern of one type as another type. It does not copy data; it simply interprets the bits of the source value directly as the target type, and then forgets the source value.

    Because of this,transmuteit is very, very unsafe! The caller must ensure the safety of the code themselves; of course, this is also the purpose of unsafe.

    example

    1. transmute can convert a pointer into a function pointer. This conversion is not portable because on different machines, function pointers and data pointers may have different sizes.
    1234567891011
    fn foo() -> i32 {    0}fn main() {    let pointer = foo as *const ();    let function = unsafe {        std::mem::transmute::<*const (), fn() -> i32>(pointer)    };    assert_eq!(function(), 0);}
    1. transmute can also extend or shorten the lifetime of an invariant, that is,an ‘illegal conversion’ of lifetimes!
    1234567891011
    // R is a struct that wraps a reference. It does not store the i32 itself, but stores a reference to the i32.struct R<'a>(&'a i32);unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> {    std::mem::transmute::<R<'b>, R<'static>>(r)}unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>)                                             -> &'b mut R<'c> {    std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r)}
    1. In fact, we can also use some safe methods to replace transmute.
    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
    fn main() {    /*Turning raw bytes(&[u8]) to u32, f64, etc.: */    let raw_bytes = [0x78, 0x56, 0x34, 0x12];    let num = unsafe { std::mem::transmute::<[u8; 4], u32>(raw_bytes) };    // use `u32::from_ne_bytes` instead    // according to the native endianness    let num = u32::from_ne_bytes(raw_bytes);    // or use `u32::from_le_bytes` or `u32::from_be_bytes` to specify the endianness    // little-endian    let num = u32::from_le_bytes(raw_bytes);    assert_eq!(num, 0x12345678);    // big-endian    let num = u32::from_be_bytes(raw_bytes);    assert_eq!(num, 0x78563412);    /*Turning a pointer into a usize: */    let ptr = &0;    let ptr_num_transmute = unsafe { std::mem::transmute::<&i32, usize>(ptr) };    // Use an `as` cast instead    let ptr_num_cast = ptr as *const i32 as usize;    /*Turning an &mut T into an &mut U: */    let ptr = &mut 0;    let val_transmuted = unsafe { std::mem::transmute::<&mut i32, &mut u32>(ptr) };    // ptr as *mut i32: convert &mut i32 to a raw pointer *mut i32.    // as *mut u32: convert the raw pointer further to *mut u32.    // &mut *(...): convert the raw pointer back to a mutable reference.    let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) };    /*Turning an &str into a &[u8]: */    // this is not a good way to do this.    let slice = unsafe { std::mem::transmute::<&str, &[u8]>("Rust") };    assert_eq!(slice, &[82, 117, 115, 116]);    // You could use `str::as_bytes`    let slice = "Rust".as_bytes();    assert_eq!(slice, &[82, 117, 115, 116]);    // Or, just use a byte string, if you have control over the string    // literal    assert_eq!(b"Rust", &[82, 117, 115, 116]);}

    function

    In Rust code,function and variable namesuse snake case conventional style. In snake case, all letters are lowercase and words are separated by underscores.

    In Rust, we define functions by entering fn followed by the function name and a pair of parentheses. The curly braces tell the compiler where the function body begins and ends.

    Parameters

    We can define it as owning Parametersparameters) of the function, parameters are special variables and are part of the function signature. When a function has parameters (formal parameters), concrete values can be provided for these parameters (actual arguments). Technically, these concrete values are called arguments (arguments

    1234567
    fn main() {    another_function(5);}fn another_function(x: i32) {    println!("The value of x is: {x}");}

    When defining multiple parameters, separate them with commas.

    Statements and Expressions

    The function body consists of a series of statements and an optional final expression.

    statementStatementsare instructions that perform some actions but do not return a value

    expressionExpressionsevaluate and produce a value

    Statements do not return values; expressions evaluate to a value.

    1234567891011121314151617181920
    fn main() {    let x = 5u32;    let y = {        let x_squared = x * x;        let x_cube = x_squared * x;        // The value of the following expression will be assigned to `y`        x_cube + x_squared + x    };    let z = {        // The semicolon turns an expression into a statement, so what is returned is no longer an expression `2 * x` value, but rather the value of the statement `()`        2 * x;    };    println!("x is {:?}", x);    println!("y is {:?}", y);    println!("z is {:?}", z);}

    In the statementlet y = 6;, the 6 is an expression that evaluates to the value 6.A function call is an expressionA macro call is an expressionA new block scope created with braces is also an expressionFor example:

    12345678
    fn main() {    let y = {        let x = 3;        x + 1    };    println!("The value of y is: {y}");}

    This expression:

    1234
    {    let x = 3;    x + 1}

    is a code block, and its value is 4. There is no semicolon at the end of the expression. If you add a semicolon at the end of the expression, it becomes a statement, and statements do not return values.

    Return value

    Do not name the return value, but you must declare its type after the arrow (->).

    In Rust, the return value of a function is equivalent to the value of the last expression in the function body. You can return early from a function using thereturnkeyword and a specified value; however, most functions implicitly return the last expression.

    123456789
    fn main() {    let x = plus_one(5);    println!("The value of x is: {x}");}fn plus_one(x: i32) -> i32 {    x + 1}

    The return type is ()

    123456789
    fn main(){    println!("{}",type_of(&println!("helloworld")))}fn type_of<T>(_: &T) -> String{    format!("{}",std::any::type_name::<T>())}//output://helloworld//()

    The return type is never

    123456789101112131415
    use std::thread;use std::time;fn never_return() -> ! {    // implement this function, don't modify fn signatures    loop {        println!("I return nothing");        // sleeping for 1 second to avoid exhausting the cpu resource        thread::sleep(time::Duration::from_secs(1))    }}fn main() {    never_return();}

    Diverging function

    Diverging functions do not return any value, so they can be used anywhere a return value is needed.

    1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
    fn main() {    println!("Success!");}fn get_option(tp: u8) -> Option<i32> {    match tp {        1 => {            // TODO        }        _ => {            // TODO        }    };    // Here, rather than returning a `None`, it is better to use a diverging function instead.    never_return_fn()}// Implement the following diverging function using three methods.fn never_return_fn() -> ! {}fn main() {    println!("Success!");}fn get_option(tp: u8) -> Option<i32> {    match tp {        1 => {            // TODO        }        _ => {            // TODO        }    };    never_return_fn()}// IMPLEMENT this function// DON'T change any code elsefn never_return_fn() -> ! {    unimplemented!()}// IMPLEMENT this function in THREE waysfn never_return_fn() -> ! {    panic!()}// IMPLEMENT this function in THREE waysfn never_return_fn() -> ! {    todo!();}// IMPLEMENT this function in THREE waysfn never_return_fn() -> ! {    loop {        std::thread::sleep(std::time::Duration::from_secs(1))    }}

    The difference betweenunimplemented!()andtodo!()is that while todo!()conveys an intent of implementing the functionality later and the message is “not yet implemented”,unimplemented!()makes no such claims. Its message is “not implemented”. Also some IDEs will marktodo!()s.

    Call

    123456789101112131415161718192021222324252627
    #[allow(unused)]fn main() {    get_option(3);    println!("Success!");}fn get_option(tp: u8) -> Option<i32> {    match tp {        1 => {            // TODO        }        _ => {            // TODO        }    };    never_return_fn()}// IMPLEMENT this function// DON'T change any code elsefn never_return_fn() -> ! {    loop {        std::thread::sleep(std::time::Duration::from_secs(1))    }}

    Usingunimplemented!()andtodo!()will report the following error:

    thread ‘main’ panicked at ‘not implemented’, src\main.rs:24:5

    12345678
    let _v = match b {        true => 1,        // Diverging functions can also be used in `match` expressions, to substitute for values of any type        false => {            println!("Success!");            panic!("we have no value for `false`, but we can panic")        }    };

    Control flow

    if expressions

    123456789
    fn main() {    let number = 3;    if number < 5 {        println!("condition was true");    } else {        println!("condition was false");    }}

    The code blocks associated with conditions in an if expression are sometimes called arms.

    if/else can be used as an expression for assignment.

    12345678910111213141516
    fn main() {    let n = 5;    let big_n =        if n < 10 && n > -10 {            println!(" 数字太小,先增加 10 倍再说");            10 * n        } else {            println!("数字太大,我们得让它减半");            n / 2        };    println!("{} -> {}", n, big_n);}

    Note

    1. In the code,the condition must be bool value. If the condition is not a bool value, we will get an error. Rust does not attempt to automatically convert non-boolean values to boolean. You must always explicitly use a boolean value as the condition for if.

    2. If more than one else if is used, it is best to use match to refactor the code.

    Using if in a let statement

    Because if is an expression, we can use it on the right side of a let statement.

    123456
    fn main() {    let condition = true;    let number = if condition { 5 } else { 6 };    println!("The value of number is: {number}");}

    The possible return values of each branch of an if must be the same type.

    Note

    The expression in the if block returns an integer, while the expression in the else block returns a string. This won’t work because a variable must have only one type. Rust needs to know exactly the type of the variable at compile time.

    Loop

    loop

    12345
    fn main() {    loop {        println!("again!");    }}
    Returning Values from Loops
    12345678910111213
    fn main() {    let mut counter = 0;    let result = loop {        counter += 1;        if counter == 10 {            break counter * 2;        }    };    println!("The result is {result}");}
    loop label

    If there are nested loops, break and continue apply to the innermost loop at that point. You can optionally specify a loop labelloop label), and then use the label with break or continue, so that these keywords apply to the labeled loop instead of the innermost loop.

    123456789101112131415161718192021
    fn main() {    let mut count = 0;    'counting_up: loop {        println!("count = {count}");        let mut remaining = 10;        loop {            println!("remaining = {remaining}");            if remaining == 9 {                break;            }            if count == 2 {                break 'counting_up;            }            remaining -= 1;        }        count += 1;    }    println!("End count = {count}");}

    while

    1234567891011
    fn main() {    let mut number = 3;    while number != 0 {        println!("{number}!");        number -= 1;    }    println!("LIFTOFF!!!");}

    for

    1234567
    fn main() {    let a = [10, 20, 30, 40, 50];    for element in a {        println!("the value is: {element}");    }}

    The for loop iterates over collection elements, which enhances code safety compared to a while loop, and eliminates bugs that may be caused by going beyond the end of an array or by not iterating enough and missing some elements.

    For iterable objects that do not implement Copy, for in will take ownership.

    12345678910111213141516
    fn main() {    let names = [String::from("liming"),String::from("hanmeimei")];    for name in &names {        // do something with name...    }    println!("{:?}", names);    let numbers = [1, 2, 3];    // The elements in numbers implement Copy, so there is no need to transfer ownership.    for n in numbers {        // do something with name...    }    println!("{:?}", numbers);}

    Iterating over an array by index and value

    12345678
    fn main() {    let a = [4,3,2,1];    // Iterating over an array by index and value `a`    for (i,v) in a.iter().enumerate() {        println!("第{}个元素是{}",i+1,v);    }}

    Range

    It is a type provided by the standard library, used to generate a sequence of all numbers starting from one number and ending before another number. (The ending number is not included.)

    The rev method can reverse a range.

    123456
    fn main() {    for number in (1..4).rev() {        println!("{number}!");    }    println!("LIFTOFF!!!");}

    Ownership, References, and Borrowing

    The Stack and the Heap

    Both the stack and the heap are parts of memory available to your code to use at runtime, but they are structured in different ways. The stack stores values in the order it gets them and removes the values in the opposite order. This is also called last in, first outlast in, first out)。

    Adding data is called pushing onto the stackpushing onto the stack), and removing data is called popping off the stackpopping off the stack). All data on the stack must occupy a known and fixed size.

    Data with an unknown size at compile time or a size that might change must be stored on the heap instead.. The heap is less organized: when you put data on the heap, you request a certain amount of space. The memory allocator finds an empty spot somewhere on the heap that is big enough, marks it as in use, and returns a pointer that represents the address of that location pointerpointer). This process is called allocating on the heapallocating on the heap), sometimes simply called “allocating.” (Pushing values onto the stack is not considered allocating.)Because the pointer to the data on the heap is known and has a fixed size, you can store the pointer on the stackbut when you want the actual data, you must follow the pointer.

    **Pushing to the stack is faster than allocating on the heap because the allocator does not have to search for a place to store new data; that location is always at the top of the stack.**In contrast, allocating on the heap requires more work because the allocator must first find a big enough space to hold the data and then perform bookkeeping to prepare for the next allocation.

    Accessing data on the heap is slower than accessing data on the stack because you have to follow a pointer. Modern processors are faster if they jump around in memory less (caching). For the same reason, processors work better when the data they’re processing is close together (such as on the stack) rather than far apart (such as on the heap).

    When your code calls a function, the values passed into the function (including, potentially, pointers to data on the heap) and the function’s local variables get pushed onto the stack. When the function is over, those values get popped off the stack.

    Keeping track of what parts of code are using what data on the heap, minimizing the amount of duplicate data on the heap, and cleaning up unused data on the heap so you don’t run out of space are all problems that ownership addresses. Once you understand ownership, you won’t need to think about the stack and the heap very often, but knowing that the main purpose of ownership is to manage heap data can help explain why it works the way it does.

    Ownership Rules

    1. Each value in Rust has an owner.
    2. There can be only one owner at a time.
    3. When the owner (variable) goes out of scope, the value will be dropped.

    Example of ownership:

    Modify the code below:

    12345678910111213
    fn main() {    let s = give_ownership();    println!("{}", s);}// Only modify the code below!fn give_ownership() -> String {    let s = String::from("hello, world");    // convert String to Vec    // Convert String to Vec type    let _s = s.into_bytes();//into_bytes transfers ownership    s}

    Method

    123456789101112
    fn main() {    let s = give_ownership();    println!("{}", s);}// Only modify the code below!fn give_ownership() -> String {    let s = String::from("hello, world");    // convert String to Vec    let _s = s.as_bytes();//as_bytes does not transfer ownership    s}

    or

    12345678910
    fn main() {    let s = give_ownership();    println!("{}", s);}// Only modify the code below!fn give_ownership() -> String {    let s = String::from("hello, world");    s}

    When ownership is transferred, mutability can also change accordingly.

    1234567
    fn main() {    let s = String::from("hello, ");    let mut s1 = s;    s1.push_str("world")}

    Variables and Scope

    A scope is the range in which an item is valid in a program. Suppose we have a variable like this:

    let s = “hello”;

    The variable s is bound to a string literal, and this string value ishardcodedinto the program code. This variable is valid from the point of declaration until the current scope ends. The comments in Example 4-1 indicate where the variable s is valid.

    12345
    {                      // s is not valid here; it is not yet declared        let s = "hello";   // From this point on, s is valid        // use s}                      // This scope has ended, s is no longer valid
    • When senters scope it is valid.
    • This continues until it leaves scope .

    str and &str

    Under normal circumstances, we cannot use the str type, but we can use &str instead.

    123
    fn main() {    let s: &str = "hello, world";}

    If you want to use the str type, it can only be used with Box.

    12345678
    fn main() {    let s: Box<str> = "hello, world".into();    greetings(s)}fn greetings(s: Box<str>) {    println!("{}",s)}

    & can be used to convert Box into the &str type , Rust’s Deref coercion will&Box<str>automatically convert to&str

    12345678
    fn main() {    let s: Box<str> = "hello, world".into();    greetings(&s)}fn greetings(s: &str) {    println!("{}",s)}

    String type

    String is a type defined in the standard library, allocated on the heap, and can grow dynamically. Its underlying storage is a dynamic byte array (Vec ), but unlike a byte array,String is UTF-8 encoded

    The difference between Unicode and encoding methods

    • Unicode
      • is a character set, which specifies that each character corresponds to a unique code point
      • Code point form:U+0000~U+10FFFF
      • For example:
        • U+4F60→ ‘you’
        • U+1F600→ 😀
    • UTF-8 / UTF-16 / UTF-32
      • is to convert Unicode code points into byte sequence the specific method
      • In other words, Unicode is a “character table”, and UTF-8 is the “encoding rule for how to store or transmit these characters”.

    String.chars() and String.bytes() iterate over Unicode characters and bytes, respectively.

    • The length of a Unicode character is not fixed.
    • A Unicode character is not necessarily a fully displayed character.

    In Unicode and text processing,Grapheme cluster is a user-perceived “character unit”, that is, it is a complete character seen by the user, but it may consist of multiple Unicode scalar values (char).

    In simple terms:

    • A grapheme cluster ≈ “a complete displayed character”
    • Unlike Rust’scharcharis a single Unicode scalar value (which may be a letter, a Chinese character, or a component of an emoji)
    • A grapheme cluster may contain:
      • Base character + combining mark (such as a diacritic)
      • emoji combination (such as the 👨‍👩‍👧‍👦 family emoji, consisting of multiple emoji and zero-width joiners)

    To iterate over grapheme clusters, you need a third-party package, such as:

    String manages data allocated on the heap, so it can store text whose size is unknown at compile time. You can create a String from a string literal using the from function.

    1
    let s = String::from("hello");

    OK Modifying such a string:

    12345
    let mut s = String::from("hello");    s.push_str(", world!"); // push_str() appends a literal to the end of the string    println!("{}", s); // will print `hello, world!`

    Memory and Allocation

    As forstring literalswe know their contents at compile time, sothe text is directly hardcoded into the final executableThis makes string literals fast and efficient. However, these properties are all due to the immutability of string literals. Unfortunately, we cannot put a block of memory into the binary for every text whose size is unknown at compile time, and its size may also change as the program runs.

    For the String type, in order to support a mutable, growable piece of text, it needs to allocate a block of memory on the heap whose size is unknown at compile time to store the contents. This means:

    • It must request memory from the memory allocator at runtime.
    • It needs a way to return the memory to the allocator when we are done with the String. (In some languages, this is garbage collection, GC.)

    Rust takes a different strategy:**The memory is automatically freed after the variable that owns it goes out of scope.**Below is a version of the scope example from Listing 4-1 that uses a String instead of a string literal:

    123456
    {        let s = String::from("hello"); // From this point on, s is valid        // use s    }                                  // This scope is now over,                                       // s is no longer valid

    This is a very natural place to return the memory that String needs to the allocator: when s goes out of scope. When a variable goes out of scope, Rust calls a special function for us. This function is called drop, and here the author of String can place the code to free the memory. Rust automatically calls drop at the closing }.

    Ways in which variables and data interact

    move

    Stack-only data

    12
    let x = 5;let y = x;

    bind 5 to x; then make a copy of the value in x and bind it to y. Now there are two variables, x and y, both equal to 5.

    Because integers are simple values with a known fixed size, these two 5s are placed on the stack.

    For this type of data, there is no difference between moving and cloning.

    12
    let s1 = String::from("hello");let s2 = s1;

    A String consists of three parts:

    1. a pointer to the memory that holds the contents of the string
    2. a length, len, which is the number of bytes required to store the string’s contents
    3. a capacity, capacity, which is the total number of bytes of memory the String has obtained from the operating system

    The aboveare stored on the stack,, and the part that holds the string contents is stored on the heap.

    When we assign s1 to s2, if the String’s data were copied, this would mean we copy its pointer, length, and capacity from the stack. We do not copy the heap data that the pointer refers to.

    When the variables go out of scope, drop is called, causing a double free (repeated deallocation).

    To ensure memory safety,

    • Rust does not attempt to copy the allocated memory.
    • Rust invalidates s1, so when the variable s1 goes out of scope, it does not need to free anything (corresponding to ownership rule 2: a value has one and only one owner at any given time).
    12345678910111213141516171819202122232425262728
    let s1 = String::from("hello");    let s2 = s1;    println!("{}, world!", s1);warning: unused variable: `s2` --> src\main.rs:3:9  |3 |     let s2 = s1;  |         ^^ help: if this is intentional, prefix it with an underscore: `_s2`  |  = note: `#[warn(unused_variables)]` on by defaulterror[E0382]: borrow of moved value: `s1` --> src\main.rs:5:28  |2 |     let s1 = String::from("hello");  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait3 |     let s2 = s1;  |              -- value moved here4 |5 |     println!("{}, world!", s1);  |                            ^^ value borrowed here after move  |  = 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)For more information about this error, try `rustc --explain E0382`.warning: `loop_test` (bin "loop_test") generated 1 warningerror: could not compile `loop_test` due to previous error; 1 warning emitted

    Rust’s approach here is different from a shallow copy, because**While performing a shallow copy, the copied-from variable is invalidated.**Therefore, the new term ‘move’ is used.

    Implicit design principle : Rust does not automatically create deep copies of data.

    Because in terms of runtime performance, any automatic copy operation in Rust is cheap (only involving shallow copies of data on the stack).

    Partial move

    When destructuring a variable, you can use both move and reference pattern bindings at the same time. When you do this, a partial move occurs:Ownership of part of the variable is transferred to other variables, while for another part we obtain a reference to it.

    In this case,**The original variable can no longer be used, but the part whose ownership was not transferred can still be used,**i.e., the part that was previously referenced.

    1234567891011121314151617181920212223242526
    fn main() {    #[derive(Debug)]    struct Person {        name: String,        age: Box<u8>,    }    let person = Person {        name: String::from("Alice"),        age: Box::new(20),    };    // Through this destructuring pattern matching, ownership of person.name is transferred to a new variable. `name`    // But here, `age` the variable is a reference to person.age; using ref here is equivalent to: let age = &person.age    let Person { name, ref age } = person;    println!("The person's age is {}", age);    println!("The person's name is {}", name);    // Error! The reason is that part of person has already been moved, so we can no longer use it.    //println!("The person struct is {:?}", person);    // Although `person` As a whole, it can no longer be used, but `person.age` can still be used.    println!("The person's age from person struct is {}", person.age);}

    clone

    If you want to deep-copy data on the heap, not just data on the stack, you can use the clone method.

    123456789101112
    let s1=String::from("Hello");let s2=s1.clone();println!("{},{}",s1,s2);#[allow(unused)]fn main() {    let t = (String::from("hello"), String::from("world"));    let (s1, s2) = t.clone();    println!("{:?}, {:?}, {:?}", s1, s2, t); // -> "hello", "world", ("hello", "world")}

    copy

    Copy traitIt can be used for types that are entirely on the stack, such as integers.

    • If a type implements the Copy trait, then the old variable remains usable after assignment.
    • If a type or part of that type implements the Drop trait, then Rust does not allow it to also implement the Copy trait.

    Any simple scalar and its composite types are Copy.

    Anything that requires allocating memory or some resource is not Copy.

    Some types that implement the Copy trait:

    • All integer types, such as u32.
    • Boolean type, bool, whose values are true and false.
    • All floating-point types, such as f64.
    • Character type, char.
    • Tuples, if and only if the types they contain also implement Copy. For example, (i32, i32) implements Copy, but (i32, String) does not.

    Ownership and Functions

    Passing a value to a function is similar to assigning a value to a variable. Passing a value to a function may move or copy, just like an assignment statement.

    1234567891011121314151617181920212223
    fn main() {    let s = String::from("hello");  // s enters scope    takes_ownership(s);             // s's value moves into the function ...                                    // ... and so is no longer valid here    let x = 5;                      // x enters scope    makes_copy(x);                  // x would move into the function,                                    // but i32 is Copy,                                    // so x can still be used afterward} // Here, x goes out of scope first, then s. But because s's value has been moved,  // nothing special happens.fn takes_ownership(some_string: String) { // some_string enters scope    println!("{}", some_string);} // Here, some_string goes out of scope and calls `drop` the method.  // The memory it occupies is freed.fn makes_copy(some_integer: i32) { // some_integer enters scope    println!("{}", some_integer);} // Here, some_integer goes out of scope. Nothing special happens.

    When you try to use s after calling takes_ownership, Rust throws a compile-time error.

    Return Values and Scope

    Return values can also transfer ownership.

    1234567891011121314151617181920212223242526272829
    fn main() {    let s1 = gives_ownership();         // gives_ownership returns a value                                        // moved to s1    let s2 = String::from("hello");     // s2 enters scope    let s3 = takes_and_gives_back(s2);  // s2 is moved to                                        // takes_and_in gives_back,                                        // it also moves the return value to s3} // Here, s3 moves out of scope and is dropped. s2 also moves out of scope, but it has already been moved,  // so nothing happens. s1 leaves scope and is dropped.fn gives_ownership() -> String {             // gives_ownership will                                             // move the return value to                                             // the function that calls it    let some_string = String::from("yours"); // some_string enters scope.    some_string                              // returns some_string                                             // and moves out to the calling function                                             //}// takes_and_gives_back takes the passed-in string and returns that valuefn takes_and_gives_back(a_string: String) -> String { // a_string enters scope                                                      //    a_string  // returns a_string and moves out to the calling function}

    The ownership of variables always follows the same pattern:

    • Move it when assigning the value to another variable.
    • when holdinga variable with a value on the heap leaves scopeits value will be cleaned up by drop unless the data has been moved to be owned by another variable.

    If you want a function to obtain the value without taking ownership, you need to return the passed-in parameter.

    12345678910111213
    fn main() {    let s1 = String::from("hello");    let (s2, len) = calculate_length(s1);    println!("The length of '{}' is {}.", s2, len);}fn calculate_length(s: String) -> (String, usize) {    let length = s.len(); // len() returns the length of the string.    (s, length)}

    This is too cumbersome. Rust provides a feature for this that allows using a value without taking ownership, called Referencereferences)。

    References and Borrowing

    1234567891011
    fn main() {    let s1 = String::from("hello");    let len = calculate_length(&s1);    println!("The length of '{}' is {}.", s1, len);}fn calculate_length(s: &String) -> usize {    s.len()}

    Referencereference) is like a pointer, becauseit is an address, and we can access data stored at that address that belongs to other variables.

    Unlike pointers,references ensure they point to a valid value of a particular type.

    1234567
    fn main() {   let x = 5;   // Fill in the blank   let p = &x;   println!("x 的内存地址是 {:p}", p); // output: 0x16fa3ac84}

    Note: the operation opposite to using & to reference is dereferencingdereferencing), which uses the dereference operator " * "

    123
    let s1 = String::from("hello");   let len = calculate_length(&s1);

    The &s1 syntax lets us create a point to reference to the value s1, but does not own it. Because we do not own the value,when the reference stops being used, the value it points to will not be dropped.

    We call the behavior of creating a reference borrowingborrowing

    Just as variables are immutable by default, so are references.References (by default) do not allow modifying the value they refer to.

    Rust will automatically dereference in certain cases.

    1234567
    fn main() {    let mut s = String::from("hello, ");    let p = &mut s;    p.push_str("world");}

    Example:

    12345678910111213141516
    fn main() {    let mut s = String::from("hello, ");    borrow_object(&s)}fn borrow_object(s: &String) {}fn main() {    let mut s = String::from("hello, ");    push_str(&mut s)}fn push_str(s: &mut String) {    s.push_str("world")}

    mutable reference

    123456789
    fn main() {    let mut s = String::from("hello");    change(&mut s);}fn change(some_string: &mut String) {    some_string.push_str(", world");}

    Mutable references have a big restriction:only one mutable reference can exist at a time

    123456
    let mut s = String::from("hello");    let r1 = &mut s;    let r2 = &mut s;    println!("{}, {}", r1, r2);

    The benefit of this restriction is that Rust can avoid data races at compile time.data racedata race) is similar to a race condition, and it can be caused by these three behaviors:

    • Two or more pointers access the same data at the same time.
    • At least one pointer is used to write data.
    • There is no mechanism for synchronizing data access.

    You can use curly braces to create a new scope to allow having multiple mutable references, just not simultaneously have:

    1234567
    let mut s = String::from("hello");{    let r1 = &mut s;} // r1 goes out of scope here, so we can definitely create a new referencelet r2 = &mut s;

    Another restriction:You cannot have a mutable reference and an immutable reference at the same time.

    However,Multiple immutable references are allowed.

    1234567
    let mut s = String::from("hello");let r1 = &s; // No problem.let r2 = &s; // No problem.let r3 = &mut s; // Big problem.println!("{}, {}, and {}", r1, r2, r3);

    Dangling References

    In Rust, the compiler ensures that references never become dangling: when you have a reference to some data, the compiler ensures that the data does not go out of scope before its reference.

    Let’s try to create a dangling reference, and Rust will prevent it with a compile-time error:

    File name: src/main.rs

    1234567891011121314151617181920212223
    fn main() {    let reference_to_nothing = dangle();}fn dangle() -> &String {    let s = String::from("hello");//s is dropped after the function ends.    &s//Returning a reference, but the address is freed after the function ends.}Compiling loop_test v0.1.0 (C:\Users\cauchy\Desktop\rust\loop_test)error[E0106]: missing lifetime specifier --> src\main.rs:5:16  |5 | fn dangle() -> &String {  |                ^ expected named lifetime parameter  |  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed fromhelp: consider using the `'static` lifetime  |5 | fn dangle() -> &'static String {  |                 +++++++For more information about this error, try `rustc --explain E0106`.error: could not compile `loop_test` due to previous error

    ref

    ref is similar to &, and can be used to get a reference to a value, but their usage differs.

    1234567891011121314151617
    fn main() {    let c = '中';    let r1 = &c;    let ref r2 = c;    assert_eq!(*r1, *r2);    // Determine whether the string representations of two memory addresses are equal.    assert_eq!(get_addr(r1),get_addr(r2));}// Get the string representation of the memory address of the passed reference.fn get_addr(r: &char) -> String {    format!("{:p}", r)}

    Summary of reference rules (borrowing rules).

    • At any given time,or there can only be one mutable reference,or there can only be multiple immutable references.
    • References must always be valid.

    Ok: Borrowing immutably from a mutable object

    123456789
    fn main() {    let mut s = String::from("hello, ");    borrow_object(&s);    s.push_str("world");}fn borrow_object(s: &String) {}

    None Lexical Lifetimes(NLL)

    Non-lexical lifetimes

    Example

    1234567891011
    // Comment out a line of code to make it workfn main() {    let mut s = String::from("hello, ");    let r1 = &mut s;    r1.push_str("world");    let r2 = &mut s;    r2.push_str("!");    println!("{}",r1);}

    Just comment out the println

    1234567891011121314151617
    fn main() {    let mut s = String::from("hello, ");    let r1 = &mut s;    r1.push_str("world");//The Rust compiler knows that after this, r1's borrow of s has ended.    let r2 = &mut s;    r2.push_str("!");    //println!("{}",r1);}fn main() {    let mut x = 22;    let p = &mut x; // mutable borrow    println!("{}", x); // later used}

    This code compiles successfully because the compiler knows that the mutable borrow of x does not last until the end of the scope, but ends before x is used again, so there is no conflict here.

    12345678910
    fn main() {    let mut s = String::from("hello, ");    let r1 = &mut s;    let r2 = &mut s;    // Add a line of code below to artificially create a compile error: cannot borrow `s` as mutable more than once at a time    // You cannot use r1 and r2 at the same time.}

    Just add r1.push_str(“world”);

    12345678910111213141516171819202122
    warning: unused variable: `r2` --> src/main.rs:5:9  |5 |     let r2 = &mut s;  |         ^^ help: if this is intentional, prefix it with an underscore: `_r2`  |  = note: `#[warn(unused_variables)]` on by defaulterror[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:5:14  |4 |     let r1 = &mut s;  |              ------ first mutable borrow occurs here5 |     let r2 = &mut s;  |              ^^^^^^ second mutable borrow occurs here...9 |     r1.push_str("world");  |     -- first borrow later used hereFor more information about this error, try `rustc --explain E0499`.warning: `rust_programming` (bin "rust_programming") generated 1 warningerror: could not compile `rust_programming` (bin "rust_programming") due to 1 previous error; 1 warning emitted

    Slice Type

    slice Allows you to reference a contiguous sequence of elements in a collection without referencing the entire collection. A slice is a kind of reference, so it does not have ownership.

    12345678910
    fn first_word(s: &String) -> usize {    let bytes = s.as_bytes();    for (i, &item) in bytes.iter().enumerate() {        if item == b' ' {            return i;        }    }    s.len()}

    This function takes a string of words separated by spaces and returns the first word found in that string. If the function does not find a space in the string, then the entire string is a single word, so it should return the whole string.

    The first_word function has a parameter &String. Because we don’t need ownership, this is fine. But what should it return? We don’t actually have a way to get… only partially a way to get a string. However, we can return the index of the end of the word, indicated by a space.

    Because we need to check the String element by element to see if a value is a space, we need to use the as_bytes method to convert the String to a byte array:

    let bytes = s.as_bytes();

    Next, use the iter method to create an iterator over the byte array:

    for (i, &item) in bytes.iter().enumerate() {

    Because the enumerate method returns a tuple, we can use a pattern to destructure it, so in the for loop, we specify a pattern where i in the tuple is the index and &item in the tuple is a single byte. Because we get a reference to the collection element from .iter().enumerate(), we use & in the pattern.

    However, there is a problem. We return an independent usize, but it is only a meaningful number in the context of the &String. In other words, because it is a value separate from the String, there is no guarantee that it will still be valid in the future.

    String slice

    String slicestring slice) is a reference to a part of a String, and it looks like this:

    1234
    let s = String::from("hello world");    let hello = &s[0..5];    let world = &s[6..11];

    [starting index…ending index]

    [starting_index…ending_index]

    where starting_index is the first position of the slice, ending_index is the slice The value after the last position.

    If you want to start at index 0, you can omit the value before the two dots.

    1234
    let s = String::from("hello");let slice = &s[0..2];let slice = &s[..2];

    If the slice includes the last byte of the String, you can also omit the trailing number.

    123456
    let s = String::from("hello");let len = s.len();let slice = &s[3..len];let slice = &s[3..];

    You can also omit both values to get a slice of the entire string.

    123456
    let s = String::from("hello");let len = s.len();let slice = &s[0..len];let slice = &s[..];

    Note:**String slice range indices must be at valid UTF-8 character boundaries,**If you try to create a string slice from the middle of a multi-byte character, the program will exit with an error.

    123456
    fn main() {    let s = "你好,世界";    let slice = &s[0..2];    println!("{}",slice);    assert!(slice == "你");}
    123
    thread 'main' panicked at src/main.rs:3:19:byte index 2 is not a char boundary; it is inside '你' (bytes 0..3) of `你好,世界`note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

    String Indexing in Rusts[i]It does not directly return a character because UTF-8 is a variable-length encoding. You need to usechars()orbytes()to iterate over or manipulate the string content:chars()can iterate over Unicode characters, whilebytes()returns individual bytes.

    Rewrite the function to return a slice (the return value of a string slice can be written as: &str)

    1234567891011
    fn first_word(s: &String) -> &str {    let bytes = s.as_bytes();    for (i, &item) in bytes.iter().enumerate() {        if item == b' ' {            return &s[0..i];        }    }    &s[..]}

    Call

    123456789
    fn main() {    let mut s = String::from("hello world");    let word = first_word(&s);    s.clear(); // Error! s needs to be a mutable reference.    println!("the first word is: {}", word);}

    When you have an immutable reference to a value, you cannot also take a mutable reference. Because clear needs to empty the String, it attempts to take a mutable reference. The println! after the call to clear uses the reference in word, so this immutable reference must still be valid at that point. Rust does not allow the mutable reference in clear and the immutable reference in word to exist at the same time, so compilation fails.

    String literals are slices.

    1
    let s = "Hello, world!";

    Here the type of s is &str:It is a slice pointing to a specific location in the binary program., which is why string literals are immutable; &str is an immutable reference.

    String Slices as Parameters

    Now that we know we can take slices of literals and Strings, we improved first_word. Here is its signature:

    fn first_word(s: &String) -> &str {

    A more experienced Rustacean would write the following signature, because it allows the same function to be used with both &String values and &str values:

    fn first_word(s: &str) -> &str {

    If you have a string slice, you can pass it directly. If you have a String, you can pass a slice of the entire String or a reference to the String. This flexibility takes advantage of deref coercions the benefits of defining a function that takes a string slice instead of a String reference, which makes our API more general and does not lose any functionality:

    1234567891011121314151617181920
    fn main() {    let my_string = String::from("hello world");    // `first_word` Applicable to `String`(a slice of), in whole or in full    let word = first_word(&my_string[0..6]);    let word = first_word(&my_string[..]);    // `first_word` also applies to `String` reference to,    // This is equivalent to the entire `String` slice of    let word = first_word(&my_string);    let my_string_literal = "hello world";    // `first_word` applies to string literals, in whole or in part    let word = first_word(&my_string_literal[0..6]);    let word = first_word(&my_string_literal[..]);    // because string literals are already string slices    // This also applies, no slice syntax needed!    let word = first_word(my_string_literal);}

    &String can be implicitly converted to &str type

    12345678910111213
    fn main() {    let mut s = String::from("hello world");    // Here, &s is `&String` type, but `first_character` what the function needs is `&str` type.    // Although the two types are different, the code still works because `&String` will be implicitly converted to `&str` Type    let ch = first_character(&s);    println!("the first character is: {}", ch);    s.clear();}fn first_character(s: &str) -> &str {    &s[..1]}

    slices of other types

    String slices are for strings. However, there are also more general slice types. Consider this array:

    let a = [1, 2, 3, 4, 5];

    Just as we might want to get a part of a string, we might also want to reference a part of an array. We can do this:

    12345
    let a = [1, 2, 3, 4, 5];let slice = &a[1..3];assert_eq!(slice, &[2, 3]);

    The type of this slice is &[i32]. It works the same way as string slices, by storing a reference to the first element of the collection and the total length of the collection. You can use this kind of slice for all other collections.

    Slices are similar to arrays, butthe length of a slice cannot be known at compile time, so youcannot directly use the slice type

    1234567891011121314
    // Fix the errors in the code, do not add new lines of code!fn main() {    let arr = [1, 2, 3];    let s1: [i32] = arr[0..2];    let s2: str = "hello, world" as str;}//After fixingfn main() {    let arr = [1, 2, 3];    let s1: &[i32] = &arr[0..2];    let s2: &str = "hello, world" as &str;}
    • A slice reference occupies2 wordsof memory space (from now on, for the sake of brevity, unless there is a special reason, we will uniformly use ‘slice’ to specifically refer to slice references). The slice’sThe first word is a pointer to the data, and the second word is the length of the slice.
    • The size of a word depends on the processor architecture. For example, on x86-64, a word is 64 bits, or 8 bytes, so a slice reference is 16 bytes in size.
    1234567
    fn main() {    let arr: [char; 3] = ['中', '国', '人'];    let slice = &arr[..2];    assert!(std::mem::size_of_val(&slice) == 16);}
    • A slice (reference) can be used toborrow a contiguous portion of an arrayThe corresponding signature is &[T], which can be compared with the array signature [T; Length].
    123456
    fn main() {   let arr: [i32; 5] = [1, 2, 3, 4, 5];  let slice: &[i32] = &arr[1..4];  assert_eq!(slice, &[2, 3, 4]);}

    Struct

    Defining a struct

    You need to use the struct keyword and provide a name for the entire struct.

    Inside the curly braces, we define the name and type of each piece of data, which we call fieldfield

    123456
    struct User {    active: bool,    username: String,    email: String,    sign_in_count: u64,}

    Instantiation

    12345678
    fn main() {    let user1 = User {        email: String::from("someone@example.com"),        username: String::from("someusername123"),        active: true,        sign_in_count: 1,    };}

    You can mark an entire struct as mutable when instantiating it, but Rust does not allow us to designate a specific field of a struct as mutable.

    12345678910111213
    struct Person {    name: String,    age: u8,}fn main() {    let age = 18;    let mut p = Person {        name: String::from("sunface"),        age,    };    p.age = 30;    p.name = String::from("sunfei");}

    Access

    12345678910
    fn main() {    let mut user1 = User {        email: String::from("someone@example.com"),        username: String::from("someusername123"),        active: true,        sign_in_count: 1,    };    user1.email = String::from("anotheremail@example.com");}

    Once an instance of a struct is mutable, all fields in the instance are mutable.

    Field Init Shorthand

    When the field name and the variable name corresponding to the field value are the same, you can use the field init shorthand.

    12345678
    fn build_user(email: String, username: String) -> User {    User {        email,        username,        active: true,        sign_in_count: 1,    }}

    Struct Update Syntax

    Create a new instance based on an existing struct instance

    12345678910
    fn main() {    // --snip--    let user2 = User {        active: user1.active,        username: user1.username,        email: String::from("another@example.com"),        sign_in_count: user1.sign_in_count,    };}

    Using struct update syntax

    12345678
    fn main() {    // --snip--    let user2 = User {        email: String::from("another@example.com"),        ..user1    };}

    Tuple Structs

    Tuple structs have the meaning provided by the struct name, but do not have specific field names; they only have field types.

    This is suitable for when you want to give the entire tuple a name and make the tuple a different type from other tuples.

    1234567
    struct Color(i32, i32, i32);struct Point(i32, i32, i32);fn main() {    let black = Color(0, 0, 0);    let origin = Point(0, 0, 0);}

    Accessing such a struct is the same as accessing a tuple:

    123456
    struct Point(i32, i32);fn main() {    let p = Point(10, 20);    println!("x = {}, y = {}", p.0, p.1);}

    Unit-like structs with no fields

    Unit-like structs with no fields, which are similar to ()

    12345
    struct AlwaysEqual;fn main() {    let subject = AlwaysEqual;}

    Ownership in structs

    In the definition of the User struct in Listing 5-1, we used the owned String type rather than the &str string slice type. This is a deliberate choice because we wantThis struct owns all of its data.Therefore, as long as the entire struct is valid, its data is also valid.

    It allows a struct to store references to data owned by other objects, but doing so requires using Lifecyclelifetimes

    123456789101112131415
    struct User {    active: bool,    username: &str,    email: &str,    sign_in_count: u64,}fn main() {    let user1 = User {        email: "someone@example.com",        username: "someusername123",        active: true,        sign_in_count: 1,    };}

    Error: missing lifecycle identifier

    1234567891011121314151617181920212223242526272829
    error[E0106]: missing lifetime specifier --> src/main.rs:3:15  |3 |     username: &str,  |               ^ expected named lifetime parameter  |help: consider introducing a named lifetime parameter  |1 ~ struct User<'a> {2 |     active: bool,3 ~     username: &'a str,  |error[E0106]: missing lifetime specifier --> src/main.rs:4:12  |4 |     email: &str,  |            ^ expected named lifetime parameter  |help: consider introducing a named lifetime parameter  |1 ~ struct User<'a> {2 |     active: bool,3 |     username: &str,4 ~     email: &'a str,  |For more information about this error, try `rustc --explain E0106`.error: could not compile `rust_programming` (bin "rust_programming") due to 2 previous errors
    12345678910111213141516
    #[derive(Debug)]struct Rectangle{    width: u32,    length: u32,}fn main() {    let rect=Rectangle{        width:30,        length:50,    };    println!("{}",area(&rect));    println!("{:#?}",rect)}fn area(rect: &Rectangle)->u32{    rect.width*rect.length}

    struct methods

    123456789101112131415161718
    #[derive(Debug)]struct Rectangle{    width: u32,    length: u32,}impl Rectangle{    fn area(&self)->u32{        self.width*self.length    }}fn main() {    let rect=Rectangle{        width:30,        length:50,    };    println!("{}",rect.area());    println!("{:#?}",rect)}
    1. Define methods in an impl block
    2. The first parameter of a method can be &self, or it can take ownership or a mutable borrow, just like other parameters.
    3. Better code organization

    Method invocation operator

    In C/C++, there are two different operators to call methods: . calls a method directly on an object, while -> calls a method on a pointer to an object, in which case the pointer must first be dereferenced. In other words, if object is a pointer, then object->something() is like (*object).something().

    Rust does not have an operator equivalent to ->; instead, Rust has a called Automatic referencing and dereferencing(_automatic referencing and dereferencing_the function of ).Method callis in RustA few places that exhibit this behavior

    It works like this: when calling a method with object.something(), Rust automatically adds &, &mut, or * to object to make it match the method signature. That is, these pieces of code are equivalent:

    12
    p1.distance(&p2);(&p1).distance(&p2);

    This automatic citation behavior works becauseA method has an explicit receiver.———— the type of selfGiven the receiver and method name, Rust can clearly determine whether the method only reads (&self), mutates (&mut self), or takes ownership (self).

    correlation function

    12345678
    impl Rectangle {    fn square(size: u32) -> Self {        Self {            width: size,            height: size,        }    }}

    All functions defined in an impl block are called correlation functionassociated functions

    Associated functions that are not methods are often used as constructors that return a new instance of a struct. These functions are usually named new, but new is not a keyword

    Use the struct name and :: syntax to call this associated function: for example, let sq = Rectangle::square(3);. This function resides in the struct’s namespace: the :: syntax is used for namespaces created by associated functions and modules.

    Each struct is allowed to have multiple impl blocks.

    example

    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
    struct Point {    x: f64,    y: f64,}// `Point` The associated functions of ... are all placed in the following `impl` in the blockimpl Point {    // Associated functions are used in a very similar way to constructors    fn origin() -> Point {        Point { x: 0.0, y: 0.0 }    }    // Another associated function, with two parameters    fn new(x: f64, y: f64) -> Point {        Point { x: x, y: y }    }}struct Rectangle {    p1: Point,    p2: Point,}impl Rectangle {    // This is a method    // `&self` Yes `self: &Self` syntactic sugar for    // `Self` is the type of the current calling object; for this example, `Self` = `Rectangle`    fn area(&self) -> f64 {        // Use the dot operator to access `self` the struct fields in        let Point { x: x1, y: y1 } = self.p1;        let Point { x: x2, y: y2 } = self.p2;        // `abs` is a `f64` method of the type, returns the absolute value of the caller        ((x1 - x2) * (y1 - y2)).abs()    }    fn perimeter(&self) -> f64 {        let Point { x: x1, y: y1 } = self.p1;        let Point { x: x2, y: y2 } = self.p2;        2.0 * ((x1 - x2).abs() + (y1 - y2).abs())    }    // This method requires the caller to be mutable,`&mut self` Yes `self: &mut Self` syntactic sugar for    fn translate(&mut self, x: f64, y: f64) {        self.p1.x += x;        self.p2.x += x;        self.p1.y += y;        self.p2.y += y;    }}// `Pair` holds two integers allocated on the heapstruct Pair(Box<i32>, Box<i32>);impl Pair {    // This method takes ownership of the caller    // `self` Yes `self: Self` syntactic sugar for    fn destroy(self) {        let Pair(first, second) = self;        println!("Destroying Pair({}, {})", first, second);        // `first` and `second` goes out of scope here and is freed    }}fn main() {    let rectangle = Rectangle {        // Associated functions are not called via the dot operator, but using `::`        p1: Point::origin(),        p2: Point::new(3.0, 4.0),    };    // Methods are called via the dot operator    // Note that the method here requires `&self` but we did not use `(&rectangle).perimeter()` The reason for calling is:    // The compiler will automatically take a reference for us.    //  `rectangle.perimeter()` === `Rectangle::perimeter(&rectangle)`    println!("Rectangle perimeter: {}", rectangle.perimeter());    println!("Rectangle area: {}", rectangle.area());    let mut square = Rectangle {        p1: Point::origin(),        p2: Point::new(1.0, 1.0),    };    // Error!`rectangle` is immutable, but this method requires a mutable object.    //rectangle.translate(1.0, 0.0);    // TODO ^ Try uncommenting this line to see what happens.    // Yes! Mutable objects can call mutable methods.    square.translate(1.0, 1.0);    let pair = Pair(Box::new(1), Box::new(2));    pair.destroy();    // Error! The previous `destroy` call took away `pair` the ownership of    //pair.destroy();    // TODO ^ Try uncommenting this line.}

    Enumeration

    Defining an enum

    1234
    enum IpAddrKind {    V4,    V6,}

    By defining an IpAddrKind enum in code to represent this concept and list the possible IP address types, V4 and V6. This is called the enum’s Membervariants):

    When creating an enum, you can use explicitintegerto set the value of the enum variant.

    12345678910111213141516171819202122232425262728293031
    enum Number {    Zero,    One,    Two,}enum Number1 {    Zero = 0,    One,    Two,}// Error, cannot use decimals.//enum Number2 {//    Zero = 0.0,//    One = 1.0,//    Two = 2.0,//}// C-like enumenum Number2 {    Zero = 0,    One = 1,    Two = 2,}fn main() {    // Through the `as` You can cast enum values to integer types.    assert_eq!(Number::One as u8, Number1::One as u8);    assert_eq!(Number1::One as u8, Number2::One as u8);}

    Enum value

    You can create instances of the two different variants of IpAddrKind like this:

    12
    let four = IpAddrKind::V4;   let six = IpAddrKind::V6;

    The values in enum variants can be obtained using pattern matching.

    12345678910111213141516171819
    enum Message {    Quit,    Move { x: i32, y: i32 },    Write(String),    ChangeColor(i32, i32, i32),}fn main() {    let msg = Message::Move{x: 1, y: 1};    if let Message::Move{x:a,y: b} = msg {    // It can also be written as    // if let Message::Move{x, y} = msg {        assert_eq!(a, b);    } else {        panic!("不要让这行代码运行!");    }}

    Attaching data to the enum variant

    Using struct

    12345678910111213141516171819
    enum IpAddrKind {       V4,       V6,   }   struct IpAddr {       kind: IpAddrKind,       address: String,   }   let home = IpAddr {       kind: IpAddrKind::V4,       address: String::from("127.0.0.1"),   };   let loopback = IpAddr {       kind: IpAddrKind::V6,       address: String::from("::1"),   };

    Simply use the enum and put data directly into each enum variant instead of using the enum as part of a struct. The new definition of the IpAddr enum shows that both V4 and V6 variants are associated with String values:

    12345678
    enum IpAddr {       V4(String),       V6(String),   }   let home = IpAddr::V4(String::from("127.0.0.1"));   let loopback = IpAddr::V6(String::from("::1"));

    Wedirectly attach data to each enum variant., so there is no need for an extra struct.

    12345678
    enum IpAddr {       V4(u8, u8, u8, u8),       V6(String),   }   let home = IpAddr::V4(127, 0, 0, 1);   let loopback = IpAddr::V6(String::from("::1"));

    Note that even though the standard library contains a definition for IpAddr, we can still create and use our own definition without conflict because we have not brought the standard library’s definition into scope.

    Enums can embed multiple types.

    123456
    enum Message {    Quit,    Move { x: i32, y: i32 },    Write(String),    ChangeColor(i32, i32, i32),}
    • Quit has no associated data.
    • Move, like a struct, includes named fields.
    • Write contains a single String.
    • ChangeColor contains three i32s.

    Defining methods on enums

    Structs and enums have another similarity: just asyou can use impl to define methods for structs, you can also define methods on enums.. Here is a method named call defined on our Message enum:

    12345678
    impl Message {       fn call(&self) {           // Define the method body here.       }   }   let m = Message::Write(String::from("hello"));   m.call();

    The method body uses self to get the value on which the method is called. In this example, we created a variable m with the value Message::Write(String::from(“hello”)), and that is what self will be in the call method when m.call() is run.

    12345678910111213141516171819202122232425
    #[derive(Debug)]enum TrafficLightColor {    Red,    Yellow,    Green,}// implement TrafficLightColor with a methodimpl TrafficLightColor {    fn color(&self) -> String {        match *self {            TrafficLightColor::Red => "red".to_string(),            TrafficLightColor::Yellow => "yellow".to_string(),            TrafficLightColor::Green => "green".to_string(),        }    }}fn main() {    let c = TrafficLightColor::Yellow;    assert_eq!(c.color(), "yellow");    println!("{:?}", c);}

    The Option Enum

    Defined in the standard library, in the prelude.

    Rust does not have Null, but provides an enum, Option, that is similar to the concept of Null., which is defined in the standard library.

    1234
    enum Option<T> {    None,    Some(T),}

    use

    1234
    let some_number = Some(5);let some_char = Some('e');let absent_number: Option<i32> = None;

    When we have a Some value, we know that a value exists, and that value is stored in the Some. When we have a None value, in a sense, it has the same meaning as null: there is no valid value. So, Option why is it better than null?

    In short, because Option and T (where T can be any type) are different types, the compiler does not allow you to use an Option like a definitely valid value.. For example, this code will not compile because it attempts to add an Option to an i8:

    1234
    let x: i8 = 5;   let y: Option<i8> = Some(5);   let sum = x + y;

    In fact, the error message means that Rust does not know how to add an Option add to i8, because their types are different. When you have a value of a type like i8 in Rust, the compiler ensures that it always has a valid value. We can use it confidently without null checks. Only when using Option(or any type in use) do we need to worry that there might be no value, and the compiler will ensure that we handle the null case before using the value.

    In other words, when dealing with Option before performing operations on T, it must be converted to T.

    To have a value that might be null, you must explicitly put it into the Option of the corresponding type In it. Then, when using this value, you must explicitly handle the case where the value is null. As long as a value is not Option type, you can OK safely assume that its value is not null.

    This is a deliberate design decision in Rust tolimit the proliferation of null valuesto increase the safety of Rust code.

    Implementing a Linked List with Enums

    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
    #![allow(unused)]enum List {    // Cons: a node in the linked list that contains a value. The node is a tuple type; the first element is the value of the node, and the second element is a pointer to the next node.    Cons(u32, Box<List>),    // Nil: the last node in the linked list, used to indicate the end of the list.    Nil,}// Implementing Methods for the Enumimpl List {    // Create an empty linked list.    fn new() -> List {        // Since there are no nodes, return the Nil node directly.        // The type of the enum variant Nil is List.        Nil    }    // Add a new node at the front of the old linked list and return the new linked list.    fn prepend(self, elem: u32) -> List {        Cons(elem, Box::new(self))    }    // Return the length of the linked list.    fn len(&self) -> u32 {        match *self {            // Here we cannot take ownership of tail, so we need to get a reference to it and compute recursively.            Cons(_,ref tail) => 1 + tail.len(),            // The length of an empty linked list is 0.            Nil => 0        }    }    // Return the string representation of the linked list, used for printing output.    fn stringify(&self) -> String {        match *self {            Cons(head, ref tail) => {                // Recursively generate the string.                format!("{}, {}", head, tail.stringify())            },            Nil => {                format!("Nil")            },        }    }}fn main() {    // Create a new linked list (also empty).    let mut list = List::new();    // Add some elements    list = list.prepend(1);    list = list.prepend(2);    list = list.prepend(3);    // Print the current state of the list    println!("链表的长度是: {}", list.len());    println!("{}", list.stringify());}

    pattern matching

    match control flow construct

    Rust has an extremely powerful control flow operator called match that allows us toa valueanda series of patternscompare, and execute the corresponding code according to the matched pattern

    1234567891011121314151617181920212223242526
    enum Coin {    Penny,    Nickel,    Dime,    Quarter,}fn value_in_cents(coin: Coin) -> u8 {    match coin {        Coin::Penny => 1,        Coin::Nickel => 5,        Coin::Dime => 10,        Coin::Quarter => 25,    }}fn value_in_cents(coin: Coin) -> u8 {    match coin {        Coin::Penny => {            println!("Lucky penny!");            1        }        Coin::Nickel => 5,        Coin::Dime => 10,        Coin::Quarter => 25,    }}

    matches!

    matches! looks like match, but it can do something special.

    12345678
    fn main() {    let alphabets = ['a', 'E', 'Z', '0', 'x', '9' , 'Y'];    // fill the blank with `matches!` to make the code work    for ab in alphabets {        assert!(matches!(ab, 'a'..='z' | 'A'..='Z' | '0'..='9'))    }}

    The following code will cause an error because enums do not implement PartialEq by default, so they cannot be compared with ==

    1234567891011121314151617181920212223242526272829303132333435363738
    enum MyEnum {    Foo,    Bar}fn main() {    let mut count = 0;    let v = vec![MyEnum::Foo,MyEnum::Bar,MyEnum::Foo];    for e in v {        if e == MyEnum::Foo { // Fix the error, only modify this line of code.            count += 1;        }    }    assert_eq!(count, 2);}Compiling demo v0.1.0 (C:\Users\cauchy\Desktop\rust\demo)error[E0369]: binary operation `==` cannot be applied to type `MyEnum`  --> src\main.rs:13:14   |13 |         if e == MyEnum::Foo { // Fix the error, only modify this line of code.   |            - ^^ ----------- MyEnum   |            |   |            MyEnum   |note: an implementation of `PartialEq<_>` might be missing for `MyEnum`  --> src\main.rs:3:1   |3  | enum MyEnum {   | ^^^^^^^^^^^ must implement `PartialEq<_>`help: consider annotating `MyEnum` with `#[derive(PartialEq)]`   |3  | #[derive(PartialEq)]   |For more information about this error, try `rustc --explain E0369`.error: could not compile `demo` due to previous error

    Change to

    1234567891011121314151617
    enum MyEnum {    Foo,    Bar}fn main() {    let mut count = 0;    let v = vec![MyEnum::Foo,MyEnum::Bar,MyEnum::Foo];    for e in v {        if matches!(e, MyEnum::Foo) { // Fix the error, only modify this line of code.            count += 1;        }    }    assert_eq!(count, 2);}

    Patterns that bind values

    Another useful feature of match arms is that they can bind to parts of the values that match the pattern. This is how we extract values from enum variants.

    1234567891011121314151617181920212223242526272829
    #[derive(Debug)] // This way we can immediately see the name of the state.enum UsState {    Alabama,    Alaska,    // --snip--}enum Coin {    Penny,    Nickel,    Dime,    Quarter(UsState),}fn value_in_cents(coin: Coin) -> u8 {    match coin {        Coin::Penny => 1,        Coin::Nickel => 5,        Coin::Dime => 10,        Coin::Quarter(state) => {            println!("State quarter from {:?}!", state);            25        }    }}fn main(){    let c= Coin::Quarter(UsState::Alaska);    println!("{}",value_in_cents(c));}

    Matching Option

    1234567891011
    fn main(){    let five = Some(5);    let six = plus_one(five);    let none = plus_one(None);}fn plus_one(x: Option<i32>) -> Option<i32> {     match x {        None => None,        Some(i) => Some(i + 1),     }}//It takes an Option<i32> and if it contains a value, add one to it. If it contains no value, the function should return None and not attempt to perform any operation.

    match must be exhaustive

    useThe _ placeholder(must be placed at the very end)

    12345
    match dice_roll {    3 => add_fancy_hat(),    7 => remove_fancy_hat(),    _ => reroll(),}

    if let concise control flow

    Handle cases where you only care about one pattern match and ignore other matches.

    12345
    let config_max = Some(3u8);match config_max {      Some(max) => println!("The maximum is configured to be {}", max),      _ => (),  }

    Using if let

    1234
    let config_max = Some(3u8);   if let Some(max) = config_max {       println!("The maximum is configured to be {}", max);   }

    In this example, the pattern is Some(max), and max is bound to the value inside the Some. You can then use max within the if let block, just as you would in the corresponding match arm. If the pattern doesn’t match, the code in the if let block won’t execute.

    forgoes exhaustiveness

    You can think of if let as syntactic sugar for match.

    Using it with else

    123456
    let mut count = 0;   if let Coin::Quarter(state) = coin {       println!("State quarter from {:?}!", state);   } else {       count += 1;   }

    Variable shadowing in pattern matching

    1234567891011121314
    fn main() {    let age = Some(30);    if let Some(age) = age { // Create a new variable that is the same as the previous `age` variable with the same name       assert_eq!(age, 30);    } // new `age` The variable goes out of scope here.    match age {        // `match` can also achieve variable shadowing        Some(age) =>  println!("age 是一个新的变量,它的值是 {}",age),        _ => ()    } }//output://age is a new variable, and its value is 30.

    Workspace, Package,Crate,Module

    • PacketPackages): A feature of Cargo that allows you to build, test, and share crates.
    • Crate : A tree structure of modules that forms a library or binary project.
    • moduleModules) and use: Allows you to control the privacy of scopes and paths.
    • pathpath): A way of naming items such as structs, functions, or modules.
    LevelNameDescriptionexample
    📦 Package“Package”, the unit managed by Cargo (a project).Contains one or more crates, andCargo.tomlthe entire project directory
    🧩 Crate“Single compilation unit”, which can be a library or an executable program.Each timerustcwhat is compiled is a cratesrc/lib.rsorsrc/main.rs
    📁 ModuleModule, a structure for organizing code within a crate.Similar to C++ namespaces or Python modules.mod network;
    🛣️ PathPaths used to access modules, structs, and functions.Similarstd::io::Writecrate::foo::bar()

    Crate types

    • binary
    • library

    Each crate is an independent compilation unit.
    The Rust compiler compiles only one crate at a time.

    Crate Root

    Is the source code file from which the Rust compiler starts, forming the root module of the crate.
    Crate root is The starting file for the Rust compiler when building a crate
    It determines module tree the top-level structure of.


    For example,

    Suppose we have a very simple project:

    12345
    my_project/├── Cargo.toml└── src/    ├── main.rs    └── lib.rs

    Both of these files are possible crate root

    filescrate typesFunction
    src/main.rsbinary crate rootProgram entry point (must containfn main()
    src/lib.rslibrary crate rootLibrary entry point (defines the library’s module structure)

    The compiler’s working logic

    When runningcargo build:

    Cargo will:

    1. ReadCargo.toml
    2. Find the current package’s crate roots(e.g.src/main.rssrc/lib.rs);
    3. For each crate root, invoke the compiler, building the entire crate’s module tree starting from this file.

    A Package

    • Contains 1 Cargo.toml, which describes how to build these crates.
    • Can only contain 0-1 library crates.
    • can contain any number of binary crates
    • but mustcontain at least one crate(library or binary)

    Example:

    12345678
    my_package/├── Cargo.toml├── src/│   ├── lib.rs          # library crate│   ├── main.rs         # main binary crate│   └── bin/│       ├── tool1.rs    # second binary crate│       └── tool2.rs    # third binary crate

    No special configuration is needed in Cargo.toml. Run:

    123456
    # main.rscargo run# tool1.rscargo run --bin tool1# tool2.rscargo run --bin tool2

    workspace

    A workspace is a collection of multiple packages, each package can have its own library crate. For example:

    123456789101112
    my_workspace/├── Cargo.toml          # workspace declaration├── app/                # a binary crate package│   ├── Cargo.toml│   └── src/main.rs├── utils/              # a library crate package│   ├── Cargo.toml│   └── src/lib.rs└── network/            # another library crate package    ├── Cargo.toml    └── src/lib.rs

    TOML for the workspace declaration

    12
    [workspace]members = ["app", "utils", "network"]

    Cargo’s conventions

    src/main.rs

    • binary crate’s crate root
    • The crate name is the same as the package name.

    src/lib.rs

    • package contains a library crate
    • library crate’s crate root
    • The crate name is the same as the package name.

    A package can contain both src/main.rs and src/lib.rs.

    A package can have multiple binary crates:

    Files placed in src/bin, each file is a separate binary crate.

    Define modules to control scope and privacy.

    Module

    • Within a crate, group the crate.
    • Control the privacy of items: public, private

    Create a module

    • The mod keyword
    • Can be nested
    • Can contain definitions of other items (struct, enum, constants, traits, functions, etc.)
    123456789101112131415
    mod front_of_house {    mod hosting {        fn add_to_waitlist() {}        fn seat_at_table() {}    }    mod serving {        fn take_order() {}        fn serve_order() {}        fn take_payment() {}    }}

    The module tree of the above code

    123456789
    crate └── front_of_house     ├── hosting     │   ├── add_to_waitlist     │   └── seat_at_table     └── serving         ├── take_order         ├── serve_order         └── take_payment

    src/main.rs and src/lib.rs are called crate roots

    • The contents of these two files (either one) form a module named crate, located at the root of the entire module tree.
    • The entire module tree is under the implicit crate module.

    path

    To find an item in Rust’s modules, you need to usepath

    • absolute pathStarting from the crate root, use the crate name or the literalcrate.
    • Relative pathStarting from the current module, use self, super, or the identifier of the current module.

    A path consists of at least one identifier, with identifiers separated by ::.

    src/lib.rs

    12345678910111213
    mod front_of_house {    mod hosting {        fn add_to_waitlist() {}    }}pub fn eat_at_restaurant() {//Public    // absolute path    crate::front_of_house::hosting::add_to_waitlist();    // Relative path    front_of_house::hosting::add_to_waitlist();}

    Privacy boundary

    • All items in Rust (functions, methods, structs, enums, modules, constants) are private by default
    • Parent modules cannot access the private items of child modules.
    • Can be used in child modulesAllItems in ancestor modules
    • Sibling modulesOKCall each other
    • The pub keyword can mark items as public.

    super

    super: used to access content in the parent module path, similar to … in the file system

    12345678910
    fn serve_order() {}mod back_of_house {    fn fix_incorrect_order() {        cook_order();        super::serve_order();    }    fn cook_order() {}}

    pub struct

    pub before struct

    • the struct is public
    • struct fields are private by defaultAdding pub before a field makes it public

    Filename: src/lib.rs

    123456789101112131415161718192021222324252627
    mod back_of_house {    pub struct Breakfast {        pub toast: String,        seasonal_fruit: String,    }    impl Breakfast {        pub fn summer(toast: &str) -> Breakfast {            Breakfast {                toast: String::from(toast),                seasonal_fruit: String::from("peaches"),            }        }    }}pub fn eat_at_restaurant() {    // In summer, order a rye toast for breakfast    let mut meal = back_of_house::Breakfast::summer("Rye");    // Change your mind and choose a different type of bread    meal.toast = String::from("Wheat");    println!("I'd like {} toast please", meal.toast);    // If you uncomment the next line, the code will not compile;    // You are not allowed to view or modify the seasonal fruit that comes with the breakfast    // meal.seasonal_fruit = String::from("blueberries");}

    pub enum

    Put pub before the enum:

    • the enum is public
    • The variants of an enum are also public by default (no need to add the pub keyword)

    the use keyword

    Use the use keyword to bring paths into scope

    12345678910111213
    mod front_of_house {    pub mod hosting {        pub fn add_to_waitlist() {}    }}use crate::front_of_house::hosting;pub fn eat_at_restaurant() {    hosting::add_to_waitlist();    hosting::add_to_waitlist();    hosting::add_to_waitlist();}
    • Privacy rules still apply
    • Use use to specify relative paths

    Idiomatic usage of use

    • Functions: bring the function’s parent module into scope (specify the parent)

    Filename: src/lib.rs

    12345678910111213
    mod front_of_house {    pub mod hosting {        pub fn add_to_waitlist() {}    }}use self::front_of_house::hosting;pub fn eat_at_restaurant() {    hosting::add_to_waitlist();    hosting::add_to_waitlist();    hosting::add_to_waitlist();}
    • struct, enum, others: specify the full path (specify the item itself)

    File name: src/main.rs

    123456
    use std::collections::HashMap;fn main() {    let mut map = HashMap::new();    map.insert(1, 2);}

    When two items with the same name are brought into scope

    Filename: src/lib.rs

    12345678910
    use std::fmt;use std::io;fn function1() -> fmt::Result {    // --snip--}fn function2() -> io::Result<()> {    // --snip--}

    Use the as keyword to provide a new name

    Filename: src/lib.rs

    12345678910
    use std::fmt::Result;use std::io::Result as IoResult;fn function1() -> Result {    // --snip--}fn function2() -> IoResult<()> {    // --snip--}

    pub use re-export

    After using use to bring a path (name) into scope,that name is private in this scope

    If you want External modules can also access through your path. That item (e.g., function, struct, module, etc.) can be re-exported usingpub use.

    pub use: Re-export

    • Bring items into scope
    • The item can beexternal codebrought into their scope

    pub(in Crate)

    Sometimes we want an item to be visible only to a specific crate, so we can use thepub(in Crate)syntax.

    Example:

    123456789101112131415161718192021
    pub mod a {    pub const I: i32 = 3;    fn semisecret(x: i32) -> i32 {        use self::b::c::J;        x + J    }    pub fn bar(z: i32) -> i32 {        semisecret(I) * z    }    pub fn foo(y: i32) -> i32 {        semisecret(I) + y    }    mod b {        pub(in crate::a) mod c {            pub(in crate::a) const J: i32 = 4;        }    }}

    Using external packages

    1. Add the dependency package in Cargo.toml

    2. Useuseto bring specific items into scope.

    • The standard librarystdis also treated as an external package, but you don’t need to modify Cargo.toml to includestd.
    • You need to useuseto bring specific items fromstdinto the current scope.

    Use nested paths to clean up a large number ofusestatements.

    The same part of the path::{the differing part of the path}

    12
    use std::{cmp::Ordering,io};fn main()

    If one of the twousepaths is a subpath of the other

    Usingself

    123
    //use std::io;//use std::io::Write;use std::io::{self,Write}

    Wildcard *

    Using*can bring all public items in the path into scope.

    Use with caution

    Application scenarios:

    • prelude
    • Testing: bring all the code under test into the tests module.

    Split modules into different files

    When defining a module, if the module name is followed by;instead of a code block,

    • Rust will load the contents from a file with the same name as the module.
    • The module tree will not change.

    Example:

    Filename: src/lib.rs

    123456789
    mod front_of_house;pub use crate::front_of_house::hosting;pub fn eat_at_restaurant() {    hosting::add_to_waitlist();    hosting::add_to_waitlist();    hosting::add_to_waitlist();}

    Example: Declaring frontof_house module, whose content will be located in _src/front_of_house.rs

    src/front_of_house.rs will get front_of_the definition content of the house module, as shown in the example.

    File name: src/front_of_house.rs

    123
    pub mod hosting {    pub fn add_to_waitlist() {}}

    Example:

    Defining modulesfront_of_house

    Method 1: Write module content directly in the file

    file:src/front_of_house.rs

    123
    pub mod hosting {    pub fn add_to_waitlist() {}}
    • Herefront_of_houseThe module directly defines submoduleshosting
    • Advantage: Simple, and can be written this way when the module is small.

    Method 2: Split the submodule into a separate file

    1. Beforefront_of_house.rsOnly declare the submodule in it:
    1
    pub mod hosting;
    1. Create the directory and file structure:
    12345
    src/├── lib.rs├── front_of_house.rs      ← front_of_house 模块的主体└── front_of_house/        ← front_of_house 的子模块目录    └── hosting.rs         ← 子模块 hosting 的实现
    1. Beforehosting.rsWrite the actual content in it:
    1
    pub fn add_to_waitlist() {}
    • At this point,hostingthe content of … is completely placed intohosting.rs
    • Advantage: When the module is large, splitting files is clearer and more maintainable.

    Traditional style

    Before Rust Edition 2018, the following style was used

    1234
    src/└── front_of_house/    ├── mod.rs       // front_of_house module body    └── hosting.rs   // front_of_house's submodule

    Common collections

    Vector

    Vec called vector

    Creating a vector

    The Vec::new function

    let v:Vec=Vec::new();

    Creating a Vec with initial values, using**vec!**Macro

    let v = vec![1,2,3];

    1234567891011121314
    let arr: [u8; 3] = [1, 2, 3];   let v = Vec::from(arr);   is_vec(v);   let v = vec![1, 2, 3];   is_vec(v);   // vec!(..) and vec![..] are the same macro; the macro can use [], (), and {} forms, so...   let v = vec!(1, 2, 3);   is_vec(v);   // ...in the code below, v is Vec<[u8; 3]>, not Vec<u8>   let v1 = vec!(arr);

    Adding elements

    12
    let mut v:Vec<i32>=Vec::new();v.push(1);

    Dropping a Vector

    Like any other struct, a vector is freed when it goes out of scope.

    12345
    {    let v = vec![1, 2, 3, 4];    // Handling variable v} // <- here v goes out of scope and is dropped

    Reading values from a Vector

    • Indexing
    • get method
    123456789
    let v = vec![1, 2, 3, 4, 5];let third: &i32 = &v[2];println!("The third element is {}", third);match v.get(2) {    Some(third) => println!("The third element is {}", third),    None => println!("There is no third element."),}
    • When using the indexing method to access a value beyond the array elements, the program will panic.
    • When using the get method to access, the program returns a None.
    123456789101112131415161718
    fn main() {    let mut v = Vec::from([1, 2, 3]);    for i in 0..5 {        println!("{:?}", v.get(i))    }    for i in 0..5 {        if let Some(x) = v.get(i) {            v[i] = x + 1        } else {            v.push(i + 2)        }    }    assert_eq!(format!("{:?}",v), format!("{:?}", vec![2, 3, 4, 5, 6]));    println!("Success!")}

    Reference borrowing rules

    When we take an immutable reference to the first element of a vector and then try to add an element to the end of the vector, referencing that element later in the function will not work.

    1234567
    let mut v = vec![1, 2, 3, 4, 5];   let first = &v[0];//Immutable borrow   v.push(6);//Mutable borrow   println!("The first element is: {}", first);//Immutable borrow

    Why would a reference to the first element care about changes at the end of the vector? The reason this cannot be done is due to the way vectors work: when adding a new element at the end of the vector, if there is not enough space to store all elements contiguously,it may require allocating new memory and copying the old elements into the new space.. At this point,the reference to the first element would point to freed memory. The borrowing rules prevent the program from getting into this situation.

    Iterating over a Vector

    1234
    let v = vec![100, 32, 57];    for i in &v {        println!("{}", i);    }

    We can also iterate over mutable references to each element of a mutable vector so that we can modify them.

    1234
    let mut v = vec![100, 32, 57];   for i in &mut v {       *i += 50;   }

    To modify the value pointed to by the mutable reference, you must use the dereference operator (*) to get the value in i before using the += operator.

    Extending a Vector

    A Vec can be extended using the extend method.

    1234567891011121314
    fn main() {    let mut v1 = Vec::from([1, 2, 4]);    v1.pop();    v1.push(3);    // v1 is [1,2,3]    let mut v2 = Vec::new();    v2.extend([1, 2, 3]);    // v2 is [1,2,3]    assert_eq!(format!("{:?}",v1), format!("{:?}",v2));    println!("Success!")}

    Using an enum to make Vec store multiple data types

    Define an enum so that different types of data can be stored in a vector.

    1234567891011
    enum SpreadsheetCell {       Int(i32),       Float(f64),       Text(String),   }   let row = vec![       SpreadsheetCell::Int(3),       SpreadsheetCell::Text(String::from("blue")),       SpreadsheetCell::Float(10.12),   ];

    Using trait objects to make Vec store multiple data types

    12345678910111213141516171819202122232425262728293031
    trait IpAddr {    fn display(&self);}struct V4(String);impl IpAddr for V4 {    fn display(&self) {        println!("ipv4: {:?}",self.0)    }}struct V6(String);impl IpAddr for V6 {    fn display(&self) {        println!("ipv6: {:?}",self.0)    }}fn main() {    // Fill in the blanks    let v: Vec<Box<dyn IpAddr>> = vec![        Box::new(V4("127.0.0.1".to_string())),        Box::new(V6("::1".to_string())),    ];    for ip in v {        ip.display();    }}

    Convert type X into a Vec (using the From/Into traits)

    1234567891011121314151617181920212223
    fn main() {    // array -> Vec    let arr = [1, 2, 3];    let v1 = Vec::from(arr);    let v2: Vec<i32> = arr.into();    assert_eq!(v1, v2);    // String -> Vec    let s = "hello".to_string();    let v1: Vec<u8> = s.into();    let s = "hello".to_string();    let v2 = s.into_bytes();    assert_eq!(v1, v2);    let s = "hello";    let v3 = Vec::from(s);    assert_eq!(v2, v3);    println!("Success!") }

    Slices

    Similar to String slices, Vec can also be sliced. If a Vec is mutable, its slice is immutable or read-only; we can obtain a slice with &.

    In Rust,**Passing slices as parameters is a more common usage.**For example, when a function only needs read-only access, passing a slice &[T] / &str of a Vec or String is more appropriate.

    123456789101112131415161718192021
    fn main() {    let mut v = vec![1, 2, 3];    let slice1 = &v[..];    // Out-of-bounds access will cause a panic.    // When modifying, you must use `v.len`    let slice2 = &v[0..v.len()];    assert_eq!(slice1, slice2);    // Slices are read-only.    // Note: slices and `&Vec` are different types; the latter is merely `Vec` a reference to, and can be directly obtained by dereferencing `Vec`    let vec_ref: &mut Vec<i32> = &mut v;    (*vec_ref).push(4);    let slice3 = &mut v[0..];    // slice3.push(4);    assert_eq!(slice3, &[1, 2, 3, 4]);    println!("Success!")}

    Capacity

    Capacity is the already allocated memory space used to store elements that will be added to the Vec in the future. Length, on the other hand, is the number of elements currently stored in the Vec. When adding a new element would cause the length to exceed the existing capacity, the capacity automatically grows: Rust reallocates a larger block of memory and copies the previous Vec over, so a new memory allocation occurs here.

    If this code is executed frequently, frequent memory allocations will greatly affect the performance of our system. The best way is to allocate enough capacity in advance to minimize memory allocations.

    123456789101112131415161718192021222324252627282930
    fn main() {    let mut vec = Vec::with_capacity(10);    assert_eq!(vec.len(), 0);    assert_eq!(vec.capacity(), 10);    // Since enough capacity was set in advance, the loop here will not cause any memory allocation...    for i in 0..10 {        vec.push(i);    }    assert_eq!(vec.len(), 10);    assert_eq!(vec.capacity(), 10);    // ...but the following code will cause new memory allocations.    vec.push(11);    assert_eq!(vec.len(), 11);    assert!(vec.capacity() >= 11);    // Fill in a suitable value, in `for` during the loop's execution, no memory allocation will occur    let mut vec = Vec::with_capacity(100);    for i in 0..100 {        vec.push(i);    }    assert_eq!(vec.len(), 100);    assert_eq!(vec.capacity(), 100);    println!("Success!")}

    As long as From is implemented for Vec trait, then T can be converted into Vec.

    String

    • A string is a collection of bytes
    • UTF-8 encoding
    • Some methods can parse bytes into text

    What is a string?

    At Rust’s core language level, there is only one string type: the string slice str (or &str)

    • String slice: a reference to a UTF-8 encoded string stored elsewhere
    • String literals: stored in the binary, also string slices
    123456789101112131415161718
    fn main() {    let s = String::from("hello, 世界");    let slice1 = &s[0..1];    //Tip: `h` occupies only 1 byte in UTF-8 encoding    assert_eq!(slice1, "h");    let slice2 = &s[7..10];// Tip: `in` occupies 3 bytes in UTF-8 encoding    assert_eq!(slice2, "世");    // Iterate over all characters in s    for (i, c) in s.chars().enumerate() {        if i == 7 {            assert_eq!(c, '世')        }    }    println!("Success!")}

    In fact, String is asmart pointers, which is stored on the stack as a struct, and points to the underlying string data stored on the heap.

    The smart pointer struct stored on the stack consists of three parts: a pointer to the byte array on the heap, the used length, and the allocated capacity (the used length is less than or equal to the allocated capacity; when the capacity is insufficient, memory space will be reallocated).

    12345678910111213141516171819202122
    use std::mem;fn main() {    let story = String::from("Rust By Practice");    // Prevent String's data from being automatically dropped    let mut story = mem::ManuallyDrop::new(story);    let ptr = story.as_mut_ptr();    let len = story.len();    let capacity = story.capacity();    assert_eq!(16, len);    // We can reconstruct String based on the ptr pointer, length, and capacity.    // This operation must be marked as unsafe, because we need to ensure that this operation is safe ourselves.    let s = unsafe { String::from_raw_parts(ptr, len, capacity) };    assert_eq!(*story, s);    println!("Success!")}

    Other string types

    String type

    From the standard library, also UTF-8 encoded.

    String vs Str: Owned or Borrowed Variants

    Other String Types

    OsString / OsStr

    12345
    use std::ffi::OsString;fn main() {    let mut os_string = OsString::from("hello");    os_string.push(" world"); // Mutable}

    Mainly used for handling system strings such as file paths, command-line arguments, environment variables, etc.

    CString / CStr

    123
    use std::ffi::CString;let c_string = CString::new("hello").expect("NUL found!");

    Fromstd::ffi, used for Interacting with C interfaces

    Features

    • With NUL-terminated (\0a string of )
    • CStringOwns the data, guaranteeing no internal NUL.
    • CStris an immutable borrow, similar to&str

    CStringdoes not provide methods likepush_stror index access to modify content; once created, itcannot modify the internal bytes

    Conversion between String and &str

    Using to_string()

    12345678
    fn main() {    let s = "hello, world".to_string();    greetings(s)}fn greetings(s: String) {    println!("{}",s)}

    Using String::from()

    12345678
    fn main() {    let s = String::from("hello, world");    greetings(s)}fn greetings(s: String) {    println!("{}",s)}

    String escaping

    1234567891011121314151617181920
    fn main() {    // You can use escaping to output the characters you want. Here we use hexadecimal values; for example, \x73 is escaped to the lowercase letter 's'.    // Fill in the blanks to output "I'm writing Rust"    let byte_escape = "I'm writing Ru\x73__!";    println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);    // You can also use Unicode escape characters    let unicode_codepoint = "\u{211D}";    let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";    println!("Unicode character {} (U+211D) is called {}",                unicode_codepoint, character_name );    // You can also use \ to concatenate multi-line strings    let long_string = "String literals                        can span multiple lines.                        The linebreak and indentation here \                         can be escaped too!";    println!("{}", long_string);}

    Sometimes there are many characters that need escaping, and we want a more convenient way to write strings: raw strings.

    1234567891011121314151617181920212223242526272829
    fn main() {    let raw_str = r"Escapes don't work here: \x3F \u{211D}";    println!("{}", raw_str);    // If the string contains double quotes, you can add # at the beginning and end    let quotes = r#"And then I said: "There is no escape!""#;    println!("{}", quotes);    // If there is still ambiguity, you can keep adding more; there is no limit    let longer_delimiter = r###"A string with "# in it. And even "##!"###;    println!("{}", longer_delimiter);}fn main() {    let raw_str = "Escapes don't work here: \x3F \u{211D}";    assert_eq!(raw_str, "Escapes don't work here: ? ℝ");    // If you need quotes in a raw string, add a pair of #s    let quotes = r#"And then I said: "There is no escape!""#;    println!("{}", quotes);    // If you need "# in your string, just use more #s in the delimiter.    // You can use up to 65535 #s.    let delimiter = r###"A string with "# in it. And even "##!"###;    println!("{}", delimiter);    // Fill the blank    let long_delimiter = r###"Hello, "##""###;    assert_eq!(long_delimiter, "Hello, \"##\"")}

    Here r#" marks the beginning of a raw string, and "# marks the end of a string. If there is still ambiguity, you can keep adding #.

    Create a new String

    String::new()

    let mut s = String::new();

    Using to_string()

    123456
    let data = "initial contents";   let s = data.to_string();   // This method can also be used directly on string literals:   let s = "initial contents".to_string();

    You can also use string::from()

    let s = String::from(“initial contents”);

    Updating a String

    push_str()

    12
    let mut s = String::from("foo");   s.push_str("bar");

    The push_str() method does not take ownership of the parameter

    123456789
    let mut s1 = String::from("foo");   let s2 = "bar";   s1.push_str(s2);   println!("s2 is {}", s2);let mut s1 = String::from("foo");   let s2 = "bar";   s1.push_str(&s2);   println!("s2 is {}", s2);

    push()

    The push method is defined to take a single character as a parameter and append it to the String

    12
    let mut s = String::from("lo");   s.push('l');

    Concatenating strings with +

    You can onlyStringSame as&strtypes for concatenation, andStringthe ownership of … is moved in this process

    123
    let s1 = String::from("Hello, ");   let s2 = String::from("world!");   let s3 = s1 + &s2; // Note that s1 has been moved and can no longer be used

    The + operator uses the add function, whose signature looks like this:

    1
    fn add(self, s: &str) -> String {

    This is not the actual signature in the standard library;

    But the type of &s2 is &String, not &str. So why does it still compile?

    The reason &s2 can be used in the add call is that &String can be coercedcoerced) to &str. When the add function is called, Rust uses a technique called Deref coercionderef coercion) technique, which you can think of as turning &s2 into &s2[…].

    format!

    12345678910
    let s1 = String::from("tic");   let s2 = String::from("tac");   let s3 = String::from("toe");   let s = s1 + "-" + &s2 + "-" + &s3;let s1 = String::from("tic");   let s2 = String::from("tac");   let s3 = String::from("toe");   let s = format!("{}-{}-{}", s1, s2, s3);

    The code generated by the format! macro uses references, soit does not take ownership of any parameters

    s1,s2,s3they are all borrows.

    Rust will Copy the contents to a new string(allocated on the heap):

    • The original string is not affected.
    • The copy here is necessary because a new independent string is generated.

    Accessing by indexString

    Rust’s stringsdoes not support indexing syntaxAccess

    Internal representation

    String is a Vec a wrapper for.

    let hello = String::from(“Hola”);

    Here, the value of len is 4, which means the Vec storing the string “Hola” is four bytes long: each letter’s UTF-8 encoding takes up one byte.

    (Note that the first letter in this string is the Cyrillic letter Ze, not the Arabic numeral 3.)

    1
    let hello = String::from("Здравствуйте");

    When asked how long this string is, someone might say 12. However, Rust’s answer is 24. This is the number of bytes needed to encode “Здравствуйте” in UTF-8, because each Unicode scalar value requires two bytes of storage.

    Therefore, an index into a string’s bytes does not always correspond to a valid Unicode scalar value.

    Byte strings

    Byte strings, or byte arrays

    1234567891011121314151617181920212223242526272829303132333435363738
    use std::str;fn main() {    // Note that this is not `&str` type!    let bytestring: &[u8; 21] = b"this is a byte string";    // Byte arrays do not implement `Display` trait, so only `Debug` way to print    println!("A byte string: {:?}", bytestring);    // Byte arrays can also use escapes    let escaped = b"\x52\x75\x73\x74 as bytes";    // ...but do not support Unicode escapes    // let escaped = b"\u{211D} is not allowed";    println!("Some escaped bytes: {:?}", escaped);    // raw string    let raw_bytestring = br"\u{211D} is not escaped here";    println!("{:?}", raw_bytestring);    // Converting byte arrays to `str` type may fail    if let Ok(my_str) = str::from_utf8(raw_bytestring) {        println!("And the same as text: '{}'", my_str);    }    let _quotes = br#"You can also use "fancier" formatting, \                    like with normal raw strings"#;    // Byte arrays may not be in UTF-8 format    let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // "ようこそ" in SHIFT-JIS    // but they may not necessarily be convertible to `str` Type    match str::from_utf8(shift_jis) {        Ok(my_str) => println!("Conversion successful: '{}'", my_str),        Err(e) => println!("Conversion failed: {:?}", e),    };}

    Bytes, scalar values, grapheme clusters

    Rust has three ways of looking at strings:

    • Bytes
    123456
    fn main(){    let w = "नमस्ते";    for b in w.bytes(){        println!("{}",b);    }}

    output

    123456789101112131415161718
    224164168224164174224164184224165141224164164224165135
    • Scalar Values
    123456
    fn main(){    let w = "नमस्ते";    for b in w.chars(){        println!("{}",b);    }}

    output

    123456
    • Grapheme Clusters (closest to what are called letters)

    Obtaining them is more complex; the standard library does not provide this, so you need to rely on third-party libraries.

    One reason Rust does not allow indexing into a String:

    Indexing operations should take constant time O(1)

    But String cannot guarantee this: it needs to traverse all the content to determine how many valid characters there are.

    Slicing Strings

    The type that string indexing should return is ambiguous: a byte value, a character, a grapheme cluster, or a string slice. Therefore, if you really want to use indexing to create a string slice, Rust requires you to be more explicit. To be more explicit about indexing and indicate that you need a string slice, instead of using [] with a single value, you can use [] with a range to create a string slice containing specific bytes:

    123
    let hello = "Здравствуйте";let s = &hello[0..4];

    s will be a &str that contains the first four bytes of the string. Earlier, we mentioned that these letters are each two bytes long, so this means s will be “Зд”.

    What happens if you get &hello[0…1]? The answer is: Rust will panic at runtime.

    Therefore,Slicing must not cross string boundaries.

    Iterating over Strings

    You cannot access a character in a string by indexing, but you can use slicing with &s1[start…end]; however, start and end must fall exactly on character boundaries.

    • For scalar values: the chars() method
    • For bytes: the bytes() method,
    • For grapheme clusters: it’s very complex, and the standard library does not provide it. Neither Chinese nor English requires attention to grapheme clusters; you can use chars to iterate over Chinese and English.
    12345678910111213141516171819
    fn main() {    let s1 = String::from("hi,中国");    let h = &s1[0..1];    assert_eq!(h, "h");    let h1 = &s1[3..6];    assert_eq!(h1, "中");}fn main() {    for c in "你好,世界".chars() {        println!("{}", c)    }}//output://you//good//,//world//world

    HashMap

    HashMap uses the SipHash 1-3 hashing algorithm by default, which is very effective at resisting HashDoS attacks. In terms of performance, if your keys are medium-sized, this algorithm is very good, but if your keys are small (such as integers) or large (such as strings), you need to use other algorithms provided by the community to improve performance.

    The hash table’s algorithm is based on Google’s SwissTable, you canHerefind the C++ implementation.

    Creating HashMap<K,V>

    Creating an empty HashMap: new() function

    1234567
        use std::collections::HashMap;    let mut scores = HashMap::new();//let mut scores:HashMap<String,i32> = HashMap::new();    scores.insert(String::from("Blue"), 10);    scores.insert(String::from("Yellow"), 50);
    • HashMap is used less often,not in the Prelude
    • the standard library has less support for it,there is no built-in macro to create a HashMap
    • data is stored on the heap
    • homogeneous, i.e., K must be one type and V another type
    12345678910111213141516171819
    use std::collections::HashMap;fn main() {    let teams = [        ("Chinese Team", 100),        ("American Team", 10),        ("France Team", 50),    ];    let mut teams_map1 = HashMap::new();    for team in &teams {        teams_map1.insert(team.0, team.1);    }    let teams_map2: HashMap<_,_> = teams.into_iter().collect();    // let teams_map2 = HashMap::from(teams);    assert_eq!(teams_map1, teams_map2);    println!("Success!")}

    collect method creates a HashMap

    The collect method can gather data into a variety of collection types

    1234567
    use std::collections::HashMap;    let teams = vec![String::from("Blue"), String::from("Yellow")];    let initial_scores = vec![10, 50];    let mut scores: HashMap<_, _> =        teams.into_iter().zip(initial_scores.into_iter()).collect();

    If the team names and initial scores are in two separate vectors, you can use zip method tocreate an iterator of tuples, where “Blue” and 10 are a pair, and so on. Then you can use the collect method to convert this iterator of tuples into a HashMap.

    HashMap and Ownership

    • For types that implement the Copy trait (such as i32), the values are copied into the HashMap.
    • For values that own ownership (such as String), the values are moved, and ownership is transferred to the HashMap.
    • If you insert references into a HashMap, the values themselves do not move, but the referenced values must remain valid for as long as the HashMap is valid.

    Accessing values in a HashMap

    get method

    123456789
    use std::collections::HashMap;    let mut scores = HashMap::new();    scores.insert(String::from("Blue"), 10);    scores.insert(String::from("Yellow"), 50);    let team_name = String::from("Blue");    let score = scores.get(&team_name);

    for loop to iterate over a HashMap

    12345678910
    use std::collections::HashMap;    let mut scores = HashMap::new();    scores.insert(String::from("Blue"), 10);    scores.insert(String::from("Yellow"), 50);    for (key, value) in &scores {        println!("{}: {}", key, value);    }

    This will be inarbitrary orderPrint out each key-value pair:

    12
    Yellow: 50Blue: 10

    Indexing and the get Method

    12345678910111213141516171819202122232425
    use std::collections::HashMap;fn main() {    let mut scores = HashMap::new();    scores.insert("Sunface", 98);    scores.insert("Daniel", 95);    scores.insert("Ashley", 69);    scores.insert("Katie", 58);    // get returns an Option<&V> enum value    let score = scores.get("Sunface");    assert_eq!(score, Some(&98));    if scores.contains_key("Daniel") {        // Indexing returns a value V        let score = scores["Daniel"];        assert_eq!(score, 95);        scores.remove("Daniel");    }    assert_eq!(scores.len(), 3);    for (name, score) in scores {        println!("The score of {} is {}", name, score)    }}

    Updating a HashMap

    Overwriting a value

    12345678
    use std::collections::HashMap;    let mut scores = HashMap::new();    scores.insert(String::from("Blue"), 10);    scores.insert(String::from("Blue"), 25);    println!("{:?}", scores);

    This will print {“Blue”: 25}. The original value 10 has been overwritten.

    Inserting only if the key has no value

    Using the entry methodInsert only if the key does not already have a value

    1234567891011
    use std::collections::HashMap;    let mut scores = HashMap::new();    scores.insert(String::from("Blue"), 10);	let e=scores.entry(String::from("Yellow"));	println!("{:?}",e);	e.or_insert(50);    scores.entry(String::from("Blue")).or_insert(50);    println!("{:?}", scores);

    Output:

    12
    Entry(VacantEntry("Yellow")){"Blue": 10, "Yellow": 50}

    The entry method

    Checks whether the specified K corresponds to a V

    Takes K as a parameter and returns an enum Entry: representing whether the value exists

    Entry’sor_insert() method:

    Returns:

    • If K exists, returns a mutable reference to the corresponding V
    • If K does not exist, inserts the method parameter as the new value for K, and returns a mutable reference to this value

    Updating a value based on the old value

    123456789101112
    use std::collections::HashMap;    let text = "hello world wonderful world";    let mut map = HashMap::new();    for word in text.split_whitespace() {        let count = map.entry(word).or_insert(0);        *count += 1;    }    println!("{:?}", map);

    Here or_insert returns a mutable reference pointing to the value corresponding to the key just inserted by the entry method

    Hash function

    HashMap by default uses a hashing function called SipHash, which can resist hash table1 denial of service (DoS) attacks. However, this is not the fastest algorithm available, but it is worth paying some performance cost for higher security. If performance monitoring shows that this hashing function is very slow, so much so that you cannot accept it, you can specify a different hasher to switch to another function. A hasher is a type that implements the BuildHasher trait. We don’t need to implement your own hasher from scratch; there are many libraries of hashers for common hashing algorithms on crates.io.

    HashMap key restrictions

    Any type that implements the Eq and Hash traits can be used as a key in HashMap, including:

    • bool (although rarely used, because it can only represent two kinds of keys)
    • int, uint, and their variants, such as u8, i32, etc.
    • String and &str (tip: when the key of a HashMap is of type String, you can actually use &str with the get method to query

    It should be noted that f32 and f64 do not implement Hash, because Floating-point precision The problem would cause them to be unable to perform equality comparison.

    If all fields of a collection type implement Eq and Hash, then the collection type will automatically implement Eq and Hash. For example, Vec To implement Hash, T must first implement Hash.

    12345678910111213141516171819202122232425262728293031
    // Tip: `derive` is a good way to implement some common traits.use std::collections::HashMap;#[derive(Hash, Eq, PartialEq, Debug)]struct Viking {    name: String,    country: String,}impl Viking {    fn new(name: &str, country: &str) -> Viking {        Viking {            name: name.to_string(),            country: country.to_string(),        }    }}fn main() {    // Use HashMap to store viking's health    let vikings = HashMap::from([        (Viking::new("Einar", "Norway"), 25),        (Viking::new("Olaf", "Denmark"), 24),        (Viking::new("Harald", "Iceland"), 12),    ]);    // Use derive to print viking's current state    for (viking, health) in &vikings {        println!("{:?} has {} hp", viking, health);    }}

    Capacity

    Regarding capacity, we have already introduced it in detail in the previous Vector section, and HashMap can also adjust capacity: you can initialize it with a specified capacity via HashMap::with_capacity(uint), or use HashMap::new(), which provides a default initial capacity.

    123456789101112131415161718
    use std::collections::HashMap;fn main() {    let mut map: HashMap<i32, i32> = HashMap::with_capacity(100);    map.insert(1, 2);    map.insert(3, 4);    // In fact, although we used a capacity of 100 to initialize, the map's capacity is likely to be more than 100.    assert!(map.capacity() >= 100);    // To shrink the capacity, the value you provide is only an allowed minimum. In fact, Rust will automatically set it based on the current amount of stored data. Of course, this value will be as close as possible to the value you provide, and it may also reserve some adjustment space.    map.shrink_to(50);    assert!(map.capacity() >= 50);    // Let Rust adjust to an appropriate value by itself; the remaining strategy is the same as above.    map.shrink_to_fit();    assert!(map.capacity() >= 2);    println!("Success!")}

    Ownership

    For types that implement the Copy trait, such as i32, the value of that type will be copied into the HashMap. For types with ownership, such as String, the ownership of their values will be transferred into the HashMap.

    12345678910111213141516
    use std::collections::HashMap;fn main() {  let v1 = 10;  let mut m1 = HashMap::new();  m1.insert(v1, v1);  println!("v1 is still usable after inserting to hashmap : {}", v1);  let v2 = "hello".to_string();  let mut m2 = HashMap::new();  // Ownership is transferred here.  m2.insert(v2, v1);  // After this, v2 can no longer be used because it has been moved into the HashMap.  // assert_eq!(v2, "hello"); // compile error: borrow of moved value  println!("Success!")}

    Third-party Hash libraries

    As mentioned earlier, if the performance of the existing SipHash 1-3 cannot meet the requirements, you can use alternative algorithms provided by the community.

    For example, one of the community libraries is used as follows:

    123456789
    use std::hash::BuildHasherDefault;use std::collections::HashMap;// Introduce a third-party hash functionuse twox_hash::XxHash64;let mut hash: HashMap<_, _, BuildHasherDefault<XxHash64>> = Default::default();hash.insert(42, "the answer");assert_eq!(hash.get(&42), Some(&"the answer"));

    Error handling

    In most cases, errors are reported at compile time and handled.

    Error classification

    • Recoverable:

    For example, file not found, can try again

    • Unrecoverable

    bug, e.g., accessing an index out of bounds

    Rust does not have an exception mechanism like C++/Java

    • Recoverable errors:Result<T,E>
    • Unrecoverable: panic! macro

    Unrecoverable errors and panic!

    When the panic! macro executes

    • The program prints an error message
    • Unwind, cleaning up the call stack
    • Exits the program

    panic = ‘abort’

    By default, when a panic occurs

    • The program unwinds the call stack (large amount of work)

    Rust walks back along the call stack, cleaning up data in each function it encounters

    • Or immediately abort the call stack

    Without cleaning up, directly stops the program; memory needs to be cleaned up by the OS

    To make the binary smaller, change the setting from ‘unwind’ to ‘abort’

    12
    [profile.release]panic = 'abort'

    Panic in your own code

    123
    fn main(){    panic!("crash and burn");}

    So in dependent code: external panic

    1234
    fn main(){    let vector=vec![1,2,3];    vector[100];}

    output

    123456
    Compiling demo v0.1.0 (C:\Users\cauchy\Desktop\rust\demo)    Finished dev [unoptimized + debuginfo] target(s) in 0.18s     Running `target\debug\demo.exe`thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', src\main.rs:3:5note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceerror: process didn't exit successfully: `target\debug\demo.exe` (exit code: 101)

    Locate the code causing the problem through the backtrace information of the function that called panic!

    Run the code with the following command (Windows cmd)

    1
    set RUST_BACKTRACE=1 && cargo run

    Windows Poweshell

    1
    $Env:RUST_BACKTRACE=1 -and (cargo run)

    On Linux

    1
    export RUST_BACKTRACE=1 && cargo run

    More detailed information

    RUST_BACKTRACE=full

    To get a backtrace with debug information, debug symbols must be enabled (without --release).

    Result enum and recoverable errors

    1234
    enum Result<T, E> {    Ok(T),    Err(E),}

    T represents the type of the data in the Ok member returned on success,

    and E represents the type of the error in the Err member returned on failure.

    Result and its variants are also brought into scope by the prelude.

    Opening a file

    12345678910
    use std::fs::File;fn main() {    let f = File::open("hello.txt");    let f = match f {        Ok(file) => file,        Err(error) => panic!("Problem opening the file: {:?}", error),    };}

    Matching different errors

    12345678910111213141516171819
    use std::fs::File;use std::io::ErrorKind;fn main() {    let f = File::open("hello.txt");    let f = match f {        Ok(file) => file,        Err(error) => match error.kind() {            ErrorKind::NotFound => match File::create("hello.txt") {                Ok(fc) => fc,                Err(e) => panic!("Problem creating the file: {:?}", e),            },            other_error => {                panic!("Problem opening the file: {:?}", other_error)            }        },    };}

    use unrap_or_else() and Closure

    1234567891011121314
    use std::fs::File;use std::io::ErrorKind;fn main() {    let f = File::open("hello.txt").unwrap_or_else(|error| {        if error.kind() == ErrorKind::NotFound {            File::create("hello.txt").unwrap_or_else(|error| {                panic!("Problem creating the file: {:?}", error);            })        } else {            panic!("Problem opening the file: {:?}", error);        }    });}

    unwrap

    A shortcut method for match expressions

    12345
    use std::fs::File;fn main() {    let f = File::open("hello.txt").unwrap();}

    equivalent to

    12345678910
    use std::fs::File;fn main() {    let f = File::open("hello.txt");    let f = match f {        Ok(file) => file,        Err(error) => panic!("Problem opening the file: {:?}", error),    };}
    • If the Result is Ok, return the value inside Ok.
    • If the Result is Err, call the panic! macro.

    expect

    unwrap with a custom error message

    12345
    use std::fs::File;fn main() {    let f = File::open("hello.txt").expect("无法打开文件");}

    Propagating errors

    Propagating errors to the caller

    Custom implementation

    1234567891011121314151617181920
    // src/main.rsuse std::fs::File;use std::io::{self, Read};fn read_username_from_file() -> Result<String, io::Error> {    let f = File::open("hello.txt");    let mut f = match f {        Ok(file) => file,        Err(e) => return Err(e),    };    let mut s = String::new();    match f.read_to_string(&mut s) {        Ok(_) => Ok(s),        Err(e) => Err(e),    }}

    The ? operator

    12345678910
    use std::fs::File;use std::io;use std::io::Read;fn read_username_from_file() -> Result<String, io::Error> {    let mut f = File::open("hello.txt")?;    let mut s = String::new();    f.read_to_string(&mut s)?;    Ok(s)}

    The ? operator is defined to work in exactly the same way as the match expression for handling Result values defined in the example of custom error propagation.

    If the Result value is Ok, this expression will return the value inside Ok and the program will continue executing.

    If the value is Err, the value inside Err will be the return value of the entire function,as if using thereturnkeyword, so the error value is propagated to the caller.

    1234567891011121314151617181920212223242526272829
    use std::fs::File;use std::io::{self, Read};fn read_file1() -> Result<String, io::Error> {    let f = File::open("hello.txt");    let mut f = match f {        Ok(file) => file,        Err(e) => return Err(e),    };    let mut s = String::new();    match f.read_to_string(&mut s) {        Ok(_) => Ok(s),        Err(e) => Err(e),    }}fn read_file2() -> Result<String, io::Error> {    let mut s = String::new();    File::open("hello.txt")?.read_to_string(&mut s)?;    Ok(s)}fn main() {    assert_eq!(read_file1().unwrap_err().to_string(), read_file2().unwrap_err().to_string());    println!("Success!")}

    ? and the from function

    The from function on the trait std::convert::From

    • Used for converting between errors.
    • Errors to which ? is applied are implicitly handled by the from function.
    • When ? calls the from function:

    The error type it receives is converted to the error type defined by the current function’s return type.

    Used for: Return the same error type for different error causes.

    As long as each error type implements the from function that converts to the returned error type.

    Can be used directly after ?Chained method callsto further shorten the code

    File name: src/main.rs

    1234567891011
    use std::fs::File;use std::io;use std::io::Read;fn read_username_from_file() -> Result<String, io::Error> {    let mut s = String::new();    File::open("hello.txt")?.read_to_string(&mut s)?;    Ok(s)}

    Custom implementation

    12345678910111213141516171819202122232425262728293031323334
    use std::fmt;use std::fs::File;use std::io::{self, Read};#[derive(Debug)]enum MyError {    Io(io::Error),    Parse,}// Implement Display for MyError (optional but common)impl fmt::Display for MyError {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        match self {            MyError::Io(e) => write!(f, "IO error: {}", e),            MyError::Parse => write!(f, "Parse error"),        }    }}impl From<io::Error> for MyError {    fn from(error: io::Error) -> MyError {        MyError::Io(error)    }}fn read_username_from_file() -> Result<String, MyError> {    let mut s = String::new();    File::open("hello.txt")?.read_to_string(&mut s)?;    Ok(s)}

    ? and the main function

    The return type of the main function is ()

    1234567
    use std::error::Error;use std::fs::File;fn main() -> Result<(), Box<dyn Error>> {    let f = File::open("hello.txt")?;    Ok(())}

    map,and_then

    map example — modifyingOkthe value inside

    Only handlesOkthe value and returns a newResult, without changing the error type

    123
    let x: Result<i32, &str> = Ok(2);let y = x.map(|n| n * 3);assert_eq!(y, Ok(6));

    What if it is an error?

    123
    let x: Result<i32, &str> = Err("error");let y = x.map(|n| n * 3);assert_eq!(y, Err("error"));  // The error is returned as is.

    and_then example — chained Result operations

    and_thenHandlesOk, and continues to return aResult(chained logic), suitable for chaining multiple fallible steps:

    12345678910
    fn sq_then_to_string(x: i32) -> Result<String, &'static str> {    if x < 0 {        Err("negative")    } else {        Ok((x * x).to_string())    }}let result = Ok(3).and_then(sq_then_to_string);assert_eq!(result, Ok("9".to_string()));

    If an error occurs, automatically stop the chained computation:

    12
    let result = Err("bad").and_then(sq_then_to_string);assert_eq!(result, Err("bad"));

    Example:

    123456789101112131415161718192021222324252627282930313233343536373839404142434445
    use std::num::ParseIntError;// With the return type rewritten, we use pattern matching without `unwrap()`.// But it's so Verbose..fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {    match n1_str.parse::<i32>() {        Ok(n1)  => {            match n2_str.parse::<i32>() {                Ok(n2)  => {                    Ok(n1 * n2)                },                Err(e) => Err(e),            }        },        Err(e) => Err(e),    }}// Rewriting `multiply` to make it succinct// You  MUST USING `and_then` and `map` herefn multiply1(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {    // IMPLEMENT...    n1_str.parse::<i32>().and_then(|n1| {        n2_str.parse::<i32>().map(|n2| n1 * n2)    })}fn print(result: Result<i32, ParseIntError>) {    match result {        Ok(n)  => println!("n is {}", n),        Err(e) => println!("Error: {}", e),    }}fn main() {    // This still presents a reasonable answer.    let twenty = multiply1("10", "2");    print(twenty);    // The following now provides a much more helpful error message.    let tt = multiply("t", "2");    print(tt);    println!("Success!")}

    When to panic!

    General Principles

    • When defining afunction that might fail, prefer returning Result
    • Otherwise, panic!

    Scenario

    • Demonstrating some concepts: unwrap
    • Prototype code: unwrap, expect
    • Tests: unwrap, expect

    Sometimes you have more information than the compiler

    • You can be sure that the Result is Ok:unwrap
    12
    use std::net::IpAddr;  let home: IpAddr = "127.0.0.1".parse().unwrap();

    Calling your code with meaningless argument values:panic!

    • Calling external code you don’t control, which returns an invalid state that you cannot fix:panic!

    • If failure is expected:Result

    • When your code operates on values, you should first validate those values:panic! (assert!)

    Creating custom types for validation

    One way to implement this is to parse the guess as an i32 instead of only a u32, to allow negative numbers, and then check whether the number is in range:

    12345678910111213141516
    loop {        // --snip--        let guess: i32 = match guess.trim().parse() {            Ok(num) => num,            Err(_) => continue,        };        if guess < 1 || guess > 100 {            println!("The secret number will be between 1 and 100.");            continue;        }        match guess.cmp(&secret_number) {            // --snip--    }

    The if expression checks whether the value is out of range, tells the user what went wrong, and calls continue to start the next loop iteration, asking for another guess. After the if expression, you can compare the guess with the secret number, knowing that guess is between 1 and 100.

    Instead, we can create a new type to put the validation in a function that creates an instance of it, rather than repeating these checks everywhere. This way, we can safely use the new type in function signatures and trust the values they receive. The example shows a way to define a Guess type that only creates an instance of Guess when the new function receives a value between 1 and 100:

    1234567891011121314151617
    pub struct Guess {    value: i32,}impl Guess {    pub fn new(value: i32) -> Guess {        if value < 1 || value > 100 {            panic!("Guess value must be between 1 and 100, got {}.", value);        }        Guess { value }    }    pub fn value(&self) -> i32 {        self.value    }}

    We implemented a method named value that borrows self, takes no other parameters, and returns an i32. This kind of method is sometimes called _getter_because its purpose is to return the data of the corresponding field. Such a public method is necessary because the value field of the Guess struct is private. The private value field is important so that code using the Guess struct is not allowed to set value directly: callers Must must use the Guess::new method to create an instance of Guess, which ensures that there will not be a Guess whose value has not passed the condition check in the Guess::new function.

    Thus, a function that takes (or returns) a number between 1 and 100 can be declared to take (or return) an instance of Guess instead of an i32, and its function body does not need to perform any additional checks.

    Using Result in fn main

    A typical main function looks like this:

    123
    fn main() {    println!("Hello World!");}

    In fact, the main function can also return a Result type: if an error occurs inside the main function, that error will be returned and a debug message about the error will be printed.

    1234567891011
    use std::num::ParseIntError;fn main() -> Result<(), ParseIntError> {    let number_str = "10";    let number = match number_str.parse::<i32>() {        Ok(number)  => number,        Err(e) => return Err(e),    };    println!("{}", number);    Ok(())}

    Generics

    Defining generics in functions

    Finding the maximum value in a vec

    12345678910111213141516171819202122232425262728
    fn largest_i32(list: &[i32]) -> i32 {    let mut largest = list[0];    for &item in list {        if item > largest {            largest = item;        }    }    largest}fn largest_char(list: &[char]) -> char {    let mut largest = list[0];    for &item in list {        if item > largest {            largest = item;        }    }    largest}fn main() {    let number_list = vec![34, 50, 25, 100, 65];    let result = largest_i32(&number_list);    println!("The largest number is {}", result);    let char_list = vec!['y', 'm', 'a', 'q'];    let result = largest_char(&char_list);    println!("The largest char is {}", result);}

    Using generics

    123456789101112131415161718
    fn largest<T>(list: &[T]) -> T {    let mut largest = list[0];    for &item in list {        if item > largest {            largest = item;        }    }    largest}fn main() {    let number_list = vec![34, 50, 25, 100, 65];    let result = largest(&number_list);    println!("The largest number is {}", result);    let char_list = vec!['y', 'm', 'a', 'q'];    let result = largest(&char_list);    println!("The largest char is {}", result);}

    Run

    12345678910111213141516
    Compiling demo v0.1.0 (C:\Users\cauchy\Desktop\rust\demo)error[E0369]: binary operation `>` cannot be applied to type `T` --> src\main.rs:5:17  |5 |         if item > largest {  |            ---- ^ ------- T  |            |  |            T  |help: consider restricting type parameter `T`  |1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {  |             ++++++++++++++++++++++For more information about this error, try `rustc --explain E0369`.error: could not compile `demo` due to previous error

    In simple terms, this error indicates that the body of largest cannot work for all possible types of T.

    Because the function body needs to compare values of type T, but it can only be used for types that we know how to order. To enable comparison, the std::cmp::PartialOrd trait defined in the standard library can implement comparison for types.

    Modification:

    12345678910111213141516171819202122
    fn largest<T>(list: &[T]) -> &Twhere    T: PartialOrd,{    let mut largest = &list[0];    for item in list {        if *item > *largest {// Here, adding * is not necessary, because Rust implements PartialOrd for reference types, provided that the referenced type T: PartialOrd.            largest = item;        }    }    largest}fn main() {    let number_list = vec![34, 50, 25, 100, 65];    let result = largest(&number_list);    println!("The largest number is {}", result);    let char_list = vec!['y', 'm', 'a', 'q'];    let result = largest(&char_list);    println!("The largest char is {}", result);}

    Defining generics in structs

    File name: src/main.rs

    123456789
    struct Point<T> {    x: T,    y: T,}fn main() {    let integer = Point { x: 5, y: 10 };    let float = Point { x: 1.0, y: 4.0 };}

    Defining generics in enums

    1234
    enum Option<T> {    Some(T),    None,}

    Enums can also have multiple generic types.

    1234
    enum Result<T, E> {    Ok(T),    Err(E),}

    Generics in method definitions

    File name: src/main.rs

    1234567891011121314151617181920
    struct Point<T> {    x: T,    y: T,}impl<T> Point<T> {    fn x(&self) -> &T {        &self.x    }}impl Point<i32> {    fn x(&self) -> &i32 {        &self.x    }}fn main() {    let p = Point { x: 5, y: 10 };    println!("p.x = {}", p.x());}
    • Putting T after the impl keyword indicates implementing methods on type T: impl Point
    • Implementing methods only for a concrete type: impl Point
    123456789101112131415
    struct Point<T> {    x: T,    y: T,}impl Point<f32> {    fn distance_from_origin(&self) -> f32 {        (self.x.powi(2) + self.y.powi(2)).sqrt()    }}fn main() {    let p = Point{x: 5.0_f32, y: 10.0_f32};    println!("{}",p.distance_from_origin())}
    • Generic type parameters in a struct can be different from the generic type parameters in its methods.
    12345678910111213141516171819202122
    struct Point<X1, Y1> {    x: X1,    y: Y1,}impl<X1, Y1> Point<X1, Y1> {    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {        Point {            x: self.x,            y: other.y,        }    }}fn main() {    let p1 = Point { x: 5, y: 10.4 };    let p2 = Point { x: "Hello", y: 'c' };    let p3 = p1.mixup(p2);    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);}

    Const generics

    Generics are implemented for types; all generics are for abstracting over different types. But is there a generic for values? The answer is const generics.

    1234567891011121314151617181920212223
    struct ArrayPair<T, const N: usize> {    left: [T; N],    right: [T; N],}impl<T: Debug, const N: usize> Debug for ArrayPair<T, N> {    // ...}fn foo<const N: usize>() {}fn bar<T, const M: usize>() {    foo::<M>(); // ok: matches the first kind    foo::<2021>(); // ok: matches the second kind    foo::<{20 * 100 + 20 * 10 + 1}>(); // ok: matches the third kind    foo::<{ M + 1 }>(); // error: violates the third kind; const expressions cannot contain generic parameter M    foo::<{ std::mem::size_of::<T>() }>(); // error: the generic expression contains generic parameter T    let _: [u8; M]; // ok: matches the first kind    let _: [u8; std::mem::size_of::<T>()]; // error: the generic expression contains generic parameter T}fn main() {}
    • Currently, const generic parameters can only use arguments of the following forms:

      • a single const generic parameter

      • a literal (i.e. an integer, boolean, or character).

      • a concrete const expression (the expression cannot contain any generic parameters)

    • const generics can also help us avoid some runtime checks and improve performance

    1234567891011121314151617181920
    pub struct MinSlice<T, const N: usize> {    pub head: [T; N],    pub tail: [T],}fn main() {    let slice: &[u8] = b"Hello, world";    let reference: Option<&u8> = slice.get(6);    // We know `.get` The returned value is `Some(b' ')`    // but the compiler doesn't know    assert!(reference.is_some());    let slice: &[u8] = b"Hello, world";    // When compiling and building MinSlice, a length check is performed, meaning we know at compile time that its length is 12.    // At runtime, once `unwrap` succeeds, within `MinSlice` the scope of, no further checks are needed.    let minslice = MinSlice::<u8, 12>::from_slice(slice).unwrap();    let value: u8 = minslice.head[6];    assert_eq!(value, b' ')}

    <T, const N: usize> is part of the struct type, just like array types, which means different lengths result in different types: Array<i32, 3> and Array<i32, 4> are different types

    12345678910111213141516171819
    #[allow(unused)]struct Array<T, const N: usize> {    data : [T; N]}fn main() {    let arrays = [        Array{            data: [1, 2, 3],        },        Array {            data: [1, 2, 3],        },        Array {            data: [4,5,6]        }    ];}

    Fill in the blanks

    1234567891011
    // Fill in the blanksfn print_array<__>(__) {    println!("{:?}", arr);}fn main() {    let arr = [1, 2, 3];    print_array(arr);    let arr = ["hello", "world"];    print_array(arr);}

    Answer:

    12345678910
    fn print_array<T: std::fmt::Debug, const N: usize>(arr: [T; N]) {    println!("{:?}", arr);}fn main() {    let arr = [1, 2, 3];    print_array(arr);    let arr = ["hello", "world"];    print_array(arr);}

    Sometimes we want tolimit the amount of memory a variable occupies, for example in embedded environments, the third form of const generic parameters — const expressions — is very suitable:

    The following code uses a feature and requires a nightly compiler.

    123456789101112131415161718192021222324
    #![allow(incomplete_features)]#![feature(generic_const_exprs)]fn check_size<T>(val: T)where    Assert<{ core::mem::size_of::<T>() < 768 }>: IsTrue,{    //...}// fix the errors in mainfn main() {    check_size([0u8; 767]);    check_size([0i32; 191]);    check_size(["hello你好"; 47]); // &str is a string reference, containing a pointer and string length in it, so it takes two word long, in x86-64, 1 word = 8 bytes    check_size([(); 31].map(|_| "hello你好".to_string()));  // String is a smart pointer struct, it has three fields: pointer, length and capacity, each takes 8 bytes    check_size(['中'; 191]); // A char takes 4 bytes in Rust}pub enum Assert<const CHECK: bool> {}pub trait IsTrue {}impl IsTrue for Assert<true> {}

    Performance of generic code

    Rust implements generics such that code using generic type parameters has no speed loss compared to using concrete types.

    Rust, by performing at compile time the monomorphization of generic codemonomorphization) to ensure efficiency.Monomorphization is the process of converting generic code into specific code by filling in the concrete types used at compile time.

    123456789101112131415161718
    //fn main(){//    let integer = Some(5);//    let float = Some(5.0);//}enum Option_i32 {    Some(i32),    None,}enum Option_f64 {    Some(f64),    None,}fn main() {    let integer = Option_i32::Some(5);    let float = Option_f64::Some(5.0);}

    At compile time, Rust expands the Option generic into Optionand OptionType

    Trait: Defining Shared Behavior

    A trait tells the Rust compiler what functionality a type has and can share with other types.

    Defining a Trait

    Putting method signatures together to define a set of behaviors required to accomplish a purpose.

    • Keyword: trait
    • Only method signatures, no concrete implementation.

    Filename: src/lib.rs

    123
    pub trait Summary {    fn summarize(&self) -> String;}

    Implementing a Trait on a Type

    Similar to implementing methods for a type.

    Difference: impl Xxxx for Tweet

    File: src/lib.rs

    12345678910111213141516171819202122232425
    pub struct NewsArticle {    pub headline: String,    pub location: String,    pub author: String,    pub content: String,}impl Summary for NewsArticle {    fn summarize(&self) -> String {        format!("{}, by {} ({})", self.headline, self.author, self.location)    }}pub struct Tweet {    pub username: String,    pub content: String,    pub reply: bool,    pub retweet: bool,}impl Summary for Tweet {    fn summarize(&self) -> String {        format!("{}: {}", self.username, self.content)    }}

    File: src/main.rs

    12345678910
    use demo::{Summary,Tweet};fn main(){    let tweet = Tweet{        username: String::from("horse_ebook"),        content: String::from("of course,sa you probably..."),        reply:false,        retweet:false,    };    println!("1 new tweet:{}",tweet.summary());}

    demo is the name of the [package] entry in the Cargo.toml file.

    Constraints for Implementing a Trait

    The precondition for implementing a trait on a type is:

    • The entire type or This trait is defined in the local crate (i.e., at least one of them is local).

    Cannot implement an external trait on an external type.

    This restriction is called coherencecoherence) part of the program property, or more specifically, orphan ruleorphan rule), named for the absence of a parent type. This rule ensures that code written by others cannot break your code, and vice versa. Without this rule, two crates could each implement the same trait for the same type, and Rust would have no way of knowing which implementation to use.

    In short:If you didn’t write the type or the traitthen you are not allowed to add an impl for them.

    Default implementations of functions in a trait

    Filename: src/lib.rs

    12345678910111213141516171819202122232425262728293031
    pub trait Summary {    fn summarize(&self) -> String {        String::from("(Read more...)")    }}pub struct NewsArticle {    pub headline: String,    pub location: String,    pub author: String,    pub content: String,}impl Summary for NewsArticle {    //fn summarize(&self) -> String {    //    format!("{}, by {} ({})", self.headline, self.author, self.location)    //}}pub struct Tweet {    pub username: String,    pub content: String,    pub reply: bool,    pub retweet: bool,}impl Summary for Tweet {    //Overriding the default implementation    fn summarize(&self) -> String {        format!("{}: {}", self.username, self.content)    }}

    Default implementations can call other methods in the same traiteven if those methods don’t have default implementations

    1234567
    pub trait Summary {    fn summarize_author(&self) -> String;    fn summarize(&self) -> String {        format!("(Read more from {}...)", self.summarize_author())    }}

    Note: you cannot call the default implementation from within an overriding implementation

    Trait as a parameter

    impl Trait syntax:

    Applies to simple cases; it is syntactic sugar for trait bounds

    123
    pub fn notify(item: &impl Summary) {    println!("Breaking news! {}", item.summarize());}

    trait bound syntax:

    Applies to complex cases

    123
    pub fn notify<T: Summary>(item: &T) {    println!("Breaking news! {}", item.summarize());}

    Compare

    1
    pub fn notify(item1: &impl Summary, item2: &impl Summary) {}

    This applies to cases where item1 and item2 are allowed to be different types (as long as they both implement Summary). But what if you want to force them to be the same type? That is only possible when using trait bounds:

    1
    pub fn notify<T: Summary>(item1: &T, item2: &T) {}

    Use + to specify multiple trait bounds

    123
    pub fn notify(item: &(impl Summary + Display)) {}pub fn notify<T: Summary + Display>(item: &T) {}

    Use where to simplify trait bounds

    fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

    Use a where clause

    1234
    fn some_function<T, U>(t: &T, u: &U) -> i32    where T: Display + Clone,          U: Clone + Debug{}

    Returning a type that implements a trait

    You can also use impl Trait syntax in the return value to return a type that implements a trait:

    12345678910
    fn returns_summarizable() -> impl Summary {    Tweet {        username: String::from("horse_ebooks"),        content: String::from(            "of course, as you probably already know, people",        ),        reply: false,        retweet: false,    }}

    However, this only applies toreturning a single type. For example, this code specifies the return type as impl Summary, but returning NewsArticle or Tweet won’t work:

    123456789101112131415161718192021222324
    fn returns_summarizable(switch: bool) -> impl Summary {    if switch {        NewsArticle {            headline: String::from(                "Penguins win the Stanley Cup Championship!",            ),            location: String::from("Pittsburgh, PA, USA"),            author: String::from("Iceburgh"),            content: String::from(                "The Pittsburgh Penguins once again are the best \                 hockey team in the NHL.",            ),        }    } else {        Tweet {            username: String::from("horse_ebooks"),            content: String::from(                "of course, as you probably already know, people",            ),            reply: false,            retweet: false,        }    }}

    Here it tries to return NewsArticle or Tweet. This won’t compile because of the limitations of how impl Trait works.

    You can use dyn trait objects

    12345678910111213141516171819202122232425262728293031323334
    struct Sheep {}struct Cow {}trait Animal {    fn noise(&self) -> String;}impl Animal for Sheep {    fn noise(&self) -> String {        "baaaaah!".to_string()    }}impl Animal for Cow {    fn noise(&self) -> String {        "moooooo!".to_string()    }}// Return a type that implements the Animal trait, but we cannot know at compile time which specific type is returned.// To fix the error here, you can use a fake random, or you can use trait objects.fn random_animal(random_number: f64) -> Box<dyn Animal> {    if random_number < 0.5 {        Box::new(Sheep {})    } else {        Box::new(Cow {})    }}fn main() {    let random_number = 0.234;    let animal = random_animal(random_number);    println!("You've randomly chosen an animal, and it says {}", animal.noise());}

    Trait objects, using trait objects in arrays

    12345678910111213141516171819202122232425262728293031323334353637383940
    trait Bird {    fn quack(&self);}struct Duck;impl Duck {    fn fly(&self) {        println!("Look, the duck is flying")    }}struct Swan;impl Swan {    fn fly(&self) {        println!("Look, the duck.. oh sorry, the swan is flying")    }}impl Bird for Duck {    fn quack(&self) {        println!("{}", "duck duck");    }}impl Bird for Swan {    fn quack(&self) {        println!("{}", "swan swan");    }}fn main() {    // Fill in the blanks    let birds :[Box<dyn Bird>;2]=[Box::new(Duck{}),Box::new(Swan{})];    for bird in birds {        bird.quack();        // When duck and swan become birds, they both forget how to soar in the sky, and only remember how to call.        // Therefore, the following code will cause an error.        // bird.fly();    }}

    &dyn and Box

    12345678910111213141516171819202122232425262728293031323334
    trait Draw {    fn draw(&self) -> String;}impl Draw for u8 {    fn draw(&self) -> String {        format!("u8: {}", *self)    }}impl Draw for f64 {    fn draw(&self) -> String {        format!("f64: {}", *self)    }}fn main() {    let x = 1.1f64;    let y = 8u8;    // draw x    draw_with_box(Box::new(x));    // draw y    draw_with_ref(&y);}fn draw_with_box(x: Box<dyn Draw>) {    x.draw();}fn draw_with_ref(x: &dyn Draw) {    x.draw();}

    Static and Dynamic dispatch

    12345678910111213141516171819202122232425262728293031
    trait Foo {    fn method(&self) -> String;}impl Foo for u8 {    fn method(&self) -> String { format!("u8: {}", *self) }}impl Foo for String {    fn method(&self) -> String { format!("string: {}", *self) }}// implement below with genericsfn static_dispatch<T: Foo>(x: T) {    x.method();}// implement below with trait objectsfn dynamic_dispatch(x: &dyn Foo) {    x.method();}fn main() {    let x = 5u8;    let y = "Hello".to_string();    static_dispatch(x);    dynamic_dispatch(&y);    println!("Success!")}

    Use trait bounds to fix the largest function

    File name: src/main.rs

    1234567891011121314151617181920212223
    fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {    let mut largest = list[0];    for &item in list {        if item > largest {            largest = item;        }    }    largest}fn main() {    let number_list = vec![34, 50, 25, 100, 65];    let result = largest(&number_list);    println!("The largest number is {}", result);    let char_list = vec!['y', 'm', 'a', 'q'];    let result = largest(&char_list);    println!("The largest char is {}", result);}

    If you do not want to restrict the largest function to only types that implement the Copy trait, we can specify Clone instead of Copy in the trait bounds of T. And clone each value of the slice so that the largest function takes ownership of them. Using the clone function means that for types that own data on the heap, such as String, it will potentially allocate more heap space, and heap allocation can be quite slow when dealing with large amounts of data.

    1234567891011121314151617
    fn largest<T: PartialOrd + Clone>(list: &[T]) -> T {    let mut largest = list[0].clone();    for item in list {        if item > &largest {            largest = item.clone();        }    }    largest}fn main() {    let str_list = vec![String::from("hello"),String::from("world")];    let result = largest(&str_list)    println!("The largest word is {}", result);}

    Or have largest return a reference directly

    1234567891011121314151617
    fn largest<T: PartialOrd + Clone>(list: &[T]) -> &T {    let mut largest = &list[0];    for item in list {        if item > &largest {            largest = item;        }    }    largest}fn main() {    let str_list = vec![String::from("hello"),String::from("world")];    let result = largest(&str_list)    println!("The largest word is {}", result);}

    Using trait bounds to conditionally implement methods

    12345678910111213141516171819202122
    use std::fmt::Display;struct Pair<T> {    x: T,    y: T,}impl<T> Pair<T> {    fn new(x: T, y: T) -> Self {        Self { x, y }    }}impl<T: Display + PartialOrd> Pair<T> {    fn cmp_display(&self) {        if self.x >= self.y {            println!("The largest member is x = {}", self.x);        } else {            println!("The largest member is y = {}", self.y);        }    }}

    Implementing a trait for all types that satisfy a trait bound is called blanket implementation (full coverage implementation)

    For example:

    123
    impl<T: Display> ToString for T {    // --snip--}

    Because the standard library has these blanket implementations, we can call the to_string method defined by ToString on any type that implements the Display trait. For example, we can convert an integer to its corresponding String value, because integers implement Display:

    let s = 3.to_string();

    Derive macro derived implementations

    We can use the #[derive] attribute to derive some traits, and the compiler will automatically provide default implementations for these traits. This is very convenient for everyday code development. For example, the Debug trait, which everyone often uses, is directly obtained through derivation, without us having to do this work manually.

    12345678910111213141516171819202122232425262728293031323334353637383940414243
    // `Centimeters`, a tuple struct that can be compared for size#[derive(PartialEq, PartialOrd)]struct Centimeters(f64);// `Inches`, a tuple struct that can be printed#[derive(Debug)]struct Inches(i32);impl Inches {    fn to_centimeters(&self) -> Centimeters {        let &Inches(inches) = self;        Centimeters(inches as f64 * 2.54)    }}// Add some attributes to make the code work// Do not modify other code!#[derive(Debug,PartialEq,PartialOrd)]struct Seconds(i32);fn main() {    let _one_second = Seconds(1);    println!("One second looks like: {:?}", _one_second);    let _this_is_true = _one_second == _one_second;    let _this_is_true = _one_second > _one_second;    let foot = Inches(12);    println!("One foot equals {:?}", foot);    let meter = Centimeters(100.0);    let cmp =        if foot.to_centimeters() < meter {            "smaller"        } else {            "bigger"        };    println!("One foot is {} than one meter.", cmp);}

    Lifecycle

    • Every reference in Rust has its own lifetime
    • Lifetimes: the scope in which references remain valid
    • In most cases: lifetimes are implicit and can be inferred
    • When the lifetimes of references may be related in different ways: manually annotate lifetimes

    The goal of lifetimes is:Avoid dangling references

    Attempting to use a reference to a value that has gone out of scope will fail.

    12345678910
    {       let r;       {           let x = 5;           r = &x;       }       println!("r: {}", r);   }

    borrow checker

    The Rust compiler has a borrow checkerborrow checker), which compares scopes to ensure that all borrows are valid.

    12345678910
    {       let r;                // ---------+-- 'a                             //          |       {                     //          |           let x = 5;        // -+-- 'b  |           r = &x;           //  |       |       }                     // -+       |                             //          |       println!("r: {}", r); //          |   }

    Here, the lifetime of r is marked as 'a and the lifetime of x as 'b. As you can see, the inner 'b block is much smaller than the outer lifetime 'a. At compile time, Rust compares the sizes of these two lifetimes and finds that r has lifetime 'a, but it references an object with lifetime 'b. The program is rejected for compilation because lifetime 'b is smaller than lifetime 'a: the referenced object exists for a shorter time than the reference to it.

    Generic Lifetimes in Functions

    1234567891011121314
    fn longest(x: &str, y: &str) -> &str {    if x.len() > y.len() {        x    } else {        y    }}fn main() {    let string1 = String::from("abcd");    let string2 = "xyz";    let result = longest(string1.as_str(), string2);    println!("The longest string is {}", result);}

    Execute

    1234567891011121314
    error[E0106]: missing lifetime specifier --> src\main.rs:1:33  |1 | fn longest(x: &str, y: &str) -> &str {  |               ----     ----     ^ expected named lifetime parameter  |  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`help: consider introducing a named lifetime parameter  |1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {  |           ++++     ++          ++          ++For more information about this error, try `rustc --explain E0106`.error: could not compile `demo` due to previous error

    Lifetime annotations

    1234567891011121314
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {    if x.len() > y.len() {        x    } else {        y    }}fn main() {    let string1 = String::from("abcd");    let string2 = "xyz";    let result = longest(string1.as_str(), string2);    println!("The longest string is {}", result);}

    It indicates that the lifetime of the return value is the overlap of the lifetimes of the two references passed in.

    Lifetime annotation syntax

    • Lifetime annotations do not change the length of a reference’s lifetime.

    • When generic lifetime parameters are specified, the function can accept references with any lifetime.

    • Lifetime annotations:Describes the relationships among the lifetimes of multiple references, but does not affect the lifetimes.

    • Lifetime parameter names: start with an apostrophe (') and are usually all lowercase and very short; many people use 'a.

    • Placement of lifetime annotations

    After the & symbol of a referenceuse a space to separate the annotation from the reference type.

    123
    &i32        // Reference&'a i32     // A reference with an explicit lifetime&'a mut i32 // A mutable reference with an explicit lifetime

    Generic lifetime parameters are declared in:the <> between the function name and the parameter list.

    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {

    1234567
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {    if x.len() > y.len() {        x    } else {        y    }}

    Its actual meaning is that the lifetime of the reference returned by the longest function is the same as the shorter of the lifetimes of the references passed in.

    Remember that by specifying lifetime parameters in the function signature, we are not changing the lifetimes of any values passed in or returned. Rather, we are stating that any value that does not satisfy this constraint will be rejected by the borrow checker. Note that the longest function does not need to know exactly how long x and y will exist, but only that some scope can be substituted for 'a that will satisfy this signature.

    When concrete references are passed to longest, the concrete lifetime substituted for 'a is the scope of x and the scope of y.the overlapping partIn other words, the concrete lifetime of the generic lifetime 'a is the one of the lifetimes of x and y that is**the smaller one.**Because we annotated the returned reference with the same lifetime parameter 'a, the returned reference is guaranteed to remain valid until the end of the shorter of the lifetimes of x and y.

    use

    123456789
    fn main() {    let string1 = String::from("long string is long");    {        let string2 = String::from("xyz");        let result = longest(string1.as_str(), string2.as_str());        println!("The longest string is {}", result);    }}

    In this example, string1 is valid until the end of the outer scope, string2 is valid in the inner scope, and result references something that is valid until the end of the inner scope. The borrow checker accepts this code; it compiles and runs, and prints The longest string is long string is long.

    Modify

    123456789
    fn main() {    let string1 = String::from("long string is long");    let result;    {        let string2 = String::from("xyz");        result = longest(string1.as_str(), string2.as_str());    }    println!("The longest string is {}", result);}

    The program fails to run.

    The error indicates that for result to be valid in println!, string2 would need to be valid until the end of the outer scope. Rust knows this because the parameters and return value of the longest function all use the same lifetime parameter 'a.

    Understanding Lifetimes in Depth

    • The correct way to specify lifetime parameters depends on the specific functionality of the function implementation.

    If we change the implementation of the longest function to always return the first parameter rather than the longest string slice, we don’t need to specify a lifetime for parameter y. The following code will compile:

    File name: src/main.rs

    123
    fn longest<'a>(x: &'a str, y: &str) -> &'a str {    x}
    • When returning a reference from a function, the lifetime parameter of the return value needs to match the lifetime parameter of one of the parameters.

    If the returned reference does not does not point to any parameter, then the only possibility is that it points to a value created inside the function, which will be a dangling reference because it will go out of scope when the function ends.

    1234
    fn longest<'a>(x: &str, y: &str) -> &'a str {    let result = String::from("really long string");    result.as_str()}

    Compilation error

    In summary, lifetime syntax is used to**associate the lifetimes of the function’s parameters with the lifetime of its return value.**Once they form such an association, Rust has enough information to allow memory-safe operations and prevent behaviors that would create dangling pointers or violate memory safety.

    Lifetime Annotations in Struct Definitions

    A struct can include:

    • owned types
    • References: you need to add a lifetime annotation to each reference.

    File name: src/main.rs

    1234567891011
    struct ImportantExcerpt<'a> {    part: &'a str,}fn main() {    let novel = String::from("Call me Ishmael. Some years ago...");    let first_sentence = novel.split('.').next().expect("Could not find a '.'");    let i = ImportantExcerpt {        part: first_sentence,    };}

    This indicates that the lifetime of the struct is the same as the lifetime of its member part.

    Lifetime Elision

    Lifetime Elision

    The patterns encoded into Rust’s reference analysis are calledlifetime elision rules.lifetime elision rules

    These are not rules that programmers need to follow; they are a set of specific scenarios that the compiler considers. If the code matches these scenarios, there is no need to explicitly specify lifetimes.

    The lifetime of a function or method’s parameters is called input lifetimeinput lifetimes), and the lifetime of the return value is called output lifetimeoutput lifetimes)。

    The compiler usesthree rulesto determine when references do not require explicit annotations. The first rule applies to input lifetimes, and the latter two rules apply to output lifetimes. If after checking these three rules there are still references whose lifetimes have not been determined, the compiler will stop and generate an error. These rulesApplicable tofndefinitions, andimplblocks

    • Rule 1Each parameter of a reference type has its own lifetime.
    • Rule 2If there is only one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.
    • Rule 3If there are multiple input lifetime parameters, but one of them is &self or &mut self (because it is a method), then the lifetime of self is assigned to all output lifetime parameters.

    Example:

    Let’s assume we are the compiler.

    1
    fn first_word(s: &str) -> &str {

    Then the compiler applies the first rule, which iseach reference parameter has its own lifetime

    1
    fn first_word<'a>(s: &'a str) -> &str {

    For the second rule, it applies because there is exactly one input lifetime parameter here. The second rule states that the input parameter’s lifetime will be assigned to the output lifetime parameters, so now the signature looks like this:

    1
    fn first_word<'a>(s: &'a str) -> &'a str {

    Now all references in this function signature have lifetimes, so the compiler can continue its analysis without the programmer needing to annotate the lifetimes in this function signature.

    1
    fn longest(x:&str,y:&str)->&str{

    Apply the first rule

    1
    fn longest<'a,'b>(x:&'a str,y:&'b str)->&str{

    The second rule does not apply, and the third rule does not apply, so the compiler reports an error.

    Lifetime annotations in method definitions

    When implementing methods on a struct with lifetimes, the syntax is still similar to that of generic type parameters.

    Where to declare lifetime parameters depends on:

    • Whether lifetime parameters are related to struct fields or to method parameters and return values.

    Lifetime names for struct fields:

    • Declared after impl
    • Used after the struct name
    • These lifetimes are part of the struct type.

    In method signatures within the impl block:

    • References must be bound to the lifetime of the struct field references, or they can be independent references.
    • Lifetime elision rules often make lifetime annotations in methods unnecessary.
    12345678910111213141516171819202122
    struct ImportantExcerpt<'a>{    part: &'a str,}impl<'a> ImportantExcerpt<'a> {    fn level(&self) -> i32 {        3    }    //Here is an example that applies the third lifetime elision rule.    fn announce_and_return_part(&self, announcement: &str) -> &str {        println!("Attention please: {}", announcement);        self.part    }}fn main(){    let novel = String::from("Call me Ishmael. Some year ago...");    let first_sentence = novel.split('.').next().expect("Could not found a '.'");    let i = ImportantExcerpt{        part: first_sentence,    };}

    announce_and_In return_part, there are two input lifetimes, so Rust applies the first lifetime elision rule and gives &self and announcement their own lifetimes. Then, because one of the parameters is &self, the return type is assigned the lifetime of &self, so all lifetimes have been worked out.

    Static lifetime

    'static, whose lifetimecanlive for the entire duration of the program.

    All string literals have'staticLifecycle

    let s: &'static str = “I have a static lifetime.”;

    The text of this string is stored directly in the program’s binary file, which is always available. Therefore, all string literals are 'static.

    Combining generic type parameters, trait bounds, and lifetimes

    1234567891011121314151617
    use std::fmt::Display;fn longest_with_an_announcement<'a, T>(    x: &'a str,    y: &'a str,    ann: T,) -> &'a strwhere    T: Display,{    println!("Announcement! {}", ann);    if x.len() > y.len() {        x    } else {        y    }}

    Writing Automated Tests

    Test functions in Rust are used to verify that non-test code is behaving as expected. The bodies of test functions typically perform the following three actions:

    1. Set up any needed data or state (Arrange)
    2. Run the code that needs to be tested (Act)
    3. Assert that the results are what we expect

    The Anatomy of a Test Function

    Writing test functions

    A test in Rust is a function annotated with the test attribute. Attributes are metadata about pieces of Rust code.

    To turn a function into a test function, add #[test] before the fn line. #[test]

    Running tests

    • Use the cargo test command to run all test functions.

    Rust will build a test runner executable that runs functions annotated with test and reports whether they succeed.

    • When you create a library project with cargo, a test module is generated with a test function inside.

    You can add any number of test modules or functions.

    1
    cargo new 项目名 --lib

    src/lib.rs

    1234567891011121314
    pub fn add(left: usize, right: usize) -> usize {    left + right}#[cfg(test)]mod tests {    use super::*;    #[test]    fn it_works() {        let result = add(2, 2);        assert_eq!(result, 4);    }}

    Run

    1
    cargo test

    Test failure

    • A test function panicking means failure.
    • Each test runs in a new thread.
    • When the main thread sees that a test thread has died, that test is marked as failed.

    Assertions

    Use the assert! macro to check test results.

    The assert! macro, from the standard library, is used to determine whether a condition is true.

    • true: the test passes.
    • false: it calls panic! and the test fails.
    123456789101112131415161718192021222324252627282930
    #[derive(Debug)]struct Rectangle {    width: u32,    height: u32,}impl Rectangle {    fn can_hold(&self, other: &Rectangle) -> bool {        self.width > other.width && self.height > other.height    }}#[cfg(test)]mod tests {    use super::*;    #[test]    fn larger_can_hold_smaller() {        let larger = Rectangle {            width: 8,            height: 7,        };        let smaller = Rectangle {            width: 5,            height: 1,        };        assert!(larger.can_hold(&smaller));    }}

    Use assert_eq! and assert_ne! macro to test equality

    • Both come from the standard library.
    • Determine whether two arguments are equal or not equal.
    • In fact, they are assert! macros that use the == and != operators.
    • When the assertion fails, it automatically prints the values of the two arguments.

    It prints the arguments using the debug format: the arguments are required to implement the PartialEq and Debug traits (all primitive types and most standard library types implement them).

    12345678910111213
    pub fn add_two(a: i32) -> i32 {    a + 2}#[cfg(test)]mod tests {    use super::*;    #[test]    fn it_adds_two() {        assert_eq!(4, add_two(2));    }}

    Custom error message

    You can pass to assert!, assert_eq! and assert_The ne! macro accepts an optional failure message parameter, which can print the custom failure message along with the test failure.

    • The first parameter of the assert! macro is required, and the custom message is the second parameter.
    • assert_eq! and assert_The first two parameters of ne! are required, and the custom message is the third parameter.
    • The custom message parameter is passed to the format! macro, and {} placeholders can be used.
    1234567891011121314
    pub fn greeting(name: &str) -> String {    format!("Hello {}!", name)}#[cfg(test)]mod tests {    use super::*;    #[test]    fn greeting_contains_name() {        let result = greeting("Carol");        assert!(result.contains("Carol"));    }}

    Custom error message

    123456789
    #[test]    fn greeting_contains_name() {        let result = greeting("Carol");        assert!(            result.contains("Carol"),            "Greeting did not contain name, value was `{}`",            result        );    }

    Verify error handling situations

    Can verify whether the code panics under specific circumstances.

    should_panic Attribute:

    • Function panics: test passes
    • Function does not panic: test fails
    123456789101112131415161718192021222324
    pub struct Guess {    value: i32,}impl Guess {    pub fn new(value: i32) -> Guess {        if value < 1 || value > 100 {            panic!("Guess value must be between 1 and 100, got {}.", value);        }        Guess { value }    }}#[cfg(test)]mod tests {    use super::*;    #[test]    #[should_panic]    fn greater_than_100() {        Guess::new(200);    }}

    Making should_panic more precise

    You can add an optional expected parameter to the should_panic attribute. The test harness will ensure that the error message contains the provided text.

    1234567891011121314151617181920212223242526272829
    // --snip--impl Guess {    pub fn new(value: i32) -> Guess {        if value < 1 {            panic!(                "Guess value must be greater than or equal to 1, got {}.",                value            );        } else if value > 100 {            panic!(                "Guess value must be less than or equal to 100, got {}.",                value            );        }        Guess { value }    }}#[cfg(test)]mod tests {    use super::*;    #[test]    #[should_panic(expected = "Guess value must be less than or equal to 100")]    fn greater_than_100() {        Guess::new(200);    }}

    Using Result<T, E> in tests

    Without needing to panic, you can write tests using Result<T, E> as the return type.

    • Returning Ok: test passes
    • Returning Err: test fails
    1234567891011
    #[cfg(test)]mod tests {    #[test]    fn it_works() -> Result<(), String> {        if 2 + 2 == 4 {            Ok(())        } else {            Err(String::from("two plus two does not equal four"))        }    }}

    Note:You cannot use the #[should_panic] annotation on these tests that use Result<T, E>.

    Because when they fail, they return Err instead of panicking.

    Controlling test execution

    Changing the behavior of cargo test: adding command-line arguments

    Default behavior:

    • Run in parallel
    • All tests
    • Capture (do not display) all standard output, making it easier to read output related to test results.

    Command-line arguments:

    • Arguments for cargo test: immediately after cargo test

    cargo test --help

    • Arguments for the test executable: after –

    cargo test – --help

    Running tests in parallel

    By default, runs in parallel using multiple threads.

    To ensure that tests:

    • do not depend on each other
    • do not depend on shared state (environment, working directory, environment variables, etc.)

    –test-threads argument

    If you do not want tests to run in parallel, or want more precise control over the number of threads, you can pass the --test-threads argument and the number of threads you want to use to the test binary. For example:

    1
    cargo test -- --test-threads=1

    Here we set the test threads to 1, telling the program not to use any parallelism. This will also take more time than running in parallel, but when there is shared state, tests will not potentially interfere with each other.

    Showing function output

    By default, if a test passes, Rust’s test library captures all output printed to standard output.

    For example, println!:

    • If the test succeeds, we will not see the println! output in the terminal.: we will only see the line indicating that the test passed.
    • ifIf the test fails, you will see all standard output and other error messages.

    If you also want to see the values printed in passing tests, you can add --show-output at the end to tell Rust to show the output of successful tests.

    1
    cargo test -- --show-output

    Running a subset of tests by name

    If you run tests without passing any arguments, all tests will run in parallel:

    Running a single test

    You can pass the name of any test to cargo test to run only that test:

    1
    cargo test one_hundred

    Filtering to Run Multiple Tests

    We can specify the name of a subset of tests, and any test whose name matches this name will be run. For example, because the first two tests’ names contain add, we can run these two tests with cargo test add:

    12345678910
    $ cargo test add   Compiling adder v0.1.0 (file:///projects/adder)    Finished test [unoptimized + debuginfo] target(s) in 0.61s     Running unittests (target/debug/deps/adder-92948b65e88960b4)running 2 teststest tests::add_three_and_two ... oktest tests::add_two_and_two ... oktest result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

    This runs all tests with add in their names and also filters out the test named one_hundred.

    Ignoring Some Tests

    Sometimes certain specific tests are very time-consuming to execute, so you want to exclude them during most runs of cargo test.

    You can use the ignore attribute to mark time-consuming tests and exclude them, as shown below:

    Filename: src/lib.rs

    12345678910
    #[test]fn it_works() {    assert_eq!(2 + 2, 4);}#[test]#[ignore]fn expensive_test() {    // code that takes an hour to run}

    For the tests you want to exclude, we add the #[ignore] line after #[test]. Now if you run the tests, you’ll see that it_works runs, while expensive_test does not run:

    If we only want to run the ignored tests, we can use cargo test – --ignored

    1
    $ cargo test -- --ignored

    Test Organization

    The Rust community tends to think about tests in terms of two main categories:

    Unit Testsunit tests) and Integration Testsintegration tests

    • Unit tests tend to be smaller and more focused, testing one module at a time in isolation, or testing private interfaces.
    • Integration tests, on the other hand, are entirely external to your library. They use your code in the same way as any other external code, testing only public interfaces, and each test may potentially test multiple modules.

    Unit Tests

    Annotating the test module with #[cfg(test)]

    • The code is only compiled and run when you run cargo test
    • Running cargo build will not.
    • Integration tests are in a different directory, and they do not need the #[cfg(test)] annotation.

    cfg: configuration

    tells Rust that the following item is only included under the specified configuration option.

    Testing Private Functions

    There has always been a debate in the testing community about whether private functions should be tested directly, and in other languages testing private functions is difficult or even impossible. However, no matter which testing ideology you adhere to,Rust’s privacy rules do allow you to test private functions

    Filename: src/lib.rs

    1234567891011121314151617
    pub fn add_two(a: i32) -> i32 {    internal_adder(a, 2)}fn internal_adder(a: i32, b: i32) -> i32 {    a + b}#[cfg(test)]mod tests {    use super::*;    #[test]    fn internal() {        assert_eq!(4, internal_adder(2, 2));    }}

    Listing 11-12: Testing a private function

    Note the internal_adder function is not marked as pub. Also, tests are just another module. As mentioned in the ‘Paths for Referring to an Item in the Module Tree’ section, items in child modules can use items in their ancestor modules. In the test, we bring all items from the test module’s parent module into scope with use super:😗, and then the test calls internal._adder。

    Integration Tests

    In Rust, integration tests are entirely external to your library.

    They use your library in the same way any other code would, which means they can only call functions that are part of your library’s public API. The purpose of integration tests is to test whether many parts of your library work together correctly. Units of code that work correctly on their own could have problems when integrated, soIntegration test coverageis also important.

    The tests Directory

    • Creating Integration Tests: The tests Directory
    • Each test file in the tests directory is a separate crate.

    Create an integration test. Keep the src/lib.rs code in the adder example. Create a tests tests directory, and create a new file _tests/integration_integration_test.rs, and enter the code from the example.

    123456
    use adder;#[test]fn it_adds_two() {    assert_eq!(4, adder::add_two(2));}
    • You don’t need to _tests/integration_annotate any code in integration_test.rs with #[cfg(test)]. **The tests directory is a special directory in Cargo.**Cargo will only compile files in this directory when you run cargo test.
    • You need to import the library under test.

    Running a Specific Integration Test

    Running a Specific Integration Test

    1
    $ cargo test 函数名

    Run all tests in a test file

    1
    $ cargo test --test 文件名

    Submodule in Integration Testing

    As integration tests increase, you may want to add more files in the tests directory to better organize them, for example, grouping tests by their functionality. As we mentioned earlier, each tests Files in the directory are compiled as separate crates.

    Treat each integration test file as its own crate, which helps create separate scopes that provide an environment more similar to how end users use the crate.

    For example, if we can create atests/common.rs file and create a function named setup, which we want to be callable by test functions in multiple test files:

    File name: tests/common.rs

    123
    pub fn setup() {    // setup code specific to your library's tests would go here}

    If you run the test again, you will see a new corresponding entry in the test results. common.rs The test result section of the file, even if this file does not contain any test functions and the setup function is not called anywhere:

    12345678910111213141516171819202122232425262728
    $ cargo test   Compiling adder v0.1.0 (file:///projects/adder)    Finished test [unoptimized + debuginfo] target(s) in 0.89s     Running unittests (target/debug/deps/adder-92948b65e88960b4)running 1 testtest tests::internal ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s     Running tests/common.rs (target/debug/deps/common-92948b65e88960b4)running 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s     Running tests/integration_test.rs (target/debug/deps/integration_test-92948b65e88960b4)running 1 testtest it_adds_two ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s   Doc-tests adderrunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

    We don’t want common to appear in the test results showing ‘running 0 tests’. We just want it to be callable from other integration test files.

    To prevent ‘common’ from appearing in the test output, we willCreatetests/common/mod.rsrather than creatingtests/common.rs . This is a Rust naming convention, and it is named this way.Tell Rust not tocommonTreat as an integration test file. Move the setup function code to tests/common/mod.rs and delete tests/common.rs After the file, this part will not appear in the test output.Subdirectories in the tests directory will not be compiled as separate crates or appear as a test result section in the test output.

    Once you have tests/common/mod.rs, you can use it as a module to be used in any integration test file. Here is a tests/integration_the it in test.rs that calls the setup function_adds_two test examples:

    File name: tests/integration_test.rs

    123456789
    use adder;mod common;#[test]fn it_adds_two() {    common::setup();    assert_eq!(4, adder::add_two(2));}//Then in the test function you can call `common::setup()` it.

    Integration Tests for Binary Crates

    • If the project is a binary crate that only contains src/main.rs and no src/lib.rs:

      • You cannot create integration tests in the tests directory

      • Cannot bring the functions in main.rs into scope

    • Only library crates can expose functions for other crates to use.

    • A binary crate is meant to run independently.

    I/O Project: Building a Command Line Program

    Accepting Command Line Arguments

    123456
    use std::env;fn main() {    let args: Vec<String> = env::args().collect();    println!("{:?}", args);}

    Note:

    std::env::args will panic if any of its arguments contain invalid Unicode characters.

    If you need to accept arguments containing invalid Unicode characters, use std::env::args_os instead. This functionreturns OsString valuesrather than String values. Here, for simplicity, we use std::env::args because OsString values are different on each platform and are more complex to handle than String values.

    Separation of Concerns for Binary Projects

    The problem of the main function being responsible for multiple tasks is common in many binary projects. So the Rust community has developed a set of guidelines for separating concerns in binary programs when the main function starts to grow large. These steps are as follows:

    • Split the program into main.rs and lib.rs and put the program’s logic into lib.rs
    • When the command line parsing logic is small, it can be kept in main.rs Middle.
    • When command line parsing starts to become complex, also extract it from main.rs to lib.rs Middle.

    After these steps, the responsibilities remaining in the main function should be limited to:

    • Call the command line parsing logic with the argument values
    • Set up any other configuration
    • Call lib.rs the run function in
    • If run returns an error, handle this error

    The whole point of this pattern is separation of concerns:main.rs The handler runs, and lib.rs handles all the real task logic. Because the main function cannot be tested directly, this structure moves all the program logic into lib.rs functions so that we can test them. Only the code left in main.rs will be small enough to verify its correctness by reading.

    TDD (Test-Driven Development)

    Following the Test-Driven Development (TDD) pattern, we will incrementally add the search logic to minigrep. This is a software development technique that follows these steps:

    1. Write a failing test and run it to make sure it fails for the reason you expect.
    2. Write or modify enough code to make the new test pass.
    3. Refactor the code you just added or changed, and make sure the tests still pass.
    4. Repeat from step 1!

    This is just one of many ways to write software, but TDD helps drive the design of the code. Writing tests before writing the code that makes them pass helps maintain high test coverage during development.

    Writing minigrep code

    src/lib.rs

    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
    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(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();        // 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}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}#[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

    12345678910111213141516
    use std::env;use std::process;use minigrep::Config;fn main() {    let args: Vec<String> = env::args().collect();    // println!("{:?}",args);    let config = Config::new(&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);    };}
    Loading comments…