Rust introduction notes

Make a backup

Rust

case

Guess number

use std::io;
use rand::Rng;
use std::cmp::Ordering;

fn main() {
    let mut guess = String::new();
    let secret_number = rand::thread_rng().gen_range(1, 101);   // 1-100
    println!("{}", secret_number);
    loop {
        io::stdin().read_line(&mut guess).expect("cant read line");
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small"),
            Ordering::Greater => println!("Too big"),
            Ordering::Equal => {
                println!("You win");
                break;
            },
        }
    }
}

variable

const MAX_P: u32 = 100_000;
fn main() {
    let x = 5;
    let x = x ** 5;
    println!("{}", x);

    let spaces = "   ";
    let spaces = spaces.len();
    println!("{}", spaces);
}

Tuple

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

    let (x, y, z) = tup;

    println!("{} {} {}", x, y, z);
    println!("{} {} {}", tup.0, tup.1, tup.2);
}

array

fn main() {
    let a = [1, 2, 3, 4, 5];

    let months = [
        "one",
        "two",
    ];

    // Array type [type; length]
    let b: [i32; 5] = [1, 2, 3, 4, 5];

    // If every element in the array is the same
    // Equivalent to let c = [3,3,3,3,3];
    let c = [3; 5];

    let one = months[0];
    let two = months[1];
}

function

fn main() {
    another_function();
    other_function(5);

    let y = {
        let x = 1;
        x + 1
    };

    let five = five();
}

fn another_function() {
    println!("the value is 5");
}

fn other_function(x: i32) {
    println!("the value is {}", x);
}

fn five() -> i32 {
    5
}

Conditional judgment

fn main() {
    let number = 5;
    if number < 5 {
        println!("less than 5");
    } else if number > 5 {
        println!("bigger than 5");
    } else {
        println!("equal 5");
    }

    // This is because the if statement is an expression
    let condition = true;
    let number = if condition { 5 } else { 6 };
    println!("The value of number is: {}", number);
}

loop

fn main() {
    loop {
        println!("hello world");
    }

    let mut count = 0;
    let result = loop {
        count += 1;
        if count == 10 {
            break count * 2;
        }
    };
    println!("The result is {}", result);

    let mut number = 3;
    while number != 0 {
        println!("{}", number);
        number -= 1;
    }

    // Returns an iterative pointer to an array element
    let a = [1, 2, 3, 4, 5];
    for element in a.iter() {
        println!("The value is {}", element);
    }

    // Left closed right open
    // rev() can reverse the range
    for number in (1 .. 4).rev() {
        println!("The value is {}", number);
    }
}

ownership

  • Ownership is born to manage data in heap
  • Problems solved by ownership
    • Tracks which parts of the code are using the data in the heap
    • Minimize the amount of duplicate data on the heap
    • Clean up unused data on the heap to avoid running out of space

Ownership rules, memory and allocation

String

  • String literals cannot be modified
  • The value of String type can be modified

Memory and allocation

  • The literal value of the string will be known at compile time, and its text content can be directly hard coded into the final executable file

    • Fast and efficient
  • String is saved in heap

  • In Rust, when a value goes out of the scope, the memory will be automatically returned to the operating system immediately (by automatically calling the drop() function)

fn main() {
    let s1 = String::from("Hello");
    let s2 = s1;    //  So s1 failed
    let s3 = s2.clone();    // copy

    // There is no move on the stack
    let x = 5;
    let y = x;
}

Own copy trail

  • Any simple scalar combination type can be Copy
  • Any that needs to allocate memory or some kind of resource is not Copy
  • Some types with copy trail
    • All integer types
    • bool
    • char
    • All floating point types
    • Tuple, if all its fields are Copy

Ownership and function

  • Semantically, passing values to functions is similar to assigning values to variables
    • Passing a value to a function moves or copies it
  • Return value and scope
    • The transfer of ownership will also occur when the function returns a value
    • Ownership of a variable always follows the same pattern
      • Movement occurs when a value is assigned to another variable
      • When a variable containing Heap data leaves the scope, its value will be cleaned up by the drop function unless the ownership of the data moves to another variable
fn main() {
    let s = String::from("Hello World");
    take_ownership(s);

    let x = 5;
    makes_copy(x);

    println!("x: {}", x);
}

fn take_ownership(some_thing: String) {
    println!("{}", some_thing);
}

fn makes_copy(some_number: i32) {
    println!("{}", some_number);
}
fn main() {
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);
}

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    some_string
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string
}
fn main() {
    let s1 = String::from("hello");
    let (s2, len) = calculate_length(s1);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();

    (s, length)
}

Reference and borrowing

  • After borrowing a variable as an immutable reference, you cannot borrow a variable as a variable reference

  • A reference allows a value to be used without taking ownership of it

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (IMG plprjxna-1639451769486) (/ users / chenghaijie / library / Application Support / typera user images / image-20211203224800474. PNG)]

  • You can't modify borrowed things

  • Like variables, references are immutable by default, but can be modified when a variable reference is created

  • Within a specific scope, there can only be one variable reference to a piece of data

    • This has the advantage of preventing data contention at compile time
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()
}
fn main() {
    let mut s1 = String::from("Hello");
    let len = calculate_length(&mut s1);
    println!("The length of {} is {}", s1, len);
}

fn calculate_length(s: &mut String) -> usize {
    s.push_str(" world");
    s.len()
}

