Specialty in Rust

不可视境界线最后变动于:2023年1月1日 晚上

《Rust 程序设计语言》 + Rust语言圣经(Rust Course)

Rust Course

  • 使用下划线开头忽略未使用的变量, 以避免警告.
  • 变量遮蔽指后遮蔽前变量声明,如果你在某个作用域内无需再使用之前的变量(在被遮蔽后,无法再访问到之前的同名变量),就可以重复的使用变量名字,而不用绞尽脑汁去想更多的名字。
  • Rust 是一门静态类型语言, 但是编译器可以自动推导变量类型.
  • 1..5,生成从 1 到 4 的连续数字,不包含 5 ;1..=5,生成从 1 到 5 的连续数字,包含 5
  • Rust 是强类型语言,因此需要你为每一个函数参数都标识出它的具体类型
  • 在新的编译器中, 引用作用域的结束位置从花括号变成最后一次使用的位置.

commandline

1
2
3
cargo doc 
cargo doc --open
rustc --explain (something)

common concepts

code example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
println!("Guess the number!");

let secret_number = rand::thread_rng().gen_range(1..101);

loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};

println!("You guessed: {}", guess);

match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
  • cargo doc --open doc is stored in the folder.
  • something special in enumeration waiting for me to be explored
  • Rust doesn’t care where you define your functions, only that they’re defined somewhere.
  • Statements do not return values. Therefore, you can’t assign a let statement to another variable, and can’t use x=y=6 to have both x and y have the value 6
  • Rust will do check of every array indexing operation, thus will run slowly

about return statement

  • Block is expression, expressions do not include ending semicolons. Most functions return the last expression implicitly. Assigning a block of statements to a variable will get an empty tuple, expressed by “()”.

if expressions

  • no parentheses in return and if keyword, Blocks of code associated with the conditions in if expressions are sometimes called arms
  • Rust will not automatically try to convert non-Boolean types to a Boolean, do not use integer as the condition of if, instead using an integer greater or less than or equal to an certain number.
  • if else if else if …… may be changed to match
  • let number = if condition { 5 } else { 6 }; don’t use different types in each arms.

Repeating

  • loop(use break and ^C to terminate) , while, and for
  • break can take a value as the return of the loop
  • use for element in a.iter() to speed up array indexing
  • for : for number in (1..4).rev()

ownership

Ownership is Rust’s most unique feature, and it enables Rust to make memory safety guarantees without needing a garbage collector.

Memory Management: Memory is managed through a system of ownership with a set of rules that the compiler checks at compile time, won’t slow down your program.

ownership rules:

  1. Each value in Rust has a variable that’s called its owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.
  • immutable string literals and the changeable String type

  • create a String from a string literal using the from function: let s = String::from("hello");

  • Rust takes a different path from those languages having GC: the memory is automatically returned once the variable that owns it goes out of scope.

  • Rust calls a special function for us. This function is called drop, and it’s where the author of String can put the code to return the memory. Rust calls drop automatically at the closing curly bracket.

  • the following code will lead to a move instead of a shallow or deep copy, or you can use s1.clone() to do deep copy

    1
    2
    let s1 = String::from("hello");
    let s2 = s1;
  • there is copy and drop trait, which won’t coexist, the former will leave old value as valid.

  • Ownership and Functions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    fn main() {
    let s = String::from("hello"); // s comes into scope

    takes_ownership(s); // s's value moves into the function...
    // ... and so is no longer valid here

    let x = 5; // x comes into scope

    makes_copy(x); // x would move into the function,
    // but i32 is Copy, so it's okay to still
    // use x afterward

    } // Here, x goes out of scope, then s. But because s's value was moved, nothing
    // special happens.

    fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
    } // Here, some_string goes out of scope and `drop` is called. The backing
    // memory is freed.

    fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
    } // Here, some_integer goes out of scope. Nothing special happens.
  • Return Values and Scope

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    fn main() {
    let s1 = gives_ownership(); // gives_ownership moves its return
    // value into s1

    let s2 = String::from("hello"); // s2 comes into scope

    let s3 = takes_and_gives_back(s2); // s2 is moved into
    // takes_and_gives_back, which also
    // moves its return value into s3
    } // Here, s3 goes out of scope and is dropped. s2 goes out of scope but was
    // moved, so nothing happens. s1 goes out of scope and is dropped.

    fn gives_ownership() -> String { // gives_ownership will move its
    // return value into the function
    // that calls it

    let some_string = String::from("hello"); // some_string comes into scope

    some_string // some_string is returned and
    // moves out to the calling
    // function
    }

    // takes_and_gives_back will take a String and return one
    fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
    // scope

    a_string // a_string is returned and moves out to the calling function
    }

