Home rustlings 学习和答案
Post
Cancel

rustlings 学习和答案

前言

去年年中对rust进行了一个大概的学习,然后完成了rustlings的笔记,最后改写了我的两个简单的java项目。今年回首,又感觉比较陌生了,所以准备在此记录一下rustlings的题解和一些笔记。

相关资料

Intro

Rust uses the print! and println! macros to print text to the console.

intro2.ts

打印写错了,改为

1
println!("Hello world!");

print

rust的打印是不换行的print!()以及换行的println!(),这两个都是宏函数

Further information

Variables

In Rust, variables are immutable by default. When a variable is immutable, once a value is bound to a name, you can’t change that value. You can make them mutable by adding mut in front of the variable name.

variables1

没有声明变量,所以增加let

1
let x = 5;

variables2

没有指定默认值,这里的变量类型申明是可以省略的。

1
let x: i32 = 0;

variables3

也是没有指定默认值

1
let x: i32 = 10;

variables4

这里涉及到变量的修改,因为rust变量默认是不可变的,所以增加修饰符mut

1
let mut x = 3;

variables5

因为这里变量已经赋值并且不可变,而且还是&str类型,题目也要求不可以对此修改,所以可以考虑利用变量再利用的特性,直接重新申明一次,之前的对象会被回收,不可再使用。

1
let number = 3;

variables6

const申明的是编译时确定值的不可变常亮,需要显示的指定类型,所以增加类型即可

1
const NUMBER: i32 = 3;

Further information

Functions

Here, you’ll learn how to write functions and how the Rust compiler can help you debug errors even in more complex code.

functions1

这里因为调用了没有申明的函数,所以直接申明就可以了,注意rust的函数和c不同,在使用之前或者之后都是OK的

1
fn call_me(){}

functions2

方法定义参数是,需要显示的指定参数的类型

1
2
3
4
5
fn call_me(num: i32) {
    for i in 0..num {
        println!("Ring! Call number {}", i + 1);
    }
}

functions3

对需要参数的方法调用时,必须要指定参数

1
call_me(3);

function4

有返回值时,需要显示的申明返回值类型。因为这里返回的是if表达式,并且函数的最后一个表达式的值可以被隐式地返回,省略return关键字,省略分号意味着这个表达式的值将成为函数的返回值。

1
2
3
4
5
6
7
fn sale_price(price: i64) -> i64{
    if is_even(price) {
         price - 10
    } else {
        price - 3
    }
}

function5

同理,这里作为返回值,需要省略分号,增加return也是不行的。

1
2
3
fn square(num: i32) -> i32 {
    num * num
}

Further information

If

if, the most basic (but still surprisingly versatile!) type of control flow, is what you’ll learn here.

if1

这里主要是写比较逻辑,返回最大值,因为是返回表达式的值,所以都不需要分号

1
2
3
fn bigger(a: i32, b: i32) -> i32 {
    if a > b { a } else { b }
}

if2

这里主要是else if如何使用

1
2
3
4
5
6
7
8
9
fn foo_if_fizz(fizzish: &str) -> &str {
    if fizzish == "fizz" {
        "foo"
    } else if fizzish == "fuzz"{
        "bar"
    } else {
        "baz"
    }
}

if3

if表达式的所有分支必须返回相同类型的值.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn animal_habitat(animal: &str) -> &str {
    // TODO: Fix the compiler error in the statement below.
    let identifier = if animal == "crab" {
        1
    } else if animal == "gopher" {
        2
    } else if animal == "snake" {
        3
    } else {
        4
    };

    // Don't change the expression below!
    if identifier == 1 {
        "Beach"
    } else if identifier == 2 {
        "Burrow"
    } else if identifier == 3 {
        "Desert"
    } else {
        "Unknown"
    }
}

Further information

quiz1

这是前面一部分的总结,需要定义一个方法满足条件

1
fn calculate_price_of_apples(num: i32) -> i32 { if num <= 40 { num * 2 } else { num } }

Primitive Types

Rust has a couple of basic types that are directly implemented into the compiler. In this section, we’ll go through the most important ones.

primitive_types1

需要定义一个新的完整变量

1
2
3
4
let is_evening: bool = false;
if is_evening {
    println!("Good evening!");
}

primitive_types2

也是定义一个变量,随意就行

1
let your_character = '1';

primitive_types3

定义一个长度超过100的arr

1
let a = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";

primitive_types4

这里需要从原来的数组中获取一个片段出来

1
let nice_slice = &a[1..4];

注意这里用到了&符号,表示这是创建一个引用出来,指向的是原来的片段;

可能有人要问了,为什么要在数组或向量的切片操作中使用&符号呢?这是因为:

  • 借用数据: Rust 中的切片操作返回的类型是一个引用,而不是数据本身。这意味着切片并不会拷贝整个数据,而只是提供了一个指向原始数据特定部分的引用。这种做法可以避免不必要的内存分配和数据复制。

  • 所有权和借用规则: Rust的所有权系统要求在操作数据时要么拥有数据的所有权,要么借用数据的引用。使用&符号创建的切片引用,表明我们只是借用了原始数据的一部分,而不是拥有整个数据。

  • 性能和内存: 借用数据比复制数据更高效,特别是当处理大量数据时。通过使用引用,可以避免在堆上分配新的内存空间,从而提高程序的性能和内存效率。

那么还有人要问了,能不能写成拷贝的形式呢 ? 那就得比较麻烦一点了,需要先创建数组,然后再拷贝过去。

1
2
3
4
let mut nice_slice = [0; 3];

let slice = &a[1..4];
nice_slice.copy_from_slice(slice);

primitive_types5

解构提取元祖的数据

1
let (name, age) = cat;

这里可以看到编译器提示类型,但是类型不能写到里面,得写到后面