section

  • String literals are slices

  • Slices are data types that do not hold ownership

fn main() {
    let mut s = String::from("Hello World");
    let worldIndex = first_world(&s);
    s.clear();
    println!("{}", worldIndex);
}

fn first_world(s: &String) -> usize {
    let bytes = s.as_bytes();

    // enumerate wraps the iteration results into primitives
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }
    s.len()
}
  • A string slice is a reference to a part of a string (closed on the left and open on the right)

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-yzrkcz00-1639451769487) (/ users / chenghaijie / library / Application Support / typora user images / image-20211204085727364. PNG)]

    fn main() {
        let s = String::from("Hello World");
    
        let hello = &s[0..5];
        let world = &s[6..];
    
        let whole = &s[0..s.len()];
    }
    
  • String literals are immutable references

  • Experienced developers usually set the function parameters and return value to & STR, because it can accept different types of parameters

    fn main() {
        let mut s = String::from("Hello World");
        let worldIndex = first_world(&s[..]);
    
        let my_s = "Hello World";
        let worldIndex = first_world(my_s);
    }
    
    fn first_world(s: &str) -> &str {
        let bytes = s.as_bytes();
    
        // enumerate wraps the iteration results into primitives
        for (i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
                return &s[0..i];
            }
        }
        &s[0..]
    }
    
  • Other types of slices

    fn main() {
        let a = [1, 2, 3, 4, 5];
        let slice = &a[1..3];
    }
    

Define and instantiate Struct

  • To assign a new value to a structure field, the structure variable must be variable
  • Once the struct instance is mutable, all fields in the instance are mutable
  • struct can be used as the return value of the function, and the name is the structure name
  • Shorthand can be used when the field name and field value are equal
  • struct supports update syntax
  • Define a tuple like struct
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let mut user = User {
        username: String::from("Hello World"),
        email: String::from("123@qq.com"),
        sign_in_count: 12,
        active: true,
    };
    user.email = String::from("456@qq.com");

    // Abbreviation
    let mut user2 = User {
        username,
        email,
        sign_in_count: 23,
        active: false,
    };

    // Update syntax
    let user2 = User {
        username,
        ..user
    };

    // Truple Struct
    struct Color(i32, i32, i32);
    struct Point(u32, u32, u32);
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

fn build_user() -> User {
    User {
        username: String::from("Hello World"),
        email: String::from("123@qq.com"),
        sign_in_count: 12,
        active: true,
    }
}
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 20,
    };

    // {:#?}  The structure will be clearer
    println!("{:?}", rect);
}

fn area(dim: (u32, u32)) -> u32 {
    dim.0 * dim.1
}

fn _area_(rect: &Rectangle) -> u32 {
    rect.width * rect.height
}

struct method

  • Differences between methods and functions
    • Methods are defined in the context of struct or enum, trait
    • The first parameter is self, which represents the struct instance in which the method is called
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 20,
    };

    let rect1 = Rectangle::square(20);

    println!("{}", rect.area());
}

enumeration

  • Enumeration type is a custom data type
enum IpAddrKind{
    V4,
    V6,
}
struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

fn main() {
    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };
    let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };
}
  • T rust supports attaching data directly to enumerated variants
enum IpAddrKind {
    V4(u8, u8, u8, u8),
    V6(String)
}

fn main() {
    let home = IpAddrKind::V4(127, 0, 0, 1);
    let loopback = IpAddrKind::V6(String::from("::1"));
}
  • Define methods for enumerations
enum IpAddrKind {
    V4(u8, u8, u8, u8),
    V6(String)
}

impl IpAddrKind {
    fn call(&self) {}
}

fn main() {
    let home = IpAddrKind::V4(127, 0, 0, 1);
    let loopback = IpAddrKind::V6(String::from("::1"));
    home.call();
}

Option enumeration

  • In Prelude (pre import module), so it can be used directly

  • Describes a situation where a value may or may not exist (of a certain type)

  • Rust is not NULL

  • Definitions in the standard library

    enum Option<T> {
      Some(T),
      None,
    }
    
  • Option is better than NULL in that:

    • These are two different types. You can'T directly regard Option as T
    • If you want to use T in Option, you must convert it
fn main() {
    let some_number = Some(5);
    let somt_string = Some(String::from("hello world"));
    let absent_number: Option<i32> = None;
}

match operator

enum Coin {
    Penny,
    Dime,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        _ => 0,
    }
}

fn main() {
    
}
  • match must do everything possible
enum Coin {
    Penny,
    Dime,
    Quarter(i32)
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Quarter(value) => value,
        _ => 0,
    }
}

fn main() {
    let c = Coin::Quarter(5);
    println!("{}", value_in_cents(c));
}

if let

  • Only care about one match and ignore other matches
  • Of course, it can be used with else
fn main() {
    let v = Some(0u8);

    if let Some(3) = v {
        println!("thrwee");
    } else {
        println!("others");
    }
}

Code organization

  • Modular system
    • Package
    • Crite (unit package)
    • Module
    • Path
  • By default, all entries (functions, methods, structures, enumerations, modules, constants) of t rust are private by default, because the internal details are expected to be hidden during the design of trust
  • The parent module cannot access private entries in the child module
  • Entries in all ancestor modules can be used in sub modules
  • If it is located at the file root level, it can be called directly, whether public or private
  • The super keyword is similar to
  • pub struct
    • The structure is public
    • Structure fields are private
    • The structure field needs to be set to pub separately to become public
  • pub enum
    • Enumerations are public
    • Fields are also public
  • use keyword
    • Use this keyword to import the path into the scope, but still abide by the privacy rules