References and Borrowing

  • we call having references as function parameters borrowing
  • Reference’s default immutable
  • Use mutabel parameter:
    1. First, we had to change variable s to be mut.
    2. Then we had to create a mutable reference with &mut s
    3. and accept a mutable reference in the function with some_string: &mut String.
  • you can have only one mutable reference to a particular piece of data in a particular scope(avoiding race)

A data race is similar to a race condition and happens when these three behaviors occur:

  • Two or more pointers access the same data at the same time.
  • At least one of the pointers is being used to write to the data.
  • There’s no mechanism being used to synchronize access to the data.

The Rust prevents it by giving out an error.

  • E0502: A variable already borrowed as immutable was borrowed as mutable.
1
2
3
4
5
6
fn bar(x: &mut i32) {}
fn foo(a: &mut i32) {
let y = &a; // a is borrowed as immutable.
bar(a); // as immutable
println!("{}", y);
}

To fix this error, ensure that you don’t have any other references to the variable before trying to access it mutably:

1
2
3
4
5
6
fn bar(x: &mut i32) {}
fn foo(a: &mut i32) {
bar(a);
let y = &a; // ok!
println!("{}", y);
}

The slice type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
//iter is a method that returns each element in a collection and that enumerate wraps the result of iter
//and returns each element as part of a tuple instead.(first index, second the reference)
if item == b' ' {
return i;
}
}
s.len()
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // word will get the value 5
s.clear(); // this empties the String, making it equal to ""
// word still has the value 5 here, but there's no more string that
// we could meaningfully use the value 5 with. word is now totally invalid!
}
  • return a string slice instead of an index
1
2
3
4
5
6
7
8
9
10
fn first_word(s: &String) -> &str {	//The type that signifies “string slice” is written as &str
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..] //equals &s[0..len]
}
fn main() {}
1
fn first_word(s: &str) -> &str {}	//use the same function on both &String values and &str values.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main() {
let my_string = String::from("hello world");

// first_word works on slices of `String`s
let word = first_word(&my_string[..]);

let my_string_literal = "hello world";

// first_word works on slices of string literals
let word = first_word(&my_string_literal[..]);

// Because string literals *are* string slices already,
// this works too, without the slice syntax!
let word = first_word(my_string_literal);
}

Compound type

array

  • 数组是 Rust 的基本类型,是固定长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fn main() {
// 编译器自动推导出one的类型
let one = [1, 2, 3];
// 显式类型标注
let two: [u8; 3] = [1, 2, 3];
let blank1 = [0; 3];
let blank2: [u8; 3] = [0; 3];

// arrays是一个二维数组,其中每一个元素都是一个数组,元素类型是[u8; 3]
let arrays: [[u8; 3]; 4] = [one, two, blank1, blank2];

// 借用arrays的元素用作循环中
for a in &arrays {
print!("{:?}: ", a);
// 将a变成一个迭代器,用于循环
// 你也可以直接用for n in a {}来进行循环
for n in a.iter() {
print!("\t{} + 10 = {}", n, n+10);
}

let mut sum = 0;
// 0..a.len,是一个 Rust 的语法糖,其实就等于一个数组,元素是从0,1,2一直增加到到a.len-1
for i in 0..a.len() {
sum += a[i];
}
println!("\t({:?} = {})", a, sum);
}
}