1
let (name, age): (&str, f32) = cat;

primitive_types6

解构获取元祖中的某一个元素。

1
let second: i32 = numbers.1;

Further information

Vectors

Vectors are one of the most-used Rust data structures. In other programming languages, they’d simply be called Arrays, but since Rust operates on a bit of a lower level, an array in Rust is stored on the stack (meaning it can’t grow or shrink, and the size needs to be known at compile time), and a Vector is stored in the heap (where these restrictions do not apply). Vectors are a bit of a later chapter in the book, but we think that they’re useful enough to talk about them a bit earlier. We shall be talking about the other useful data structure, hash maps, later.

vecs1

这里规定了返回值类型,需要把Array转换成Vec

1
2
3
4
5
6
7
fn array_and_vec() -> ([i32; 4], Vec<i32>) {
    let a = [10, 20, 30, 40]; // Array

    let v = a.to_vec();

    (a, v)
}

vecs2

观察单测,可以发现其实都是想返回乘2之后的数据,关键就在怎么写,其中还给了一个例子,可以参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn vec_loop(input: &[i32]) -> Vec<i32> {
    let mut output = Vec::new();

    for element in input {
        output.push(element * 2)
    }

    output
}

fn vec_map(input: &[i32]) -> Vec<i32> {
    input
        .iter()
        .map(|element| {
            element * 2
        })
        .collect()
}

Further information

Move Semantics

These exercises are adapted from pnkfelix’s Rust Tutorial – Thank you Felix!!!

move_semantics1

这里主要是传入的是不可变对象,然后需要向其添加元素,所以需要进行所有权转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    let mut vec = vec;

    vec.push(88);

    vec
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn move_semantics1() {
        let vec0 = vec![22, 44, 66];
        let vec1 = fill_vec(vec0);
        assert_eq!(vec1, vec![22, 44, 66, 88]);
    }
}

在这里vec0已经把所有权传给了fill_vec方法,后续就不能再继续使用了,然后看看fill_vec函数的作用

在 Rust 中,函数参数的传递有两种方式:按值传递(move)和按引用传递(borrow)。

按值传递(move) 在 Rust 中,对于不可变的数据结构(比如基本数据类型和不可变引用),函数参数默认是按值传递的。这意味着当你调用函数时,函数会获取参数的所有权,也就是将参数的所有权转移给函数,称为 move。在函数内部对参数的修改不会影响原始的数据。

解释 fill_vec 函数的行为:

  1. 参数传递:fn fill_vec(vec: Vec<i32>) -> Vec<i32> 定义了一个函数 fill_vec,它接收一个 Vec<i32> 类型的参数 vec。这里的 vec 是按值传递的,即函数调用时会发生所有权的转移。
  2. 函数内部操作:let mut vec = vec; 在函数内部,通过 let mut vec = vec; 将参数 vec 的所有权转移给了函数内部的新变量 vec。这一步实际上是一个 move 操作,导致原始的 vec 参数被移动(所有权被转移),在函数返回后不再有效。
  3. 修改操作:vec.push(88); 在函数内部,通过 vec.push(88);vec 中添加了一个新元素 88。
  4. 返回值:vec 在函数末尾作为返回值返回给调用者。由于 Rust 中的函数返回值可以传递所有权,因此返回的 vec 将成为函数调用者所拥有的新的 Vec<i32>

move_semantics2

这第二个例子就是解决所有权转移后vec0不再可用的

1
2
3
4
5
6
7
8
9
#[test]
fn move_semantics2() {
    let vec0 = vec![22, 44, 66];

    let vec1 = fill_vec(vec0.clone());

    assert_eq!(vec0, [22, 44, 66]);
    assert_eq!(vec1, [22, 44, 66, 88]);
}

move_semantics3

因为前面的在fill_vec中都进行了额外的所有权转移,这个例子就是来解释怎么优化这块的,可以考虑直接声明为可变动,那后续里面就可以直接操作了

1
2
3
4
5
fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {
    vec.push(88);

    vec
}

move_semantics4

因为x连续进行了两次借用,所以第二次借用会出现错误。所以可以考虑先对y进行使用,当y后续没有使用归还了x之后,z就可以继续借用x了。

1
2
3
4
5
6
7
8
9
#[test]
fn move_semantics4() {
    let mut x = 100;
    let y = &mut x;
    *y += 100;
    let z = &mut x;
    *z += 1000;
    assert_eq!(x, 1200);
}

move_semantics5

get_char不获取所有权,只获取值,不影响原来的值;string_uppercase获取所有权,进行转换,影响原来的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() {
    let data = "Rust is great!".to_string();

    get_char(&data);

    string_uppercase(data);
}

// Shouldn't take ownership
fn get_char(data: &String) -> char {
    data.chars().last().unwrap()
}

// Should take ownership
fn string_uppercase(mut data: String) {
    data = data.to_uppercase();

    println!("{data}");
}

Further information

For this section, the book links are especially important.

Structs

Rust has three struct types: a classic C struct, a tuple struct, and a unit struct.

structs1

这里主要表示常规结构体和元祖结构体的定义和使用,注意常规结构体都需要写字段名。

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
struct ColorRegularStruct {
    red: i32,
    green: i32,
    blue: i32,
}

struct ColorTupleStruct(i32, i32, i32);

#[derive(Debug)]
struct UnitStruct;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn regular_structs() {
        let green = ColorRegularStruct { red: 0, green: 255, blue: 0 };