Vector

  • Vec is called vector
    • Provided by standard library
    • Multiple values can be stored
    • Only the same type of data can be stored
    • Values are stored continuously in memory
fn main() {
    let v: Vec<i32> = Vec::new();
    let vv = vec![1, 2, 3];
}
fn main() {
    let mut v: Vec<i32> = Vec::new();

    v.push(1);
    v.push(2);
    v.push(3);

    let mut vv = vec![1, 2, 3];
    vv.push(4);
}
  • Read elements in Vector
    • Indexes
    • get method
fn main() {
    let v = vec![1, 2, 3, 4, 5];
    let third: &i32 = &v[2];
    println!("{}", third);

    match v.ge(2) {
        Some(third) => println!("{}", third),
        None => println!("None"),
    }
}

Vector + enum

  • Different types of data are stored in vec

    enum Speed {
        Int(i32),
        Text(String),
    }
    
    fn main() {
        let row = vec![
            Speed::Int(12),
            Speed::Text(String::from("hello")),
        ];
    }
    

String

  • The string in t rust is a collection of bytes
  • Using utf8 encoding
fn main() {
    let str1 = "hello".to_string();
    let str2 = String::from("hello");
}
fn main() {
    let mut s = String::from("foo");
    let s1 = String::from("bar");
    s.push_str(&s1);    // Added is a string slice
    s.push('l');
}
fn main() {
    let s1 = "hello".to_string();
    let s2 = " world".to_string();
    
    // s1 ownership has been moved and therefore lapsed
    let s3 = s1 + &s2;
}
fn main() {
    let s1 = "hello".to_string();
    let s2 = " world".to_string();
    
    // He will not take ownership of any parameters
    let s3 = format!("{}-{}", s1, s2);
}
  • The string in t rust does not support index syntax to access
  • String is the encapsulation of Vec
fn main() {
    let len = String::from("iot").len();
}
  • T rust has three ways to look at strings
    • Bytes bytes
    • Scalar Values scalar values
    • Grapheme Clusters, which is the most consistent with the concept of string in our cognition

Bytes:

fn main() {
    let w = "Hello";
    for b in w.bytes() {
        println!("{}", b);
    }
}

Variable value:

fn main() {
    let w = "Hello";
    for b in w.chars() {
        println!("{}", b);
    }
}
  • Cut String
    • It must be used with caution
    • If the character boundary is crossed during cutting, the program will panic
  • Method of traversing String
    • For scalar values, use the chars() method
    • For bytes, use the bytes() method
    • For font assembly, it is very complex, and the standard library does not provide it

HashMap

  • HashMap is used less often, so it is not in Prelude
  • The standard library supports it less, and there is no built-in macro to create HashMap
  • Data village is clustered on heap
use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

    scores.insert("blue".to_string(), 10);
    scores.insert("gree".to_string(), 20);

    let score = scores.get("blue");
    match score {
        Some(s) => println!("{}", s),
        None => println!("team not exists"),
    }
}
use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

    scores.insert("blue".to_string(), 10);
    scores.insert("gree".to_string(), 20);

    // Reference is used because we usually continue to use HashMap after traversal
    for (k, v) in &scores {
        println!("{}: {}", k, v);
    }
}

Update HashMap

  • k already exists

    • Replace existing v

      use std::collections::HashMap;
      
      fn main() {
          let mut scores = HashMap::new();
      
          scores.insert("blue".to_string(), 10);
          scores.insert("blue".to_string(), 20);
      }
      
    • Keep the existing V and ignore the new v

      use std::collections::HashMap;
      
      fn main() {
          let mut scores = HashMap::new();
      
          scores.insert("blue".to_string(), 10);
          // Insert only if it does not exist
          scores.entry("blue".to_string()).or_insert(50);
      }
      
    • Merge existing v and new v

  • k does not exist

Count the number of occurrences of words

use std::collections::HashMap;

fn main() {
    let text = "hello world hi world";
    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{:?}", map);
}

Unrecoverable error in panic

  • Wrong classification
    • recoverable
      • For example, if the file is not found, try again
    • Unrecoverable
      • Bug: for example, the index accessed is out of range
  • T rust has no exception mechanism similar to java
    • Recoverable error
      • Result<T, E>
    • Unrecoverable
      • Panic! macro

The Result enumeration applies to recoverable errors

  • Enum Result<T, E> {
      Ok(T),
      Err(E),
    }
    
  • Result is pre imported

Common matching error

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("Error: {:?}", error)
        }
    };
}
  • Unwrap()

    • If the Result is Ok, return the value in Ok

    • If the result is Err, call panic! macro

    • This is a shortcut for match

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

    • Similar to unwrap(), but with error messages

    • use std::fs::File;
      
      fn main() {
          let f = File::open("hello.txt").expect("File open error");
      }
      
  • Propagation error

    • Handling errors in functions

    • Returns the error to the caller

    • Traditional ways of spreading errors

      use std::fs::File;
      use std::io::Read;
      use std::io;
      
      fn main() {
          let result = read_username_from_file();
      }
      
      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),
          }
      }
      
    • ? Simplify the operation of propagating errors

      use std::fs::File;
      use std::io::Read;
      use std::io;
      
      fn main() {
          let result = read_username_from_file();
      }
      
      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);
      }
      

      or

      use std::fs::File;
      use std::io::Read;
      use std::io;
      
      fn main() {
          let result = read_username_from_file();
      }
      
      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);
      }
      

      ? Operator can only be used with functions that return Result