stuct

  • Rust doesn’t allow us to mark only certain fields as mutable. As with any expression, we can construct a new instance of the struct as the last expression in the function body to implicitly return that new instance.
  • Unit-like structs can be useful in situations in which you need to implement a trait on some type but don’t have any data that you want to store in the type itself
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}

// A unit struct
struct Unit;

// A tuple struct
struct Pair(i32, f32);

// A struct with two fields
struct Point {
x: f32,
y: f32,
}

// Structs can be reused as fields of another struct
#[allow(dead_code)]
struct Rectangle {
// A rectangle can be specified by where the top left and bottom right
// corners are in space.
top_left: Point,
bottom_right: Point,
}

fn main() {
// Create struct with field init shorthand
let name = String::from("Peter");
let age = 27;
let peter = Person { name, age };

// Print debug struct
println!("{:?}", peter);


// Instantiate a `Point`
let point: Point = Point { x: 10.3, y: 0.4 };

// Access the fields of the point
println!("point coordinates: ({}, {})", point.x, point.y);

// Make a new point by using struct update syntax to use the fields of our
// other one
let bottom_right = Point { x: 5.2, ..point };

// `bottom_right.y` will be the same as `point.y` because we used that field
// from `point`
println!("second point: ({}, {})", bottom_right.x, bottom_right.y);

// Destructure the point using a `let` binding
let Point { x: left_edge, y: top_edge } = point;

let _rectangle = Rectangle {
// struct instantiation is an expression too
top_left: Point { x: left_edge, y: top_edge },
bottom_right: bottom_right,
};

// Instantiate a unit struct
let _unit = Unit;

// Instantiate a tuple struct
let pair = Pair(1, 0.1);

// Access the fields of a tuple struct
println!("pair contains {:?} and {:?}", pair.0, pair.1);

// Destructure a tuple struct
let Pair(integer, decimal) = pair;

println!("pair contains {:?} and {:?}", integer, decimal);
}
  • Refactoring with Structs: Adding More Meaning: use struct to wrap relative fields
  • print the struct by println!() will get an error, and because struct don’t have a provided implementation of Display, we can only use the #[derived(Debug)] just before the struct definition. Then use “{:?}” or “{:#?}” as the formatter.

Rust has provided a number of traits for us to use with the derive annotation that can add useful behavior to our custom types. Those traits and their behaviors are listed in Appendix C. We’ll cover how to implement these traits with custom behavior as well as how to create your own traits in Chapter 10.

  • Struct Update Syntax && field init shorthand syntax 两个挺有意思的语法.
  • The fields in struct that don’t implement Copy trait will lose ownership during assignment.

Method syntax

  • definition :they’re defined within the context of a struct (or an enum or a trait object) ……

  • intention: We’ve put all the things we can do with an instance of a type in one impl block rather than making future users of our code search for capabilities of Rectangle in various places in the library we provide.

  • automatic referencing and dereferencing : when you call a method with object.something(), Rust automatically adds in &, &mut, or * so object matches the signature of the method. In other words, the following are the same:

    1
    2
    p1.distance(&p2);
    (&p1).distance(&p2);
  • Associated Functions

    • impl blocks allow us to define functions within impl blocks that don’t take self as a parameter. The usage is just like String::from, often used in struct builder.

Enums and Pattern Matching

enum

  • Defining an enum with variants such as the ones in Listing 6-2 is similar to defining different kinds of struct definitions, except the enum doesn’t use the struct keyword and all the variants are grouped together under the Message type.

  • we can define methods on struct and the enums.

  • The Option Enum and Its Advantages Over Null Values

    • As such, Rust does not have nulls, but it does have an enum that can encode the concept of a value being present or absent. This enum is Option<T> and it is defined by the standard library as follows:

      1
      2
      3
      4
      enum Option<T> {
      None,
      Some(T),
      }

      The <T> syntax is generic type parameter.(就跟模板是一样的)

    • So why is having Option<T> any better than having null? In short, because Option<T> and T (where T can be any type) are different types, the common_used_operation won’t work.