        assert_eq!(green.red, 0);
        assert_eq!(green.green, 255);
        assert_eq!(green.blue, 0);
    }

    #[test]
    fn tuple_structs() {
        let green = ColorTupleStruct(0, 255, 0);

        assert_eq!(green.0, 0);
        assert_eq!(green.1, 255);
        assert_eq!(green.2, 0);
    }

    #[test]
    fn unit_structs() {
        let unit_struct = UnitStruct;
        let message = format!("{unit_struct:?}s are fun!");

        assert_eq!(message, "UnitStructs are fun!");
    }
}
  • #[derive(Debug)]:这行代码为结构体自动实现了Debug trait,使其可以使用:?进行格式化输出。
  • {unit_struct:?}:花括号内的 :? 是一种格式化标记,表示使用 Debug trait 来打印 unit_struct 的调试信息。

structs2

创建一下自己的结构体,满足结果判断就行

1
2
3
4
5
6
7
8
9
let your_order = Order {
    name: String::from("Hacker in Rust"),
    year: 2019,
    made_by_phone: false,
    made_by_mobile: false,
    made_by_email: true,
    item_number: 123,
    count: 1,
};

structs3

这里是给结构体增加方法,和常规定义方法类似。

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
#[derive(Debug)]
struct Package {
    sender_country: String,
    recipient_country: String,
    weight_in_grams: u32,
}

impl Package {
    fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self {
        if weight_in_grams < 10 {
            panic!("Can't ship a package with weight below 10 grams");
        }

        Self {
            sender_country,
            recipient_country,
            weight_in_grams,
        }
    }

    fn is_international(&self) -> bool {
        self.sender_country != self.recipient_country
    }

    fn get_fees(&self, cents_per_gram: u32) -> u32 {
        cents_per_gram * self.weight_in_grams
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn fail_creating_weightless_package() {
        let sender_country = String::from("Spain");
        let recipient_country = String::from("Austria");

        Package::new(sender_country, recipient_country, 5);
    }
}
  • 需要注意返回不能用return以及;
  • #[should_panic] 表示期望有异常,所以会打印出报错日志,但是这测试是通过的
  • :: 用于命名空间(模块)访问、调用关联函数和常量,以及泛型和特征的约束。
  • . 用于访问结构体或枚举的字段和方法,以及调用对象实例的方法。

    Further information

  • Structures
  • Method Syntax

Enums

Rust allows you to define types called “enums” which enumerate possible values. Enums are a feature in many languages, but their capabilities differ in each language. Rust’s enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell. Useful in combination with enums is Rust’s “pattern matching” facility, which makes it easy to run different code for different values of an enumeration.

enums1

定义一个没有参数的枚举

1
2
3
4
5
6
7
8
#[derive(Debug)]
enum Message {
    Resize,
    Move,
    Echo,
    ChangeColor,
    Quit,
}

enums2

丰富的枚举类型支持,可以支持结构体,元祖等混合使用.

1
2
3
4
5
6
7
8
9
10
11
12
#[derive(Debug)]
enum Message {
    // TODO: Define the different variants used below.
    Resize {
        width: i32,
        height: i32,
    },
    Move(Point),
    Echo(String),
    ChangeColor(i32, i32, i32),
    Quit
}

enums3

主要就是枚举的match操作,类似于kotlinwhen,不需要使用break

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
struct Point {
    x: u64,
    y: u64,
}

enum Message {
    Resize {
        width: u64,
        height: u64,
    },
    Move(Point),
    Echo(String),
    ChangeColor(u8, u8, u8),
    Quit,
}

struct State {
    width: u64,
    height: u64,
    position: Point,
    message: String,
    color: (u8, u8, u8),
    quit: bool,
}

impl State {
    fn resize(&mut self, width: u64, height: u64) {
        self.width = width;
        self.height = height;
    }

    fn move_position(&mut self, point: Point) {
        self.position = point;
    }

    fn echo(&mut self, s: String) {
        self.message = s;
    }

    fn change_color(&mut self, red: u8, green: u8, blue: u8) {
        self.color = (red, green, blue);
    }

    fn quit(&mut self) {
        self.quit = true;
    }

    fn process(&mut self, message: Message) {
        // TODO: Create a match expression to process the different message
        // variants using the methods defined above.
        match message {
            Message::Resize { width, height } => {
                self.resize(width, height)
            }
            Message::Move(point) => { self.move_position(point) }
            Message::Echo(msg) => { self.echo(msg) }
            Message::ChangeColor(red, green, blue) => { self.change_color(red, green, blue) }
            Message::Quit => { self.quit() }
        }
    }
}

Further information

Strings

Rust has two string types, a string slice (&str) and an owned string (String). We’re not going to dictate when you should use which one, but we’ll show you how to identify and create them, as well as use them.

strings1

需要返回String,而不是&str

1
2
3
fn current_favorite_color() -> String {
    String::from("blue")
}
  • 字符串字面量 &'str 是字符串字面量,它是一个指向程序数据段中某个位置的不可变引用,具有 'static 生命周期。
  • String 类型:String 是一个堆分配的字符串类型,提供了更多的功能和灵活性。

strings2

这里主要考查String&str

1
2
3
4
5
6
7
8
9
10
11
12
13
fn is_a_color_word(attempt: &str) -> bool {
    attempt == "green" || attempt == "blue" || attempt == "red"
}

fn main() {
    let word = String::from("green"); // Don't change this line.

    if is_a_color_word(&word) {
        println!("That is a color word I know!");
    } else {
        println!("That is not a color word I know.");
    }
}

因为不涉及改变,直接借用就行了,也可以使用as_str()方法

string3

字符串的一些操作

1
2
3
4
5
6
7
8
9
10
11
fn trim_me(input: &str) -> &str {
    input.trim()
}

fn compose_me(input: &str) -> String {
    format!("{} world!", input)
}

fn replace_me(input: &str) -> String {
    input.replace("cars","balloons")
}

string4

也是字符串的操作,多种情况的返回值

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() {
    string_slice("blue");

    string("red".to_string());

    string(String::from("hi"));

    string("rust is fun!".to_owned());

    string("nice weather".into());

    string(format!("Interpolation {}", "Station"));

    // WARNING: This is byte indexing, not character indexing.
    // Character indexing can be done using `s.chars().nth(INDEX)`.
    string_slice(&String::from("abc")[0..1]);

    string_slice("  hello there ".trim());

    string("Happy Monday!".replace("Mon", "Tues"));

    string("mY sHiFt KeY iS sTiCkY".to_lowercase());
}

Further information

Modules

In this section we’ll give you an introduction to Rust’s module system.

modules1

这里是考察module的可见性,默认是私有的,需要在外访问加pub

1
2
3
4
5
6
7
8
9
10
mod sausage_factory {
    fn get_secret_recipe() -> String {
        String::from("Ginger")
    }