When to use panic

In fact, an explicit call to panic will execute an unrecoverable error

  • If you can be sure that the Result is OK, you can use unwrap directly

    use std::net::IpAddr;
    
    fn main() {
        let home: IpAddr = "127.0.0.1".parse().unwrap();
    }
    
fn main() {
    let guess = "32";
    let guess: i32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(_) => 0,
    };

    if guess < 1 || guess > 100 {
        println!("yes");
    }
}

Extraction function

  • Original low level code

    fn main() {
        let num_list = vec![34, 50, 25, 100, 65];
        let mut largest = num_list[0];
    
        for num in num_list {
            if num > largest {
                largest = num;
            }
        }
    
        println!("The largest num is {}", largest);
    }
    
  • After extracting the function

    fn main() {
        let num_list = vec![34, 50, 25, 100, 65];
        println!("{}", largest(&num_list));
    }
    
    fn largest(list: &[i32]) -> i32 {
        let mut largest = list[0];
        for &item in list {
            if item > largest {
                largest = item;
            }
        }
        largest
    }
    

generic paradigm

  • Improve code reuse capability

The code you write is not the final code, but a template with some "placeholders" "

in a word

Generics are similar to macro definitions
  • Structs use generics

    struct Point<T> {
        x: T,
        y: T,
    }
    
    struct PPoint<T, U> {
        x: T,
        y: U,
    }
    
    fn main() {
        let integer = Point {x: 5, y: 10};
        let integer = PPoint {x: 5, y: 9.1};
    }
    
  • More complex structures use generics

    struct Point<T> {
        x: T,
        y: T,
    }
    
    impl<T> Point<T> {
        fn x(&self) -> &T {
            &self.x
        }
    }
    
    // These functions are available only for i32
    impl Point<i32> {
        fn x1(&self) -> &i32 {
            &self.x
        }
    }
    fn main() {
        let integer = Point {x: 5, y: 10};
    }
    

Trait

  • Definition of Trait: put method signatures together to define a set of behaviors necessary to achieve a certain purpose
    • Only method signature, no specific implementation
    • Each method ends with a semicolon
    • The type implementing the trait must provide a specific method implementation
    • In fact, it is similar to the concept of interface
pub trait Summary {
    fn summarize(self) -> String;
    
    // If you write it in trait, you don't have to implement it when you implement trait
    fn _summarize(self) -> String {
        "hello".to_string()
    }
}

pub struct NewArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewArticle {
    fn summarize(self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

fn main() {

}
// This parameter must implement the Summary trait. This is a simple case
fn notify1(item: impl Summary) {
    println!("{}", item._summarize())
}

// Complex, using generics
fn notify2<T: Summary>(item: T) {
    println!("{}", item._summarize())
}

// Implementation of multiple trait s
fn notify3(item: impl Summary + Dis) {
    println!("{}", item._summarize())
}

fn notify4<T: Summary + Dis>(item: T) {
    println!("{}", item._summarize())
}

// Use the where clause to make it look neat
fn notify5<T, U>(a: T, b: U) -> String
where
    T: Summary + Dis,
    U: Summary,
{
    format!("hello{}", a.summarize())
}

life cycle

  • In most cases, the life cycle is implicit and can be inferred

  • When referenced lifecycles may be related to each other in different ways, you need to label the lifecycles manually

  • Meaning of life cycle existence: avoid hanging references

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-mxwsiog8-1639451769488) (/ users / chenghaijie / library / Application Support / typera user images / image-20211208121129662. PNG)]

  • T rust implements lifecycle checking through the lifecycle checker

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-afkcqiv0-1639451769489) (/ users / chenghaijie / library / Application Support / typora user images / image-20211208121429097. PNG)]

  • This is not allowed because it is not known whether the return value comes from x or y

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-ogusowim-1639451769490) (/ users / chenghaijie / library / Application Support / typera user images / image-20211208121613886. PNG)]

  • This still doesn't reflect whether the returned type has been released

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-muu2rf18-1639451769491) (/ users / chenghaijie / library / Application Support / typera user images / image-20211208121735144. PNG)]

  • That's it, 'a takes the one with the shortest life cycle

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-wdbwi410-1639451769492) (/ users / chenghaijie / library / Application Support / typera user images / image-20211208122342800. PNG)]

  • In a structure, this means that the field must survive longer than the structure

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-jxrmojdu-1639451769493) (/ users / chenghaijie / library / Application Support / typera user images / image-20211208122608753. PNG)]

  • We know:

    • Each reference has a lifecycle
    • You need to specify lifecycle parameters for functions or struct s that use lifecycles

Small project

use std::{env, fs, process};

fn main() {
    // collect returns a collection
    let args: Vec<String> = env::args().collect();

    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("Msg: {}", err);
        process::exit(1);
    });

    let contents = fs::read_to_string(config.filename).expect("Error to do this work");
    println!("With text:\n{}", contents);
}