match

将模式与 target 进行匹配,即为模式匹配

Patterns that Bind to Values

This is how we can extract values out of enum variants.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 { //If we were to call value_in_cents(Coin::Quarter(UsState::Alaska))
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => { //then we can use the state variable
println!("State quarter from {:?}!", state);
25
}}}

Matching with Option<T>

1
2
3
4
5
6
7
8
9
10
11
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
  • Combining match and enums is useful in many situations. You’ll see this pattern a lot in Rust code:

    1. match against an enum,
    2. bind a variable to the data inside, and then execute code based on it.

    It’s a bit tricky at first, but once you get used to it, you’ll wish you had it in all languages. It’s consistently a user favorite.

Matches Are Exhaustive

  • Matches in Rust are exhaustive: we must exhaust every last possibility in order for the code to be valid. Especially in the case of Option<T>, when Rust prevents us from forgetting to explicitly handle the None case, it protects us from assuming that we have a value when we might have null, thus making the billion-dollar mistake discussed earlier impossible.

The _ Placeholder

  • The _ pattern will match any value.

Concise Control Flow with if let

  • In other words, you can think of if let as syntax sugar for a match that runs code when the value matches one pattern and then ignores all other values.
1
2
3
4
5
6
7
8
9
let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
println!("three");
}// here can add an "else"
//equals
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}

matches!宏

它可以将一个表达式跟模式进行匹配,然后返回匹配的结果 true or false

1
2
3
4
5
6
7
8
#![allow(unused)]
fn main() {
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
}

Managing Growing Projects with Packages, Crates, and Modules

Common Collections

Vectors

common operation

  • Rust provides the vec! macro for convenience. let v = vec![1, 2, 3]; or let v: Vec<i32> = Vec::new();
  • use .push() to add elements
  • Dropping a Vector Drops Its Elements
  • read elements
    • First method is best used when you want your program to crash if there’s an attempt to access an element past the end of the vector.
    • When the get method is passed an index that is outside the vector, it returns None without panicking
1
2
3
4
let v = vec![1, 2, 3, 4, 5];

let does_not_exist = &v[100];
let does_not_exist = v.get(100);
  • following code won’t work, because vec may be copied to a new place
1
2
3
4
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {}", first);
  • To change the value that the mutable reference refers to, we have to use the dereference operator (*) to get to the value in i before we can use the += operator
1
2
3
for i in &mut v {  //let mut v = vec![100, 32, 57];
*i += 50;
}

Using an Enum to Store Multiple Types

  • when we need to store elements of a different type in a vector, we can define and use an enum!
1
2
3
4
5
6
7
8
9
10
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];

string(DOC)

  • For that, we use the to_string method, which is available on any type that implements the Display trait, as string literals do.
  • we can find out that the implementation of string is Vec.

CREATE A NEW STRING

1
2
3
4
5
6
7
8
9
10
11
12

let mut s = String::new();
let data = "initial contents";
let s = data.to_string();
// the method also works on a literal directly:
let s = "initial contents".to_string();
// or you can:
let s = String::from("initial contents");
//strings are UTF-8 encoded, so we can include any properly encoded data, like:
let hello = String::from("السلام عليكم");


UPDATING THE STRING

1
2
3
s.push_str("initial");	//it's a method--
s.push('l'); //push single character and single quote
s = s1 + s2; //the '+' operation use the add() method
  • pay attention to the ownership taking
1
2
3
4
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);
println!("s2 is {}", s2);
  • deref coercion: why does the first line compile ? Because Rust compiler can coerce &String into a &str