    pub fn make_sausage() {
        get_secret_recipe();
        println!("sausage!");
    }
}

modules2

和1类似,这里是重导,外部访问要求内部必须都是pub

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#[allow(dead_code)]
mod delicious_snacks {
    pub use self::fruits::PEAR as fruit;
    pub use self::veggies::CUCUMBER as veggie;

    mod fruits {
        pub const PEAR: &str = "Pear";
        pub const APPLE: &str = "Apple";
    }

    mod veggies {
        pub const CUCUMBER: &str = "Cucumber";
        pub const CARROT: &str = "Carrot";
    }
}

module3

简单演示怎么导入公共模块内容

1
use std::time::{SystemTime, UNIX_EPOCH};

Further information

Hashmaps

A hash map allows you to associate a value with a particular key. You may also know this by the names unordered map in C++, dictionary in Python or an associative array in other languages.

This is the other data structure that we’ve been talking about before, when talking about Vecs.

hashmap1

创建和插入

1
2
3
4
5
6
7
8
9
10
fn fruit_basket() -> HashMap<String, u32> {
    let mut basket = HashMap::new();

    basket.insert(String::from("banana"), 2);
    basket.insert(String::from("apple"), 3);
    basket.insert(String::from("watermelon"), 1);

    basket
}

hashmap2

如果不存在再插入

1
2
3
4
5
6
7
8
9
10
11
12
13
fn fruit_basket(basket: &mut HashMap<Fruit, u32>) {
    let fruit_kinds = [
        Fruit::Apple,
        Fruit::Banana,
        Fruit::Mango,
        Fruit::Lychee,
        Fruit::Pineapple,
    ];

    for fruit in fruit_kinds {
        basket.entry(fruit).or_insert(1);
    }
}

hashmap3

一个插入的小练习

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
use std::collections::HashMap;

#[derive(Default)]
struct Team {
    goals_scored: u8,
    goals_conceded: u8,
}

fn build_scores_table(results: &str) -> HashMap<&str, Team> {
    let mut scores = HashMap::new();

    for line in results.lines() {
        let mut split_iterator = line.split(',');
        // NOTE: We use `unwrap` because we didn't deal with error handling yet.
        let team_1_name = split_iterator.next().unwrap();
        let team_2_name = split_iterator.next().unwrap();
        let team_1_score: u8 = split_iterator.next().unwrap().parse().unwrap();
        let team_2_score: u8 = split_iterator.next().unwrap().parse().unwrap();

        let entry1 = scores.entry(team_1_name).or_insert(Team { goals_scored: 0, goals_conceded: 0 });
        entry1.goals_scored += team_1_score;
        entry1.goals_conceded += team_2_score;
        let entry2 = scores.entry(team_2_name).or_insert(Team { goals_scored: 0, goals_conceded: 0 });
        entry2.goals_scored += team_2_score;
        entry2.goals_conceded += team_1_score;
    }

    scores
}

quiz2

阶段性小练习,总结

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
enum Command {
    Uppercase,
    Trim,
    Append(usize),
}

mod my_module {
    use super::Command;

    pub fn transformer(input: Vec<(String, Command)>) -> Vec<String> {
        input.iter().map(|(item, cmd)| {
            match cmd {
                Command::Uppercase => {
                    item.to_uppercase()
                }
                Command::Trim => {
                    item.trim().to_string()
                }
                Command::Append(size) => {
                    format!("{}{}", item, "bar".repeat(*size))
                }
            }
        }).collect()
    }
}

#[cfg(test)]
mod tests {
    use super::my_module::{transformer};
    use super::Command;

    #[test]
    fn it_works() {
        let input = vec![
            ("hello".to_string(), Command::Uppercase),
            (" all roads lead to rome! ".to_string(), Command::Trim),
            ("foo".to_string(), Command::Append(1)),
            ("bar".to_string(), Command::Append(5)),
        ];
        let output = transformer(input);

        assert_eq!(
            output,
            [
                "HELLO",
                "all roads lead to rome!",
                "foobar",
                "barbarbarbarbarbar",
            ]
        );
    }
}

Further information

Options

Type Option represents an optional value: every Option is either Some and contains a value, or None, and does not. Option types are very common in Rust code, as they have a number of uses:

  • Initial values
  • Return values for functions that are not defined over their entire input range (partial functions)
  • Return value for otherwise reporting simple errors, where None is returned on error
  • Optional struct fields
  • Struct fields that can be loaned or “taken”
  • Optional function arguments
  • Nullable pointers
  • Swapping things out of difficult situations

Options1

Option的两个值,Some和None的基本使用

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 maybe_icecream(hour_of_day: u16) -> Option<u16> {
    if hour_of_day < 22 { Some(5) }
    else if hour_of_day < 24 { Some(0) }
    else { None }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn raw_value() {
        let icecreams = maybe_icecream(12).unwrap();

        assert_eq!(icecreams, 5); // Don't change this line.
    }

    #[test]
    fn check_icecream() {
        assert_eq!(maybe_icecream(0), Some(5));
        assert_eq!(maybe_icecream(9), Some(5));
        assert_eq!(maybe_icecream(18), Some(5));
        assert_eq!(maybe_icecream(22), Some(0));
        assert_eq!(maybe_icecream(23), Some(0));
        assert_eq!(maybe_icecream(24), None);
        assert_eq!(maybe_icecream(25), None);
    }
}

Options2

Option的使用,可以使用if let表达式解构里面的值,或者while let

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
#[cfg(test)]
mod tests {
    #[test]
    fn simple_option() {
        let target = "rustlings";
        let optional_target = Some(target);

        // TODO: Make this an if-let statement whose value is `Some`.
        if let Some(word) = optional_target {
            assert_eq!(word, target);
        }
    }

    #[test]
    fn layered_option() {
        let range = 10;
        let mut optional_integers: Vec<Option<i8>> = vec![None];

        for i in 1..=range {
            optional_integers.push(Some(i));
        }

        let mut cursor = range;

        // TODO: Make this a while-let statement. Remember that `Vec::pop()`
        // adds another layer of `Option`. You can do nested pattern matching
        // in if-let and while-let statements.
        while let Some(Some(integer)) = optional_integers.pop() {
            assert_eq!(integer, cursor);
            cursor -= 1;
        }

        assert_eq!(cursor, 0);
    }
}
  • if let Some(word) = optional_target { ... }:这是一个 if let 表达式,用于模式匹配和解构 optional_target。如果 optional_targetSome 枚举,那么将 word 绑定为 Some 内部的值,并执行相应的代码块。
  • while let Some(Some(integer)) = optional_integers.pop() { ... }
    • while let 表达式用于循环处理 optional_integers 向量的元素,直到向量为空为止。
    • Vec::pop() 方法从向量的末尾移除一个元素,并返回其值。由于 optional_integersVec<Option<i8>> 类型,所以每次 pop() 返回的是 Option<i8>
    • Some(Some(integer)) 是模式匹配的方式,表示要匹配的值是 Some 枚举中的另一个 Some 枚举,其中包含一个整数 integer
    • 在循环体内,断言 integer 的值应该等于 cursor 的当前值,并且 cursor 自减 1。

Option3

match直接使用对象,导致了主权丢失,后面继续使用值会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let optional_point = Some(Point { x: 100, y: 200 });

    // TODO: Fix the compiler error by adding something to this match statement.
    match optional_point {
        Some(ref p) => println!("Co-ordinates are {},{}", p.x, p.y),
        _ => panic!("No match!"),
    }

    println!("{optional_point:?}"); // Don't change this line.
}

ref表示是借用值,避免所有权的转移

Further Information

Error handling

Most errors aren’t serious enough to require the program to stop entirely. Sometimes, when a function fails, it’s for a reason that you can easily interpret and respond to. For example, if you try to open a file and that operation fails because the file doesn’t exist, you might want to create the file instead of terminating the process.

errors1

异常判断和前面判空类似,也是枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
#[rustc_diagnostic_item = "Result"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Result<T, E> {
    /// Contains the success value
    #[lang = "Ok"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),

    /// Contains the error value
    #[lang = "Err"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}

所以改为

1
2
3
4
5
6
7
fn generate_nametag_text(name: String) -> Result<String, String> {
    if name.is_empty() {
        Err("Empty names aren't allowed".to_string())
    } else {
        Ok(format!("Hi! My name is {name}"))
    }
}

errors2

进行一个解码

1
2
3
4
5
6
7
8
9
10
fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
    let processing_fee = 1;
    let cost_per_item = 5;

    let qty = item_quantity.parse::<i32>();
    match qty {
        Ok(v) => { Ok(v * cost_per_item + processing_fee) }
        Err(e) => { Err(e) }
    }
}

errors3

因为这里没有返回值,所以不能直接用?去抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
    let mut tokens = 100;
    let pretend_user_input = "8";

    let cost = total_cost(pretend_user_input).unwrap();

    if cost > tokens {
        println!("You can't afford that many!");
    } else {
        tokens -= cost;
        println!("You now have {tokens} tokens.");
    }
}

errors4

简单的演示怎么返回Ok与Err

1
2
3
4
5
6
7
8
9
10
11
impl PositiveNonzeroInteger {
    fn new(value: i64) -> Result<Self, CreationError> {
        if value > 0 {
            Ok(Self(value as u64))
        } else if value == 0 {
            Err(CreationError::Zero)
        } else {
            Err(CreationError::Negative)
        }
    }
}

error5

因为会抛出异常,所以表明异常返回值即可

1
2
3
4
5
6
fn main() -> Result<(), Box<dyn Error>> {
    let pretend_user_input = "42";
    let x: i64 = pretend_user_input.parse()?;
    println!("output={:?}", PositiveNonzeroInteger::new(x)?);
    Ok(())
}

error6

map_err对错误值进行转换,而不影响正确的值

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
#[derive(PartialEq, Debug)]
enum ParsePosNonzeroError {
    Creation(CreationError),
    ParseInt(ParseIntError),
}

impl ParsePosNonzeroError {
    fn from_creation(err: CreationError) -> Self {
        Self::Creation(err)
    }

    fn from_parseint(err: ParseIntError) -> Self { Self::ParseInt(err) }
}

#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);

impl PositiveNonzeroInteger {
    fn new(value: i64) -> Result<Self, CreationError> {
        match value {
            x if x < 0 => Err(CreationError::Negative),
            0 => Err(CreationError::Zero),
            x => Ok(Self(x as u64)),
        }
    }

    fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> {
        let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;
        Self::new(x).map_err(ParsePosNonzeroError::from_creation)
    }
}

Further information