struct Config {
    query: String,
    filename: String,
}

impl Config {
    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();

        Ok(Config { query, filename })
    }
}

Using environment variables

use std::env;

fn main() {
    // Judge whether it exists. Of course, it returns result < >
    let var = env::var("CASE").is_err();
    println!("{}", var);
}

closure

Creating abstract behaviors using closures

  • Closure:

    You can capture anonymous functions in their environment

  • Closure:

    • Is an anonymous function
    • Save as variable, as parameter
    • You can create closures in one place, and then invoke closures in another context to complete operations.
    • Values can be captured from the scope they define
fn main() {
    let exp = |num| {
        num
    };

    let add = |a: i32, b: i32| -> i32 {
        a + b
    };
    
    let v4 = |a: i32, b: i32| a + b;
}	

iterator

  • He is an inert production consumer model

  • Create a custom iterator in one step:

    Just implement the next() method

    struct Counter {
        count: u32
    }
    
    impl Counter {
        fn new() -> Counter {
            Counter { count: 0 }
        }
    }
    
    impl Iterator for Counter {
        type Item = u32;
        fn next(&mut self) -> Option<Self::Item> {
            if self.count < 5 {
                self.count += 1;
                Some(self.count)
            } else {
                None
            }
        }
    }
    
    fn main() {
        let mut counter = Counter::new();
    
        for i in vec![1, 2, 3, 4, 5] {
            println!("{}", counter.next().unwrap())
        }
    }
    
  • In fact, the collect() method turns iterators into collections

Smart pointer

Smart pointers are such data structures
	- Behavior is similar to pointers
	- Additional metadata and functionality

Smart pointers are usually implemented using struct, and two trait s, Deref and Drop, are implemented

Deref trait: allows instances of smart pointer struct to be used as references

Drop trait: allows you to customize the code when the smart pointer instance goes out of scope

  • Common smart pointers in standard libraries
    • Box: allocate values on heap memory
    • Rc: reference count type with multiple ownership enabled
    • Ref and RefMut: accessed through RefCell: force borrowing of rule types at run time rather than compile time

Use the Box to point to the data on the heap

  • In fact, when rust allocates space for enumeration types, it is similar to the union of c language (in fact, this is how rust determines the space size of non recursive types)

  • T rust cannot calculate the size of recursive types. It uses box to avoid self-contained types, but it can access box values (from direct storage to pointer pointing storage)

  • This is the simplest smart pointer

    • Allows you to store data on heap instead of stack
    • On the Stack is a pointer to the heap data
    • No performance overhead
    • There are no additional features
    • Derek trait and Drop trait are implemented (this is why smart pointers are called)
  • Common scenarios

    • At compile time, the size of a type cannot be determined. However, when using this type, the context needs to know its exact size
    • When you have a lot of data, you want to transfer ownership, but you need to ensure that the data will not be copied during operation
    • When using a value, you only care whether it implements a specific trait, not its specific type
  • Using Box to store data on heap

    fn main() {
        let b = Box::new(5);
        println!("b = {}", b);
    }
    
  • Enable recursive types using Box

    • At compile time, Rust needs to know the space occupied by a type
    • The size of recursive types cannot be determined at compile time
    • And Box is certain

Deref Trait

  • With this, smart pointers can be handled like regular references
  • Box is defined as a tuple struct with one element

Smart pointer example

  • String and Vec

  • Each has a memory area and allows users to operate on it

  • It also has metadata (such as capacity)

  • Enable recursive types using Box

    This is because the size of the recursive type cannot be determined at compile time
     For recursive types, Box There is a way to solve this problem
    

Box

  • summary

    • Only indirect storage and heap memory allocation are provided

    • There is no additional performance overhead

    • Applicable to scenes that require "indirect" storage, such as Cons List

    use crate::List::{Cons, Nil};
    
    fn main() {
        let list = Cons(1,
                        Box::new(Cons(2,
                                      Box::new(Cons(3,
                                                    Box::new(Nil))))));
        
    }
    
    enum List {
        Cons(i32, Box<List>),
        Nil,
    }
    
  • Derek Trait

    • Implementing deref trait allows us to customize the behavior of the dereference operator *

    • By implementing deref trait, smart pointers can be handled like regular references

    fn main() {
    	let x = 5;
    	let y = &x;
    
    	assert_eq!(5, x);
    
    	// Dereference
    	assert_eq!(5, *y);
    
        println!("Hello, world!");
    }
    

    It is implemented with Box

    fn main() {
    	let x = 5;
    	let y = Box::new(5);
    	assert_eq!(5, x);
    
    	// Dereference
    	assert_eq!(5, *y);
    
        println!("Hello, world!");
    }
    
  • Define your own smart pointer

    • Box is defined as a tuple struct with one element

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

      • This method borrows self

      • Returns a reference to internal data

      • Implementation example

        use std::ops::Deref;
        
        struct MyBox<T>(T);
        
        impl<T> MyBox<T> {
            fn new(x: T) -> MyBox<T> {
                MyBox(x)
            }
        }
        
        impl<T> Deref for MyBox<T> {
            type Target = T;
        
            fn deref(&self) -> &T {
                &self.0
            }
        }
        
        fn main() {
            let x = 5;
            let y = MyBox::new(x);
        
            assert_eq!(5, x);
            assert_eq!(5, *y);
        }
        
  • Implicit dereference transformation of functions and methods (Deref Coercion)

    • This is a convenient feature for functions and methods

      principle

      • Deref Coercion can convert the reference of T into the reference generated by T after deref operation

      • sketch

        When a reference of a type is passed to a function or method, but its type does not match the defined parameter type:

        • Deref Coercion will happen automatically

        • The compiler makes a series of calls to deref to convert it to the required parameter type

          • This is done at compile time without additional performance overhead

          • example

            use std::ops::Deref;
            
            fn hello(name: &str) {
                println!("Hello: {}", name);
            }
            
            fn main() {
                let m = MyBox::new(String::from("Rust"));
            
                hello(&(*m)[..]);
                hello("Rust");
                hello(&m);
            }
            
            struct MyBox<T>(T);
            
            impl<T> MyBox<T> {
                fn new(x: T) -> MyBox<T> {
                    MyBox(x)
                }
            }
            
            impl<T> Deref for MyBox<T> {
                type Target = T;
            
                fn deref(&self) -> &T {
                    &self.0
                }
            }
            
  • Dereference and variability

    • The * operator of variable references can be overloaded with DerefMut trait

    • When type and trait occur in the following three cases, Rust will execute deref coercion

      • When t: deref < target = u >, allow & T to convert to & U
      • When t: derefmut < target = u >, allow & mut t to be converted to & mut U
      • When t: deref < target = u >, allow & mut t to convert to & U

      Just understand these things, and you will naturally know in the future. This is a great philosophy