1
2
3
let s3 = s1 + &s2;	//they are all strings, compiler turns &s2 into &s2[..], and s1 has been moved 

fn add(self, s: &str) -> String { //add's signature, s1 don't have '&' which will take the ownership
  • For more complicated string combining, we can use the format! macro
1
2
3
4
5
6
7
fn main() {
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
//format! macro uses references so that this call doesn’t take ownership of any of its parameters.
let s = format!("{}-{}-{}", s1, s2, s3);
}

INDEXING

  • try to index will lead to an error, because String is a wrapper of Vec<u8>
  • You must use range syntax like :
1
2
let hello = "Здравствуйте";
let s = &hello[0..4];

​ But you can’t simply use the &hello[0..1] because index 1 is not a char boundary.

  • Use for_in_ to iterating over strings.
1
2
3
4
5
6
for c in "नमस्ते".chars() {
println!("{}", c);
}
for b in "नमस्ते".bytes() {
println!("{}", b);
}
  • This trade-off exposes more of the complexity of strings than is apparent in other programming languages, but it prevents you from having to handle errors involving non-ASCII characters later in your development life cycle.

hash map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::collections::HashMap;

// 创建一个HashMap,用于存储宝石种类和对应的数量
let mut my_gems = HashMap::new();
//或者创建指定大小的map, 提高使用性能
let mut blank = HashMap::with_capacity(capacity)

// 将宝石类型和对应的数量写入表中
my_gems.insert("红宝石", 1);
my_gems.insert("蓝宝石", 2);
my_gems.insert("河边捡的误以为是宝石的破石头", 18);

//从vec teams_list到hashmap teams_map. 下划线表示编译器自动推导.
let teams_map: HashMap<_,_> = teams_list.into_iter().collect();

HashMap 的所有权规则与其它 Rust 类型没有区别:

  • 若类型实现 Copy 特征,该类型会被复制进 HashMap,因此无所谓所有权
  • 若没实现 Copy 特征,所有权将被转移给 HashMap
1
std::mem::drop(name);

Error Handling

  • Rust group errors into two categories: recoverable and unrecoverable errors. Most languages don’t distinguish these two kinds of errors, using mechanism such as exceptions. Rust don’t have exceptions. Instead, it has the type Result<T, E> for recoverable errors and the panic! macro that stops execution when the program encounters an unrecoverable error.

Generic Types, Traits, and Lifetimes

  • we can restrict the types valid for T to only those that implement PartialOrd(or std::fmt::Debug below)
    so that can implement generic comparation.

const 泛型: 对值的泛型.

1
2
3
4
5
6
7
8
9
10
fn display_array<T: std::fmt::Debug, const N: usize>(arr: [T; N]) {
println!("{:?}", arr);
}
fn main() {
let arr: [i32; 3] = [1, 2, 3];
display_array(arr);

let arr: [i32; 2] = [1, 2];
display_array(arr);
}
  • A trait defines functionality a particular type has and can share with other types.

    • 孤儿规则: 如果你想要为类型 A 实现特征 T,那么 A 或者 T 至少有一个是在当前作用域中定义的, 这样别人的代码不会影响你的代码.
    • 特征可以有默认实现.
    1
    2
    3
    4
    5
    6
    pub trait Summary {
    fn summarize(&self) -> String {
    String::from("(Read more...)")
    }
    }
    impl Summary for Weibo { ...... } //为Weibo实现Summary特征
    • 特征参数. 形如 T: Summary 被称为特征约束.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
    }
    //上面的语法糖等同于
    pub fn notify<T: Summary>(item: &T) { ...... }
    //优势在可以使两个参数都为同一类型.
    pub fn notify<T: Summary>(item1: &T, item2: &T) {}
    //多重约束
    pub fn notify(item: &(impl Summary + Display)) {}
    pub fn notify<T: Summary + Display>(item: &T) {}
    //where约束简化复杂约束.
    fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
    U: Clone + Debug
    {}
    • 待看.