Generics

Generics is the topic of generalizing types and functionalities to broader cases. This is extremely useful for reducing code duplication in many ways, but can call for some rather involved syntax. Namely, being generic requires taking great care to specify over which types a generic type is actually considered valid. The simplest and most common use of generics is for type parameters.

generics1

需要一个可以同时存储u8i8的数据结构,向上提升一下即可

1
2
3
4
5
6
7
8
9
10
11
fn main() {
    let mut numbers: Vec<i16> = Vec::new();

    // Don't change the lines below.
    let n1: u8 = 42;
    numbers.push(n1.into());
    let n2: i8 = -1;
    numbers.push(n2.into());

    println!("{numbers:?}");
}

generics2

泛型类和泛型方法

1
2
3
4
5
6
7
8
9
struct Wrapper<T> {
    value: T,
}

impl<T> Wrapper<T> {
    fn new(value: T) -> Self {
        Wrapper { value }
    }
}

Further information

Traits

A trait is a collection of methods.

Data types can implement traits. To do so, the methods making up the trait are defined for the data type. For example, the String data type implements the From<&str> trait. This allows a user to write String::from("hello").

In this way, traits are somewhat similar to Java interfaces and C++ abstract classes.

Some additional common Rust traits include:

  • Clone (the clone method)
  • Display (which allows formatted display via {})
  • Debug (which allows formatted display via {:?})

Because traits indicate shared behavior between data types, they are useful when writing generics.

traits1

String实现一个特征,和接口类似

1
2
3
4
5
6
7
8
9
10
trait AppendBar {
    fn append_bar(self) -> Self;
}

impl AppendBar for String {
    // TODO: Implement `AppendBar` for the type `String`.
    fn append_bar(self) -> Self {
        self + "Bar"
    }
}

traits2

因为Vec是支持泛型的,这里要添加的为String,所以先把泛型写死,然后因为这里传入的是self,是不可变的,所以需要先转化为可变动

1
2
3
4
5
6
7
8
9
10
11
trait AppendBar {
    fn append_bar(self) -> Self;
}

impl AppendBar for Vec<String> {
    fn append_bar(self) -> Self {
        let mut vec = self.clone();
        vec.push(String::from("Bar"));
        vec
    }
}

traits3

需要一个简单的默认实现,和一般方法没什么区别

1
2
3
4
5
trait Licensed {
    fn licensing_info(&self) -> String {
        String::from("Default license")
    }
}

traits4

对泛型的直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
trait Licensed {
    fn licensing_info(&self) -> String {
        "Default license".to_string()
    }
}

struct SomeSoftware;
struct OtherSoftware;

impl Licensed for SomeSoftware {}
impl Licensed for OtherSoftware {}

fn compare_license_types(software1: impl Licensed, software2: impl Licensed) -> bool {
    software1.licensing_info() == software2.licensing_info()
}

traits5

泛型的多从约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
trait SomeTrait {
    fn some_function(&self) -> bool {
        true
    }
}

trait OtherTrait {
    fn other_function(&self) -> bool {
        true
    }
}

struct SomeStruct;
impl SomeTrait for SomeStruct {}
impl OtherTrait for SomeStruct {}

struct OtherStruct;
impl SomeTrait for OtherStruct {}
impl OtherTrait for OtherStruct {}

fn some_func(item: impl SomeTrait + OtherTrait) -> bool {
    item.some_function() && item.other_function()
}

Further information

quiz3

泛型总结,确保T是实现了的Display

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct ReportCard<T> {
    grade: T,
    student_name: String,
    student_age: u8,
}

impl<T: std::fmt::Display> ReportCard<T> {
    fn print(&self) -> String {
        format!(
            "{} ({}) - achieved a grade of {}",
            &self.student_name, &self.student_age, &self.grade,
        )
    }
}

Lifetimes

Lifetimes tell the compiler how to check whether references live long enough to be valid in any given situation. For example lifetimes say “make sure parameter ‘a’ lives as long as parameter ‘b’ so that the return value is valid”.

They are only necessary on borrows, i.e. references, since copied parameters or moves are owned in their scope and cannot be referenced outside. Lifetimes mean that calling code of e.g. functions can be checked to make sure their arguments are valid. Lifetimes are restrictive of their callers.

If you’d like to learn more about lifetime annotations, the lifetimekata project has a similar style of exercises to Rustlings, but is all about learning to write lifetime annotations.

lifetimes1

当有多个借用参数时,这时无法区分,所以需要显示指定生命周期之间的关系,方便编译器判断回收。

1
2
3
4
5
6
7
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

lifetimes2

因为生命周期是根据最短的来的,所以括号后就已经不安全了,所以得把最后的返回值打印放到括号结束之前

1
2
3
4
5
6
7
8
9
fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(&string1, &string2);
        println!("The longest string is '{result}'");
    }
}

lifetimes3

给struct内的借用对象指定生命周期

1
2
3
4
struct Book<'a> {
    author: &'a str,
    title: &'a str,
}

Further information

Tests

Going out of order from the book to cover tests – many of the following exercises will ask you to make tests pass!

test1

使用use引入要测试的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn is_even(n: i64) -> bool {
    n % 2 == 0
}

#[cfg(test)]
mod tests {
    use super::is_even;

    #[test]
    fn you_can_assert() {
        assert!(is_even(10));
        assert!(!is_even(11));
    }
}

test2

assert_eq!判断两值相等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn power_of_2(n: u8) -> u64 {
    1 << n
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn you_can_assert_eq() {
        assert_eq!(power_of_2(2), 4);
        assert_eq!(power_of_2(3), 8);
        assert_eq!(power_of_2(4), 16);
        assert_eq!(power_of_2(8), 256);
    }
}

test3