Drop Trait

  • Any type can implement drop trait

  • drop trait only requires you to implement the drop method

    • Parameters of this method: only a variable reference to self
  • Drop trail pre import module (Prelude)

  • Implementing drop trail allows us to customize the action that occurs when the value is about to leave the scope

    • For example, network connection, resource release, etc
  • example

    struct CustomSmartPointer {
        data: String
    }
    
    impl Drop for CustomSmartPointer {
        fn drop(&mut self) {
            println!("Dropping CustomSmartPointer with data {}",self.data)
        }
    }
    
    fn main() {
        let c = CustomSmartPointer { data: String::from("my stuff")};
        let d = CustomSmartPointer { data: String::from("other stuff")};
    
        println!("CustomSmartPointer created");
    }
    
  • Use to disable the automatic drop function directly. It's not necessary

    • This is because the purpose of drop trail is to automatically release processing logic
  • Rust does not allow you to manually call the drop method of Drop trait, that is

    c.drop()
    

    Is not allowed

  • In fact, this trait is similar to a destructor

  • But you can call the std::mem::drop function of the standard library to invoke the drop value in advance.

    This function is located in the pre import module, so it does not need to be referenced manually

    Of course, this will not lead to double release

    example

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-9emjlo0d-1639451769494) (/ users / chenghaijie / library / Application Support / typera user images / image-20211210231520329. PNG)]

Rc

Reference count smart pointer
  • Sometimes, a value corresponds to multiple owners, such as in a graph data structure

    Illustration

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-uxxxtzef-1639451769494) (/ users / chenghaijie / library / Application Support / typera user images / image-20211210231934499. PNG)]

    For example, when releasing a node, the central node is multi pointed

RC = reference counting

This thing will maintain a counter internally to judge that the value release is still referenced. If the counter is 0, it indicates that it can be released

Safe release

  • Usage scenario

    • This thing is only suitable for single threaded scenarios
    • Data needs to be allocated on the heap, which is read by multiple parts of the program (read-only), but it is impossible to determine which part will use up the data at compile time
  • Note that this is not in the pre import module

    [the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-bmifizj9-1639451769495) (/ users / chenghaijie / library / Application Support / typera user images / image-20211210232522918. PNG)]

  • example

    Two lists share ownership of the other list

    Figure [external link image transfer fails, and the source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-es5ojkoy-1639451769497) (/ users / chenghaijie / library / Application Support / typera user images / image-20211210232625328. PNG)]

    Note that this thing is set to read-only through immutable references

    Well, I don't know why the code is wrong

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

    alas

pattern

  • classification

    • Refutable: a pattern that matches any value that may be passed

      let x = 5;
      
    • Irrefutable: a pattern that cannot be matched for some possible values

      if let Some(x) = a_value
      
    • Function parameters, let statements, and for loops only accept irrefutable patterns

    • ordinary

      fn main() {
          let x = Some(5);
          let y = 10;
      
          match x {
              Some(50) => println!("Got 50"),
              Some(y) => println!("Matched, y = {:?}", y),
              _ => println!("Default case, x = {:?}", x)
          }
      
          println!("at the end: x = {:?}", x);
      }
      
    • Multiple matching

      fn main() {
          let x = 5;
          let y = 10;
      
          match x {
              1 | 2=> println!("Got 50"),
              Some(y) => println!("Matched, y = {:?}", y),
              _ => println!("Default case, x = {:?}", x)
          }
      
          println!("at the end: x = {:?}", x);
      }
      
    fn main() {
        let x = 5;
        match x {
            1..=5 => println!("one enough five"),
            _ => println!("something else")
        }
    
        let x = 'c';
        match x {
            'a'..='j' => println!("early ascii letter"),
            'k'..='z' => println!("late ascii letter"),
            _ => println!("something else")
        }
    }
    
    • Deconstruction

      struct Point {
          x: i32,
          y: i32
      }
      
      fn main() {
          let p = Point { x: 0, y: 7 };
      
          let Point { x: a, y: b } = p;
          assert_eq!(0, a);
          assert_eq!(7, b);
      
          let Point { x, y } = p;
          assert_eq!(0, a);
          assert_eq!(7, b);
      }
      
    • Useful deconstruction

      struct Point {
          x: i32,
          y: i32
      }
      
      fn main() {
          let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: 10 });
      }
      
    • Better pattern matching

      fn main() {
          let numbers = (2, 4, 8, 16, 32);
          match numbers {
              // ..  It is placed in the middle and used to ignore some values
              (first, .., last) => {
                  println!("Some numbers: {} {}", first, last)
              }
          }
      }
      
    • Using match guard to provide additional conditions is actually an additional if condition after match arm mode, which is suitable for more complex scenes

      fn main() {
          let num = Some(4);
      
          match num {
              Some(x) if x < 5 => println!("less than five: {}", x),
              Some(x) => println!("{}", x),
              None => (),
          }
      }
      
      fn main() {
          let x = 4;
          let y = false;
      
          match x {
              4 | 5 | 6 if y => println!("yes"),
              _ => println!("no"),
          }
      }
      

      This will save that value

      enum Message {
          Hello { id: i32 }
      }
      
      fn main() {
          let msg = Message::Hello { id: 5 };
      
          match msg {
              Message::Hello {
                  id: id_variable @ 3..=7,
              } => println!("Found an id in range: {}", id_variable),
              _ => println!("else lalala")
          }
      }
      

Unsafe Rust

  • Rust hides the second language, it meiyo
  • Mandatory memory security
    • It is the same as normal t rust, but it provides super power
  • Existing reasons
    • Static analysis is conservative
      • Use unsafe Trust: tell the compiler that I know what I'm doing and take the corresponding risks
    • The computer hardware itself is not secure, and t rust needs to be able to program the system

Unsafe superpower

  • Use the unsafe keyword to switch to unsafe trust and open a block containing unsafe code

  • Four actions that can be executed in unsafe trust (unsafe trust) super ability

    • Dereference original pointer
    • Calling unsafe functions and methods
    • Accessing and modifying variable static variables
    • Implement unsafe trait
  • be careful

    • unsafe does not turn off borrowing checks or disable other security checks
    • Any memory security related errors must stay in the unsafe block
    • Isolate unsafe code as much as possible, and it is best to encapsulate it in a secure abstraction to provide a secure api
  • Dereference original pointer

    • Raw pointer

      • Variable: * mut T
      • Immutable: * const T, which means that the pointer cannot be directly assigned after dereference
      • Note that * here is not a dereference symbol, it is part of the type name
    • Unlike references, raw pointers

      • Allows you to ignore borrowing rules by having both immutable and variable pointers or multiple variable pointers pointing to the same location
        • There is no guarantee that it can point to reasonable memory
        • null allowed
        • No automatic cleaning is achieved
    • Give up the guaranteed security in exchange for better performance / the ability to interface with other languages or hardware

    • example

      fn main() {
          let mut num = 5;
      
          // Pointer to determine validity
          let r1 = &num as *const i32;
          let r2 = &mut num as *mut i32;
      
          // The original pointer must be dereferenced in an unsafe block
          unsafe {
              println!("r1: {}", *r1);
              println!("r2: {}", *r2);
          }
      
          // Unable to determine valid pointer
          let address = 0x012345usize;
          let r = address as *const i32;
      
          // Even if there is an illegal access error, you have to deal with it yourself
          unsafe {
              println!("r: {}", *r);
          }
      }
      
    Why use raw pointers
    	- And c Language interface
    	- Construct a security abstraction that the borrowing checker cannot understand
    
    • Calling an unsafe function or method

      • Before calling, you need to meet some conditions manually (mainly by looking at the documents), because t rust cannot verify these conditions

      • It needs to be called in the unsafe block

      • example

        unsafe fn dangerous() {
        
        }
        
        fn main() {
            unsafe {
                dangerous();
            }
        }
        
    • Create a security abstraction for unsafe code

      • Just because a function contains unsafe code does not mean that the entire function needs to be marked unsafe
      • Wrapping unsafe code in secure functions is a common abstraction
    • Use the extern function to call external code

      • extern keyword: simplify and use the process of external function interface (FFI)

      • FFI: allows one programming language to define functions and other programming languages to call these functions

      • Any code in an extern block is unsafe

      • example

        #[no_mangle]
        pub extern "C" fn call() {
            println!("hello world")
        }
        
        fn main() {
        
        }
        
  • Access or modify a variable static variable

    • In t rust, global variables are called static variables

    • And constant types

      static HELLO_WORLD: &str = "hello, world";
      
      fn main() {
          println!("{}", HELLO_WORLD)
      }
      
  • Implement unsafe trait

    • When at least one method in a trait has an unsafe factor that cannot be verified by the compiler, it is said that the trait is unsafe

    • Life unsafe trait: add the unsafe keyword before the definition

      • This trait can only be implemented in unsafe code blocks
    • example

      unsafe trait Foo {
      
      }
      
      unsafe impl Foo for i32 {
      
      }
      
      fn main() {
      
      }
      
  • When to use unsafe code

    • The compiler cannot guarantee memory security. It is not easy to ensure that unsafe code is correct
    • You can do this when you have good reason to use unsafe code
    • By explicitly marking unsafe, you can easily locate problems when they occur