主要是验证需要抛出异常,前面的测试也遇到过类似的,这里是让添加注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn correct_width_and_height() {
        let rect = Rectangle::new(10, 20);
        assert_eq!(rect.width, 10); // Check width
        assert_eq!(rect.height, 20); // Check height
    }

    #[test]
    #[should_panic]
    fn negative_width() {
        let _rect = Rectangle::new(-10, 10);
    }

    #[test]
    #[should_panic]
    fn negative_height() {
        let _rect = Rectangle::new(10, -10);
    }
}

Further information

Iterators

This section will teach you about Iterators.

iterators1

迭代器的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[cfg(test)]
mod tests {
    #[test]
    fn iterators() {
        let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"];

        // TODO: Create an iterator over the array.
        let mut fav_fruits_iterator = my_fav_fruits.iter();

        assert_eq!(fav_fruits_iterator.next(), Some(&"banana"));
        assert_eq!(fav_fruits_iterator.next(), Some(&"custard apple")); // TODO: Replace `todo!()`
        assert_eq!(fav_fruits_iterator.next(), Some(&"avocado"));
        assert_eq!(fav_fruits_iterator.next(), Some(&"peach")); // TODO: Replace `todo!()`
        assert_eq!(fav_fruits_iterator.next(), Some(&"raspberry"));
        assert_eq!(fav_fruits_iterator.next(), None); // TODO: Replace `todo!()`
    }
}

iterators2

字符串和数组的迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn capitalize_first(input: &str) -> String {
    let mut chars = input.chars();
    match chars.next() {
        None => String::new(),
        Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
    }
}

fn capitalize_words_vector(words: &[&str]) -> Vec<String> {
    words.iter().map(|x| {
        capitalize_first(x)
    }).collect()
}

fn capitalize_words_string(words: &[&str]) -> String {
    let mut string = String::new();
    words.iter().for_each(|x| {
        string.push_str(capitalize_first(x).as_str());
    });
    string
}

iterators3

完全相同的函数体,可以返回不同的结果

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
#[derive(Debug, PartialEq, Eq)]
enum DivisionError {
    // Example: 42 / 0
    DivideByZero,
    // Only case for `i64`: `i64::MIN / -1` because the result is `i64::MAX + 1`
    IntegerOverflow,
    // Example: 5 / 2 = 2.5
    NotDivisible,
}

fn divide(a: i64, b: i64) -> Result<i64, DivisionError> {
    if b == 0 {
        Err(DivisionError::DivideByZero)
    } else if a == i64::MIN && b == -1 {
        Err(DivisionError::IntegerOverflow)
    } else if a % b == 0 {
        Ok(a / b)
    } else {
        Err(DivisionError::NotDivisible)
    }
}

fn result_with_list() -> Result<Vec<i64>, DivisionError> {
    let numbers = [27, 297, 38502, 81];
    numbers.into_iter().map(|n| divide(n, 27)).collect()
}

fn list_of_results() -> Vec<Result<i64, DivisionError>> {
    let numbers = [27, 297, 38502, 81];
    numbers.into_iter().map(|n| divide(n, 27)).collect()
}
  • 返回 Result<Vec<i32>, DivisionError>,表示这个函数在遇到任何错误时会立即返回错误,否则会返回包含所有成功结果的向量。
  • 返回 Vec<Result<i32, DivisionError>>, 这个函数将所有结果(成功和失败)都收集到向量中,而不会在遇到错误时立即返回。

iterators4

迭代器求积

1
2
3
fn factorial(num: u64) -> u64 {
    (1..=num).product()
}

或者使用fold但是会有警告

1
2
3
fn factorial(num: u64) -> u64 {
    (1..=num).fold(1, |acc, n| acc * n)
}

iterators5

多层的迭代

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
use std::collections::HashMap;

#[derive(Clone, Copy, PartialEq, Eq)]
enum Progress {
    None,
    Some,
    Complete,
}

fn count_for(map: &HashMap<String, Progress>, value: Progress) -> usize {
    let mut count = 0;
    for val in map.values() {
        if *val == value {
            count += 1;
        }
    }
    count
}

fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
    map.iter().filter(|x| { x.1 == &value }).count()
}

fn count_collection_for(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    let mut count = 0;
    for map in collection {
        for val in map.values() {
            if *val == value {
                count += 1;
            }
        }
    }
    count
}

fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
    collection.iter().map(|x| { count_iterator(x, value) }).sum()
}

Further information

Smart Pointers

In Rust, smart pointers are variables that contain an address in memory and reference some other data, but they also have additional metadata and capabilities. Smart pointers in Rust often own the data they point to, while references only borrow data.

box

用于把对象分配在堆上,也可以用于固定递归类型的数据大小,这里就是枚举递归了,所以用Box来解决

1
2
3
4
5
6
7
8
9
10
11
12
13
#[derive(PartialEq, Debug)]
enum List {
    Cons(i32, Box<List>),
    Nil,
}

fn create_empty_list() -> List {
    List::Nil
}

fn create_non_empty_list() -> List {
    List::Cons(32, Box::new(List::Nil))
}

rc

Rust的所有权使得一个对象只能有一个所有者,但在一些特殊的情况时很难处理,所以这里就引入了一个引用计数的对象Rc

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use std::rc::Rc;

#[derive(Debug)]
struct Sun;

#[allow(dead_code)]
#[derive(Debug)]
enum Planet {
    Mercury(Rc<Sun>),
    Venus(Rc<Sun>),
    Earth(Rc<Sun>),
    Mars(Rc<Sun>),
    Jupiter(Rc<Sun>),
    Saturn(Rc<Sun>),
    Uranus(Rc<Sun>),
    Neptune(Rc<Sun>),
}