Advanced trait

  • Use the association type in the trait definition to specify the placeholder type

    • An association type is a type placeholder in a trait that can be used in a trait method signature

      • You can define trait s that contain certain types without knowing what they are before implementation

      • In fact, it feels a bit like generics

        pub trait Iterator {
            type Item;
        
            fn text(&mut self) -> Option<Self::Item>;
        }
        
        fn main() {
            println!("hello world")
        }
        
    • The difference between association types and generics

      generic paradigmAssociation type
      Label the type each time you implement traitNo dimension type is required
      A trait can be implemented multiple times for a type (through different type parameters)A trait cannot be implemented more than once for a single type
  • Default generic parameters and operator overloading

    • You can specify a default concrete type for a generic when using generic parameters

      • Syntax: < placeholdertype = concretetype >
    • This technique is often used for operator overloading (although t rust does not allow you to create your own operators and overload any operators)

    • However, you can overload some of the corresponding operators by implementing the trait s listed in std::ops

    • Example: overloaded addition operator

      use std::ops::Add;
      
      #[derive(Debug, PartialEq)]
      struct Point {
          x: i32,
          y: i32
      }
      
      impl Add for Point {
          type Output = Point;
      
          fn add(self, rhs: Self) -> Self::Output {
              Point {
                  x: self.x + rhs.x,
                  y: self.y + rhs.y
              }
          }
      }
      
      fn main() {
          assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
              Point { x: 3, y: 3 }
          )
      }
      
    • Fully qualified syntax, how to call a method with the same name

      trait Pilot {
          fn fly(&self);
      }
      
      trait Wizard {
          fn fly(&self);
      }
      
      struct Human;
      
      impl Pilot for Human {
          fn fly(&self) {
              println!("This is your caption speaking...")
          }
      }
      
      impl Wizard for Human {
          fn fly(&self) {
              println!("Up")
          }
      }
      
      impl Human {
          fn fly(&self) {
              println!("*waving arms furiously*")
          }
      }
      
      fn main() {
          let person = Human;
          person.fly();
      
          Pilot::fly(&person);
          Wizard::fly(&person);
      }
      
    • Fully qualified syntax

      ::function(receiver_if_method, next_arg, ...)

      • Can be used anywhere a function or method is called

      • Allows you to ignore parts that can be inferred from other contexts

      • This syntax is needed when t rust cannot distinguish which specific implementation you expect to call, because it is cumbersome to write

        <Human as Pilot>::fly(&Human);
        
  • Implementing type safety and abstraction using the new type pattern

    • Create type synonyms using type aliases

      • Produce another name (synonym) for an existing type

      • Is not a separate type

      • Use the type keyword

      • Main purpose: reduce code character repetition

        type Killmeters = i32;
        
        fn main() {
            let x: i32 = 5;
            let y: Killmeters = 5;
        
            println!("{}", x + y)
        }
        
  • never type

    • There is one named! Special types of
      • It has no value and is called empty type in jargon
      • We prefer to call it never type because it acts as a return type in functions that do not return
    • A function that does not return a value is also called a divergence function
  • Dynamic size and Sized Trait

    • T rust needs to determine at compile time how much space is allocated for a particular type of value

    • There is the concept of dynamic size type (DST) in trust

      • When writing code, use values that can only be sized at run time
    • For example, str is a type of dynamic size (note not & str): only the runtime can determine the length of the string

      • The following code does not work

        let s1: str = "hello";
        let s2: str = "world...";
        
      • Use & STR to solve this problem. In fact, it saves

        • str address
        • Length of str
    • A common way for t rust to use dynamic size types

      • Additional metadata is attached to store the size of the dynamic information
        • Like & STR, when using a dynamic size type, its value is always placed behind some kind of pointer
    • In fact, trait is also a type of dynamic size

Advanced functions and closures

  • You can pass functions to other functions

  • The function is cast to fn type during passing

  • fn type is called function pointer

  • Transfer function pointer instance

    fn add_one(x: i32) -> i32 {
        x + 1
    }
    
    fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
        f(arg) + f(arg)
    }
    
    fn main() {
        let answer = do_twice(add_one, 5);
        println!("{}", answer)
    }
    
  • The difference between function pointers and closures

    fnclosure
  • Return closure from function

    fn return_closure() -> Box<dyn Fn(i32) -> i32> {
        Box::new(|x| x + 1)
    }
    
    fn main() {
    
    }
    

macro

macro
  • A macro in t rust refers to a collection of related features
    • Using macro_rules! Built declaration macro
    • Three process macros
      • Custom (#[derive]) macros for struct and enum, for which you can specify the code added with the derive attribute
      • A macro similar to an attribute that adds a custom attribute to any entry
      • A macro similar to a function looks like a function call and operates on the token specified as a parameter
  • Differences between functions and macros
    • In essence, macros are used to write code that can generate other code (metaprogramming)
    • When defining a signature, a function must declare the number and type of parameters. Macros can handle variable parameters
    • The definition of macros is much more complex than functions. Macros must be defined in advance or introduced into the current scope
    • Functions can be defined anywhere and called anywhere

Keywords: Back-end Rust

Added by bingo333 on Tue, 14 Dec 2021 06:40:28 +0200