impl Planet {
    fn details(&self) {
        println!("Hi from {self:?}!");
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rc1() {
        let sun = Rc::new(Sun);
        println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

        let mercury = Planet::Mercury(Rc::clone(&sun));
        println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
        mercury.details();

        let venus = Planet::Venus(Rc::clone(&sun));
        println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
        venus.details();

        let earth = Planet::Earth(Rc::clone(&sun));
        println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
        earth.details();

        let mars = Planet::Mars(Rc::clone(&sun));
        println!("reference count = {}", Rc::strong_count(&sun)); // 5 references
        mars.details();

        let jupiter = Planet::Jupiter(Rc::clone(&sun));
        println!("reference count = {}", Rc::strong_count(&sun)); // 6 references
        jupiter.details();

        let saturn = Planet::Saturn(Rc::clone(&sun));
        println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
        saturn.details();

        let uranus = Planet::Uranus(Rc::clone(&sun));
        println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
        uranus.details();

        let neptune = Planet::Neptune(Rc::clone(&sun));
        println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
        neptune.details();

        assert_eq!(Rc::strong_count(&sun), 9);

        drop(neptune);
        println!("reference count = {}", Rc::strong_count(&sun)); // 8 references

        drop(uranus);
        println!("reference count = {}", Rc::strong_count(&sun)); // 7 references

        drop(saturn);
        println!("reference count = {}", Rc::strong_count(&sun)); // 6 references

        drop(jupiter);
        println!("reference count = {}", Rc::strong_count(&sun)); // 5 references

        drop(mars);
        println!("reference count = {}", Rc::strong_count(&sun)); // 4 references

        drop(earth);
        println!("reference count = {}", Rc::strong_count(&sun)); // 3 references

        drop(venus);
        println!("reference count = {}", Rc::strong_count(&sun)); // 2 references

        drop(mercury);
        println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

        assert_eq!(Rc::strong_count(&sun), 1);
    }
}

arc

对于单线程场景,rc性能更高,但是在多线程场景并不适用,所以增加了Arc在多线程场景,当然性能会有损耗

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
#![forbid(unused_imports)]
use std::{sync::Arc, thread};

fn main() {
    let numbers: Vec<_> = (0..100u32).collect();

    let shared_numbers = Arc::new(numbers);

    let mut join_handles = Vec::new();

    for offset in 0..8 {
        let child_numbers = Arc::clone(&shared_numbers);

        let handle = thread::spawn(move || {
            let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
            println!("Sum of offset {offset} is {sum}");
        });

        join_handles.push(handle);
    }

    for handle in join_handles.into_iter() {
        handle.join().unwrap();
    }
}

cow

可以指向一个借用的(&T)或拥有的(T)数据。这对于处理那些可能需要克隆数据以进行修改的场景非常有用,但在大多数情况下只是读取数据,从而避免不必要的克隆开销。Cow常用于优化性能,特别是当你有只读和少数写入的场景。它可以帮助你延迟克隆数据,直到真的需要修改数据时才进行克隆。

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
use std::borrow::Cow;

fn abs_all(input: &mut Cow<[i32]>) {
    for ind in 0..input.len() {
        let value = input[ind];
        if value < 0 {
            input.to_mut()[ind] = -value;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn reference_mutation() {
        let vec = vec![-1, 0, 1];
        let mut input = Cow::from(&vec);
        abs_all(&mut input);
        assert!(matches!(input, Cow::Owned(_)));
    }

    #[test]
    fn reference_no_mutation() {
        let vec = vec![0, 1, 2];
        let mut input = Cow::from(&vec);
        abs_all(&mut input);
        assert!(matches!(input, Cow::Borrowed(_)));
    }

    #[test]
    fn owned_no_mutation() {
        let vec = vec![0, 1, 2];
        let mut input = Cow::from(vec);
        abs_all(&mut input);
        assert!(matches!(input, Cow::Owned(_)));
    }

    #[test]
    fn owned_mutation() {
        let vec = vec![-1, 0, 1];
        let mut input = Cow::from(vec);
        abs_all(&mut input);
        assert!(matches!(input, Cow::Owned(_)));
    }
}

Further Information

Threads

In most current operating systems, an executed program’s code is run in a process, and the operating system manages multiple processes at once. Within your program, you can also have independent parts that run simultaneously. The features that run these independent parts are called threads.

threads1.rs

因为定义的results没有申明类型,并且后面又没有用到,所以不知道他的泛型。这里我们可以为他添加泛型,也可以在后面用起来,这样就可以智能推断了。

1
2
3
4
5
6
let mut results = Vec::new();
for handle in handles {
    if let Ok(result) = handle.join() {
        results.push(result)
    }
}

threads2.rs

正如提示写的那样,Arc在多线程里面并不够用,所以对于多线程写入的场景,我们需要进行加锁处理。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
struct JobStatus {
    jobs_done: u32,
}

fn main() {
    let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 }));

    let mut handles = Vec::new();
    for _ in 0..10 {
        let status_shared = Arc::clone(&status);
        let handle = thread::spawn(move || {
            thread::sleep(Duration::from_millis(250));

            status_shared.lock().unwrap().jobs_done += 1;
        });
        handles.push(handle);
    }

    // Waiting for all jobs to complete.
    for handle in handles {
        handle.join().unwrap();
    }

    println!("Jobs done: {}", status.lock().unwrap().jobs_done);
}

thread3.rs

因为tx会被move,所以可以考虑clone一份出来,两个线程各用一份

1
2
3
4
5
6
7
8
9
let sender = tx.clone();
// into the first thread. How could you solve this problem?
thread::spawn(move || {
    for val in q.first_half {
        println!("Sending {val:?}");
        sender.send(val).unwrap();
        thread::sleep(Duration::from_millis(250));
    }
});

Further information

This post is licensed under CC BY 4.0 by the author.