0%

第一阶段:RUST语法练习

variables6.rs

关于const:

Rust的常量必须加类型。那为什么常量不推导呢? 这是因为故意这样设计的,指定类型就不会因为自动推导类型出问题。

常量 - 通过例子学 Rust 中文版

static, const, let 声明变量有什么区别? - Rust语言中文社区

1
2
3
4
5
6
7
8
9
10
11
// variables6.rs
//
// Execute `rustlings hint variables6` or use the `hint` watch subcommand for a
// hint.

// const类型需要声明变量类型

const NUMBER : i32 = 3;
fn main() {
println!("Number {}", NUMBER);
}

primitive_types3.rs

数组(array)的三要素:

  • 长度固定
  • 元素必须有相同的类型
  • 依次线性排列

数组必须要初始化,以下这种写法会报错:

1
2
3
4
5
6
7
8
9
fn main() {
let a:[i32; 100];

if a.len() >= 100 {
println!("Wow, that's a big array!");
} else {
println!("Meh, I eat arrays like that for breakfast.");
}
}

改为这样就编译通过:

1
2
3
4
5
6
7
8
9
fn main() {
let a:[i32; 100] = [0; 100];

if a.len() >= 100 {
println!("Wow, that's a big array!");
} else {
println!("Meh, I eat arrays like that for breakfast.");
}
}

vecs2.rs

iter_mut():

iter_mut() 创建一个可变引用迭代器。当你想要修改集合中的元素时,应使用 iter_mut()。iter_mut() 返回的迭代器将生成集合中每个元素的可变引用。

1
2
3
4
let mut v = vec![1, 2, 3];
for i in v.iter_mut() {
*i += 1;
}

move_semantics2.rs

1
2
3
4
5
6
7
8
// move_semantics2.rs
//
// Expected output:
// vec0 has length 3, with contents `[22, 44, 66]`
// vec1 has length 4, with contents `[22, 44, 66, 88]`
//
// Execute `rustlings hint move_semantics2` or use the `hint` watch subcommand
// for a hint.

方法1:

将vec0的内容clone一份传进函数,然后返回值的所有权交给vec1,此时vec1=[22, 44, 66],vec0=[]

然后再把vec0的内容clone一份传进函数,然后返回值的所有权交给vec0,此时vec1=[22, 44, 66],vec0=[22, 44, 66]

这个时候不管是vec0还是vec1都拥有一片自己的堆上的空间,二者互不相关,因此vec1.push(88)只会改变vec1的值,且vec0的值也还存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
let mut vec0 = Vec::new();

let mut vec1 = fill_vec(vec0.clone());
vec0 = fill_vec(vec0.clone());
println!("{} has length {}, with contents: `{:?}`", "vec0", vec0.len(), vec0);

vec1.push(88);

println!("{} has length {}, with contents `{:?}`", "vec1", vec1.len(), vec1);
}

fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
let mut vec = vec;

vec.push(22);
vec.push(44);
vec.push(66);

vec
}

方法2:

首先,创建了vec0的一个可变引用&mut vec0,将这个可变引用传入函数,函数接受一个可变引用类型,然后对其进行操作,也就是操作了vec0指向的那片堆,因此在函数内部,vec0就已经变成了[22, 44, 66]

然后最后返回vec.to_vec(),相当于又创建了一个新的vec,作为vec1绑定的值,因此vec1和vec0又变成了互不相关的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
let mut vec0 = Vec::new();

let mut vec1 = fill_vec(&mut vec0);

println!("{} has length {}, with contents: `{:?}`", "vec0", vec0.len(), vec0);

vec1.push(88);

println!("{} has length {}, with contents `{:?}`", "vec1", vec1.len(), vec1);
}

fn fill_vec(vec: &mut Vec<i32>) -> Vec<i32> {
let mut vec = vec;

vec.push(22);
vec.push(44);
vec.push(66);

vec.to_vec()
}

move_semantics4.rs

这个的意思就是函数不再接收参数,而是直接在里面新创建一个包含[22, 44, 66]的vector返回

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
// move_semantics4.rs
//
// Refactor this code so that instead of passing `vec0` into the `fill_vec`
// function, the Vector gets created in the function itself and passed back to
// the main function.
//
// Execute `rustlings hint move_semantics4` or use the `hint` watch subcommand
// for a hint.

fn main() {
// let vec0 = Vec::new();

let mut vec1 = fill_vec();

println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);

vec1.push(88);

println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
}

// `fill_vec()` no longer takes `vec: Vec<i32>` as argument
fn fill_vec() -> Vec<i32> {
let mut vec = Vec::new();

vec.push(22);
vec.push(44);
vec.push(66);

vec
}

structs3.rs

结构体,有几点值得注意:

  1. 初始化实例时,每个字段都需要进行初始化
  2. 初始化时的字段顺序不需要和结构体定义时的顺序一致

需要注意的是,必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。

结构体方法:

  • Unlike functions, methods are defined within the context of a struct,and their first parameter is always self, which represents the instance of the struct the method is being called on.

  • we still need to use the & in front of the self shorthand to indicate that this method borrows the Self instance, just as we did in rectangle: &Rectangle. Methods can take ownership of self, borrow self immutably, as we’ve done here, or borrow self mutably, just as they can any other parameter.

  • 方法参数里面不止&self:package.get_fees(cents_per_gram)

    比如这个,get_fees是结构体的方法,它在结构体里面是这样定义的:

    1
    2
    3
    4
    fn get_fees(&self, cents_per_gram: i32) -> i32 {
    // Something goes here...
    self.weight_in_grams*cents_per_gram
    }

    也就是说,第二个参数跟在&self后面就好了,在外部调用结构体时只需要传入那个另外的参数

例子:

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
// structs3.rs
//
// Structs contain data, but can also have logic. In this exercise we have
// defined the Package struct and we want to test some logic attached to it.
// Make the code compile and the tests pass!
//
// Execute `rustlings hint structs3` or use the `hint` watch subcommand for a
// hint.

// I AM NOT DONE

#[derive(Debug)]
struct Package {
sender_country: String,
recipient_country: String,
weight_in_grams: i32,
}

impl Package {
fn new(sender_country: String, recipient_country: String, weight_in_grams: i32) -> Package {
if weight_in_grams <= 0 {
panic!("Can not ship a weightless package.")
} else {
Package {
sender_country,
recipient_country,
weight_in_grams,
}
}
}

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

fn get_fees(&self, cents_per_gram: i32) -> i32 {
// Something goes here...
self.weight_in_grams*cents_per_gram
}
}

#[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, -2210);
}

#[test]
fn create_international_package() {
let sender_country = String::from("Spain");
let recipient_country = String::from("Russia");

let package = Package::new(sender_country, recipient_country, 1200);

assert!(package.is_international());
}

#[test]
fn create_local_package() {
let sender_country = String::from("Canada");
let recipient_country = sender_country.clone();

let package = Package::new(sender_country, recipient_country, 1200);

assert!(!package.is_international());
}

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

let cents_per_gram = 3;

let package = Package::new(sender_country, recipient_country, 1500);

assert_eq!(package.get_fees(cents_per_gram), 4500);
assert_eq!(package.get_fees(cents_per_gram * 2), 9000);
}
}

enums2.rs

更为复杂的枚举:

Move:包含了一个匿名结构体

Echo:包含了一个String

ChangeColor:包含了三个整数

Quit:没有关联任何数据

1
2
3
4
5
6
7
enum Message {
// TODO: define the different variants used below
Move { x: i32, y: i32 },
Echo(String),
ChangeColor(i32, i32, i32),
Quit
}

enums3.rs

模式匹配和模式绑定

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
95
// enums3.rs
//
// Address all the TODOs to make the tests pass!
//
// Execute `rustlings hint enums3` or use the `hint` watch subcommand for a
// hint.

// I AM NOT DONE

enum Message {
// TODO: implement the message variant types based on their usage below
ChangeColor(u8, u8, u8),
Echo(String),
Move(Point),
Quit
}

struct Point {
x: u8,
y: u8,
}

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

impl State {
fn change_color(&mut self, color: (u8, u8, u8)) {
self.color = color;
}

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

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

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

fn process(&mut self, message: Message) {
// TODO: create a match expression to process the different message
// variants
// Remember: When passing a tuple as a function argument, you'll need
// extra parentheses: fn function((t, u, p, l, e))

// 模式匹配message
match message {
// 结构枚举类型绑定的值,相当于还完成了语句:Message::ChangeColor(r,g,b)=message
Message::ChangeColor(r, g, b) => {
self.change_color((r, g, b));
}
Message::Echo(text) => {
self.echo(text);
},
Message::Move(point) => {
self.move_position(point);
}
Message::Quit => {
self.quit();
}
}
}
}

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

#[test]
fn test_match_message_call() {
// 创建了一个结构体变量state,并为其赋值
let mut state = State {
quit: false,
position: Point { x: 0, y: 0 },
color: (0, 0, 0),
message: "hello world".to_string(),
};
// 调用结构体内的方法process,参数为枚举类型,每个类型绑定了一个值
state.process(Message::ChangeColor(255, 0, 255));
state.process(Message::Echo(String::from("hello world")));
state.process(Message::Move(Point { x: 10, y: 15 }));
state.process(Message::Quit);

assert_eq!(state.color, (255, 0, 255));
assert_eq!(state.position.x, 10);
assert_eq!(state.position.y, 15);
assert_eq!(state.quit, true);
assert_eq!(state.message, "hello world");
}
}

strings3.rs

  • 字符串的字面量是切片

    一般写字符串可以这样写:let s = "Hello, world!";

    实际上,s 的类型是 &str,因此你也可以这样声明:let s: &str = "Hello, world!";

  • String转&str:取引用即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fn main() {
    let s = String::from("hello,world!");
    say_hello(&s);
    say_hello(&s[..]);
    say_hello(s.as_str());
    }

    fn say_hello(s: &str) {
    println!("{}",s);
    }
  • &str转String:"hello,world".to_string()或者String::from("hello,world")

  • String的操作(必须把String声明为mut)

    由于 String 是可变字符串,因此只有String 这种字符串可以被操作更改

    • push(),在末尾追加字符;push_str() ,在末尾追加字符串

      这两个方法都是在原有的字符串上追加,并不会返回新的字符串,即返回值是()

    • insert() 方法插入单个字符,insert_str() 方法插入字符串字面量

      也是在原有的字符串上面操作,没有返回值

    • replace该方法可适用于 String&str 类型

      该方法是返回一个新的字符串,而不是操作原来的字符串

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
// strings3.rs
//
// Execute `rustlings hint strings3` or use the `hint` watch subcommand for a
// hint.

// I AM NOT DONE

fn trim_me(input: &str) -> String {
// TODO: Remove whitespace from both ends of a string!
input.trim().to_string()
}

fn compose_me(input: &str) -> String {
// TODO: Add " world!" to the string! There's multiple ways to do this!
let mut ret: String = input.to_string();
ret.push_str(" world!");
// push_str没有返回值,因此单独返回ret
ret
}

fn replace_me(input: &str) -> String {
// TODO: Replace "cars" in the string with "balloons"!
let mut ret: String = input.to_string();
// replace()直接返回新的字符串
ret.replace("cars","balloons")
}

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

#[test]
fn trim_a_string() {
assert_eq!(trim_me("Hello! "), "Hello!");
assert_eq!(trim_me(" What's up!"), "What's up!");
assert_eq!(trim_me(" Hola! "), "Hola!");
}

#[test]
fn compose_a_string() {
assert_eq!(compose_me("Hello"), "Hello world!");
assert_eq!(compose_me("Goodbye"), "Goodbye world!");
}

#[test]
fn replace_a_string() {
assert_eq!(replace_me("I think cars are cool"), "I think balloons are cool");
assert_eq!(replace_me("I love to look at cars"), "I love to look at balloons");
}
}

hashmaps2.rs

问题1:

关于hashmap的更新实例中:为什么修改count的值就可以修改hashmap的键值呢?

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

let text = "hello world wonderful world";

let mut map = HashMap::new();
// 根据空格来切分字符串(英文单词都是通过空格切分)
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}

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

解答:

map.entry(word) 返回了一个 Entry 枚举类型的值,该枚举有两个变体:OccupiedVacantEntry 枚举表示 HashMap 中某个键对应的条目。

当调用 or_insert 方法时,如果 word 对应的条目已经存在,则 or_insert 方法会返回该条目的值的可变引用(Occupied 变体),如果该条目不存在,则会在 map 中插入一个新的键值对,然后返回新插入的值的可变引用(Vacant 变体)

问题2:

这道题里面有这样一个语句:

1
2
**let mut basket = get_fruit_basket();
assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4);**

其中,basket是这样来的:

1
2
3
4
5
6
7
8
fn get_fruit_basket() -> HashMap<Fruit, u32> {
let mut basket = HashMap::<Fruit, u32>::new();
basket.insert(Fruit::Apple, 4);
basket.insert(Fruit::Mango, 2);
basket.insert(Fruit::Lychee, 5);

basket
}

所以,为什么basket已经是一个hashmap了,还要用*解引用呢?

解答:

*解引用解的不是basket,而是basket.get(&Fruit::Apple),因为get会返回一个指向该条目键值的引用

跟这段代码一个道理:

1
2
3
4
5
let mut scores = HashMap::new();

// 查询Yellow对应的值,若不存在则插入新值
let v = scores.entry("Yellow").or_insert(5);
assert_eq!(*v, 5); // 不存在,插入5

or_insert()会返回一个指向该条目键值的引用,因此也需要把它解引用来跟5比较

quiz2.rs

这道题主要注意Append这个命令:迭代器是向量中每个元素的引用(想想这是肯定的,不然在for循环里面所有权都丢失了的话有点太危险),而这种引用默认就是不可变引用,那么使用String类型的push_str()自然就是要报错的,因为这个方法是改变原字符串而不是返回一个新的字符串。

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
// quiz2.rs
//
// This is a quiz for the following sections:
// - Strings
// - Vecs
// - Move semantics
// - Modules
// - Enums
//
// Let's build a little machine in the form of a function. As input, we're going
// to give a list of strings and commands. These commands determine what action
// is going to be applied to the string. It can either be:
// - Uppercase the string
// - Trim the string
// - Append "bar" to the string a specified amount of times
// The exact form of this will be:
// - The input is going to be a Vector of a 2-length tuple,
// the first element is the string, the second one is the command.
// - The output element is going to be a Vector of strings.
//
// No hints this time!

pub enum Command {
Uppercase,
Trim,
Append(usize),
}

pub mod my_module {
use super::Command;

// TODO: Complete the function signature!
pub fn transformer(input: Vec<(String, Command)>) -> Vec<String> {
// TODO: Complete the output declaration!
let mut output: Vec<String> = vec![];
for (string, command) in input.iter() {
// TODO: Complete the function body. You can do it!
match command {
Command::Uppercase => {
output.push(string.to_uppercase());
},
Command::Trim => {
output.push(string.trim().to_string());
},
Command::Append(size) => {
let mut appened_str = string.clone();
appened_str.push_str(&"bar".repeat(*size));
output.push(appened_str);
}
}
}
output
}
}

#[cfg(test)]
mod tests {
// TODO: What do we need to import to have `transformer` in scope?
use super::my_module::transformer;
use super::Command;

#[test]
fn it_works() {
let output = transformer(vec![
("hello".into(), Command::Uppercase),
(" all roads lead to rome! ".into(), Command::Trim),
("foo".into(), Command::Append(1)),
("bar".into(), Command::Append(5)),
]);
assert_eq!(output[0], "HELLO");
assert_eq!(output[1], "all roads lead to rome!");
assert_eq!(output[2], "foobar");
assert_eq!(output[3], "barbarbarbarbarbar");
}
}

options2.rs

主要是if let语句和while let语句的使用

首先,Option实际上是一种枚举类型,包含两个枚举成员:

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

所以下面代码中的if let Some(word) = optional_target {}实际上就是将optional_target这个枚举类型的值结构到word上,然后进行一个操作

while let语句也是一个道理,由于optional_integers的元素都是Some类型的变量,因此首先要把optional_integers的值解构到Some(integer)上,然后再进行操作,因此出现一个Some包裹一个Some

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
// options2.rs
//
// Execute `rustlings hint options2` or use the `hint` watch subcommand for a
// hint.

#[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" type
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 + 1) {
optional_integers.push(Some(i));
}

let mut cursor = range;

// TODO: make this a while let statement - remember that vector.pop also
// adds another layer of Option<T>. You can stack `Option<T>`s into
// while let and if let.
while let Some(Some(integer)) = optional_integers.pop() {
assert_eq!(integer, cursor);
cursor -= 1;
}

assert_eq!(cursor, 0);
}
}

关于if let的详细讲解

if 和 if let 表达式 - Rust 参考手册 中文版

options3.rs

ref和&:

1
2
3
4
5
let c = 'Q';

// 赋值语句中左边的 `ref` 关键字等价于右边的 `&` 符号。
let ref ref_c1 = c;
let ref_c2 = &c;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// options3.rs
//
// Execute `rustlings hint options3` or use the `hint` watch subcommand for a
// hint.

// I AM NOT DONE

struct Point {
x: i32,
y: i32,
}

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

match y {
Some(ref p) => println!("Co-ordinates are {},{} ", p.x, p.y),
_ => panic!("no match!"),
}
y; // Fix without deleting this line.
}

errors2.rs

关于errors:

  • 关于parse:

    parse返回的是一个Result枚举类型,成功的话将会是Result::Ok(),失败是话是Result::Err,因此想获取parse成功时返回的正确值,需要将返回值unwrap()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    fn main() {
    let astr = "7";

    let n = astr.parse::<i32>().unwrap();
    println!("{:?}", n);

    let n = astr.parse::<i64>().unwrap();
    println!("{:?}", n);

    let n = astr.parse::<u32>().unwrap();
    println!("{:?}", n);

    let n = astr.parse::<u64>().unwrap();
    println!("{:?}", n);

    let astr = "7.42";
    let n: f32 = astr.parse().unwrap();
    println!("{:?}", n);

    let n: f64 = astr.parse().unwrap();
    println!("{:?}", n);
    }
  • 认识到Result这个枚举类型:[Result](https://rustwiki.org/zh-CN/std/result/enum.Result.html)[Option](https://rustwiki.org/zh-CN/std/option/enum.Option.html) 类型的更丰富的版本,描述的是可能的错误而不是可能的不存在

    结果 Result - 通过例子学 Rust 中文版

而做题的时候这个例子,返回的错误类型是std::num::ParseIntError

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
// errors2.rs
//
// Say we're writing a game where you can buy items with tokens. All items cost
// 5 tokens, and whenever you purchase items there is a processing fee of 1
// token. A player of the game will type in how many items they want to buy, and
// the `total_cost` function will calculate the total cost of the tokens. Since
// the player typed in the quantity, though, we get it as a string-- and they
// might have typed anything, not just numbers!
//
// Right now, this function isn't handling the error case at all (and isn't
// handling the success case properly either). What we want to do is: if we call
// the `parse` function on a string that is not a number, that function will
// return a `ParseIntError`, and in that case, we want to immediately return
// that error from our function and not try to multiply and add.
//
// There are at least two ways to implement this that are both correct-- but one
// is a lot shorter!
//
// Execute `rustlings hint errors2` or use the `hint` watch subcommand for a
// hint.

// I AM NOT DONE

use std::num::ParseIntError;

pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
let processing_fee = 1;
let cost_per_item = 5;
let qty = item_quantity.parse::<i32>();
// 如果parse()转换成功,qty将会是一个Result::Ok(qty)
// 如果parse()转换失败,qty将会是一个Result::Err(ParseIntError)
match qty {
Ok(qty) => Ok(qty * cost_per_item + processing_fee),
Err(ParseIntError) => Err(ParseIntError)
}
}

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

#[test]
fn item_quantity_is_a_valid_number() {
assert_eq!(total_cost("34"), Ok(171));
}

#[test]
fn item_quantity_is_an_invalid_number() {
assert_eq!(
total_cost("beep boop").unwrap_err().to_string(),
"invalid digit found in string"
);
}
}

还可以用?:

1
2
3
4
5
6
7
8
9
10
11
12
13
pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
let processing_fee = 1;
let cost_per_item = 5;
// 如果成功就把Ok里面的值给qty,错误就直接返回错误
let qty = item_quantity.parse::<i32>()?;
// match qty {
// Ok(qty) => Ok(qty * cost_per_item + processing_fee),
// Err(ParseIntError) => Err(ParseIntError)
// }

// 成功就返回费用
Ok(qty * cost_per_item + processing_fee)
}

errors3.rs

在函数里面使用?:

因为?的逻辑是这样的:

如果返回的不是Err,就正常运行并且将Result枚举类型的值结构给cost

但如果返回的是Err,函数就会立刻终止,然后返回一个Result::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
32
33
// errors3.rs
//
// This is a program that is trying to use a completed version of the
// `total_cost` function from the previous exercise. It's not working though!
// Why not? What should we do to fix it?
//
// Execute `rustlings hint errors3` or use the `hint` watch subcommand for a
// hint.

use std::num::ParseIntError;

fn main() -> Result<(), ParseIntError> {
let mut tokens = 100;
let pretend_user_input = "8";

let cost = total_cost(pretend_user_input)?;

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

pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
let processing_fee = 1;
let cost_per_item = 5;
let qty = item_quantity.parse::<i32>()?;

Ok(qty * cost_per_item + processing_fee)
}

但是在main函数里面返回一个Result::Ok(String::from("implement success!")) 是不合法的,因为main函数通常有两种有效的返回类型:()(表示成功)和Result<(), E>(表示成功或出现错误)。main函数的返回类型必须实现Termination trait,该trait规定了程序的终止行为

errors4.rs

可以像这样用匹配:

1
2
3
4
5
6
7
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
match value {
x if x < 0 => Err(CreationError::Negative),
x if x == 0 => Err(CreationError::Zero),
x => Ok(PositiveNonzeroInteger(x as u64)),
}
}

但感觉这里也可以直接if判断:

1
2
3
4
5
6
7
if value > 0 {
Ok(PositiveNonzeroInteger(value as u64))
} else if value < 0 {
Err(CreationError::Negative)
} else {
Err(CreationError::Zero)
}

errors6.rs

转换错误类型:将标准库定义的错误类型转换为自定义的ParsePosNonzeroError

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
95
96
97
98
99
100
// errors6.rs
//
// Using catch-all error types like `Box<dyn error::Error>` isn't recommended
// for library code, where callers might want to make decisions based on the
// error content, instead of printing it out or propagating it further. Here, we
// define a custom error type to make it possible for callers to decide what to
// do next when our function returns an error.
//
// Execute `rustlings hint errors6` or use the `hint` watch subcommand for a
// hint.

use std::num::ParseIntError;

// This is a custom error type that we will be using in `parse_pos_nonzero()`.
#[derive(PartialEq, Debug)]
enum ParsePosNonzeroError {
Creation(CreationError),
ParseInt(ParseIntError),
}

impl ParsePosNonzeroError {
// 将错误类型转换为ParsePosNonzeroError的Creation类型
fn from_creation(err: CreationError) -> ParsePosNonzeroError {
ParsePosNonzeroError::Creation(err)
}
// TODO: add another error conversion function here.
// 将错误类型转换为ParsePosNonzeroError的ParseInt类型
fn from_parseint(err: ParseIntError) -> ParsePosNonzeroError {
ParsePosNonzeroError::ParseInt(err)
}
}

fn parse_pos_nonzero(s: &str) -> Result<PositiveNonzeroInteger, ParsePosNonzeroError> {
// TODO: change this to return an appropriate error instead of panicking
// when `parse()` returns an error.

// 处理parse()出错的情况
let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;

// 处理创建时出错的情况
PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation)
}

// Don't change anything below this line.

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

#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
}

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

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

#[test]
fn test_parse_error() {
// We can't construct a ParseIntError, so we have to pattern match.
assert!(matches!(
parse_pos_nonzero("not a number"),
Err(ParsePosNonzeroError::ParseInt(_))
));
}

#[test]
fn test_negative() {
assert_eq!(
parse_pos_nonzero("-555"),
Err(ParsePosNonzeroError::Creation(CreationError::Negative))
);
}

#[test]
fn test_zero() {
assert_eq!(
parse_pos_nonzero("0"),
Err(ParsePosNonzeroError::Creation(CreationError::Zero))
);
}

#[test]
fn test_positive() {
let x = PositiveNonzeroInteger::new(42);
assert!(x.is_ok());
assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap()));
}
}

关于map_err:

  • Maps a Result<T, E> to Result<T, F> by applying a function to a contained Err value, leaving an Ok value untouched.
  • 也就是不触发错误返回,而是等待下一步对这个错误的处理

代码:

1
2
3
4
5
// 处理parse()出错的情况
let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;

// 处理创建时出错的情况
PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation)
  • 首先s.parse()尝试将s进行转换:
    • 如果出错的话,map_err()就会返回ParsePosNonzeroError::from_parseint 这个自定义的错误类型然后直接返回,而不是ParseIntError
    • 如果没出错,map_err() 就会返回一个Result::Ok(value)作为s转换后的值——因此后面一定要加?,不然出错的情况确实可以正常返回,但是没出错时Result类型的值是无法与i64类型的x绑定的,而?可以将正常返回的值直接unwrap
  • 然后x被成功绑定一个i64类型的整数,就需要判断x是否满足非负数,因此创建一个PositiveNonzeroInteger 类型的结构体,x的值作为结构体的值,然后调用结构体方法new()
    • 如果new成功返回Ok(PositiveNonzeroInteger(x as u64)),那么map_err()就返回这个Result::Ok类型的值
    • 如果new出错了返回了一个Result::Err类型的值,那么map_err()就返回ParsePosNonzeroError::from_creation 这个自定义的错误类型然后直接返回,而不是CreationError

generics2.rs

使用泛型

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
// generics2.rs
//
// This powerful wrapper provides the ability to store a positive integer value.
// Rewrite it using generics so that it supports wrapping ANY type.
//
// Execute `rustlings hint generics2` or use the `hint` watch subcommand for a
// hint.

pub struct Wrapper<T> {
value: T,
}

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

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

#[test]
fn store_u32_in_wrapper() {
assert_eq!(Wrapper::<i32>::new(42).value, 42);
}

#[test]
fn store_str_in_wrapper() {
assert_eq!(Wrapper::<&str>::new("Foo").value, "Foo");
}
}

traits5.rs

特征的多重约束

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
// traits5.rs
//
// Your task is to replace the '??' sections so the code compiles.
//
// Don't change any line other than the marked one.
//
// Execute `rustlings hint traits5` or use the `hint` watch subcommand for a
// hint.

pub trait SomeTrait {
fn some_function(&self) -> bool {
true
}
}

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

struct SomeStruct {}
struct OtherStruct {}

impl SomeTrait for SomeStruct {}
impl OtherTrait for SomeStruct {}
impl SomeTrait for OtherStruct {}
impl OtherTrait for OtherStruct {}

// YOU MAY ONLY CHANGE THE NEXT LINE
fn some_func(item: (impl SomeTrait + OtherTrait)) -> bool {
item.some_function() && item.other_function()
}

fn main() {
some_func(SomeStruct {});
some_func(OtherStruct {});
}

quiz3.rs

关于格式化字符串format!:并不是所有的泛型都可以用这个函数,必须要有Display特征的泛型才可以,因此使用T: Display这个特征约束

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

像这段代码的意思就是:约束T必须具有特征Summary,且item1和item2都是T类型的引用

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
// quiz3.rs
//
// This quiz tests:
// - Generics
// - Traits
//
// An imaginary magical school has a new report card generation system written
// in Rust! Currently the system only supports creating report cards where the
// student's grade is represented numerically (e.g. 1.0 -> 5.5). However, the
// school also issues alphabetical grades (A+ -> F-) and needs to be able to
// print both types of report card!
//
// Make the necessary code changes in the struct ReportCard and the impl block
// to support alphabetical report cards. Change the Grade in the second test to
// "A+" to show that your changes allow alphabetical grades.
//
// Execute `rustlings hint quiz3` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

use std::fmt::Display;

// 因为成绩既有字符串又有浮点数,因此定义grade为一个泛型
pub struct ReportCard<T> {
pub grade: T,
pub student_name: String,
pub student_age: u8,
}
// 这里规定T必须是具有Display特征的类型
impl<T: Display> ReportCard<T> {
pub fn print(&self) -> String {
format!("{} ({}) - achieved a grade of {}",
&(*self).student_name, &self.student_age, &self.grade)
// 这里访问self字段时隐式解引用,但你要手动解引用也可以
}
}

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

#[test]
fn generate_numeric_report_card() {
let report_card = ReportCard {
grade: 2.1,
student_name: "Tom Wriggle".to_string(),
student_age: 12,
};
assert_eq!(
report_card.print(),
"Tom Wriggle (12) - achieved a grade of 2.1"
);
}

#[test]
fn generate_alphabetic_report_card() {
// TODO: Make sure to change the grade here after you finish the exercise.
let report_card = ReportCard {
grade: String::from("A+"),
student_name: "Gary Plotter".to_string(),
student_age: 11,
};
assert_eq!(
report_card.print(),
"Gary Plotter (11) - achieved a grade of A+"
);
}
}

lifetimes3.rs

关于结构体的生命周期:

不仅仅函数具有生命周期,结构体其实也有这个概念,只不过我们之前对结构体的使用都停留在非引用类型字段上。细心的同学应该能回想起来,之前为什么不在结构体中使用字符串字面量或者字符串切片,而是统一使用 String 类型?原因很简单,后者在结构体初始化时,只要转移所有权即可,而前者,抱歉,它们是引用,它们不能为所欲为。

既然之前已经理解了生命周期,那么意味着在结构体中使用引用也变得可能:只要为结构体中的每一个引用标注上生命周期即可:

1
2
3
4
5
6
7
8
9
10
11
struct ImportantExcerpt<'a> {
part: &'a str,
}

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

结构体 ImportantExcerpt 所引用的字符串 str 生命周期需要大于等于该结构体的生命周期

iterators2.rs

迭代器:

  • 迭代器是会被消耗的:

    当使用了next(),迭代器就会被消耗,相当于取出了那一个元素,那个元素其实已经不在迭代器里面了,因此最开始我直接将first转换为大写后直接返回了迭代器剩余的元素,这会导致返回的字符串没有预期的第一个字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // Step 1.
    // Complete the `capitalize_first` function.
    // "hello" -> "Hello"
    pub fn capitalize_first(input: &str) -> String {
    let mut c = input.chars();
    match c.next() {
    None => String::new(),
    Some(first) => {
    let mut first_upp: String = first.to_uppercase().collect();
    let chars_str: String = c.collect();
    first_upp.push_str(&chars_str);
    first_upp
    }
    }
    }

iterators4.rs

使用迭代器处理元素序列 - Rust 程序设计语言 中文版

用迭代器实现递归:使用消费者适配器的方法

1
2
3
4
5
6
7
8
9
10
11
12
pub fn factorial(num: u64) -> u64 {
// Complete this function to return the factorial of num
// Do not use:
// - return
// Try not to use:
// - imperative style loops (for, while)
// - additional variables
// For an extra challenge, don't use:
// - recursion
// Execute `rustlings hint iterators4` for hints.
(1..=num).product()
}

在 Rust 中,(1..=num) 是一个范围(Range)表达式,表示从1到num(包括num本身)的一个范围。这个范围实际上是一个迭代器,它可以生成从1到num的所有数字序列。

在这种情况下,我们可以通过 (1..=num) 创建一个包含从1到num的数字序列的迭代器。然后,我们调用迭代器的 product() 方法,这个方法将迭代器中的所有元素相乘,得到它们的乘积作为结果。

因此,(1..=num).product() 这个语句的意思是:先生成从1到num的数字序列迭代器,然后计算这个序列中所有数字的乘积,最终得到阶乘的结果。

iterators5.rs

关于闭包和迭代器方法的使用

不用循环,找出map里面有多少个键值为value的元素

1
2
3
4
5
fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize {
// map is a hashmap with String keys and Progress values.
// map = { "variables1": Complete, "from_str": None, ... }
map.values().filter(|progress| **progress == value).count()
}
  • .values():使用了HashMap的value方法,获取了一个包含所有键值的迭代器。

    pub fn [values](https://rustwiki.org/zh-CN/std/collections/struct.HashMap.html#method.values)(&self) -> [Values](https://rustwiki.org/zh-CN/std/collections/hash_map/struct.Values.html)<'_, K, V>一个以任意顺序访问所有值的迭代器。 迭代器元素类型为 &'a V ,即对键值的引用

  • .filter()创建一个迭代器,该迭代器使用闭包确定是否应产生元素。给定一个元素,闭包必须返回 truefalse。返回的迭代器将仅生成闭包为其返回 true 的元素。

  • .count() :消耗迭代器,计算迭代次数并返回它。此方法将反复调用 [next](https://rustwiki.org/zh-CN/std/iter/trait.Iterator.html#tymethod.next),直到遇到 [None](https://rustwiki.org/zh-CN/std/option/enum.Option.html#variant.None),并返回它看到 [Some](https://rustwiki.org/zh-CN/std/option/enum.Option.html#variant.Some) 的次数。 请注意,即使迭代器没有任何元素,也必须至少调用一次 [next](https://rustwiki.org/zh-CN/std/iter/trait.Iterator.html#tymethod.next)

返回一个所有哈希表里面键值为指定状态的数量和:

1
2
3
4
5
6
fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize {
// collection is a slice of hashmaps.
// collection = [{ "variables1": Complete, "from_str": None, ... },
// { "variables2": Complete, ... }, ... ]
collection.iter().map(|map| count_iterator(map, value)).sum()
}
  • .map() :Iterator特征的函数,获取一个闭包并创建一个迭代器,该迭代器在每个元素上调用该闭包。
  • 首先创建collection的迭代器,再使用map使得迭代器上的每个元素都调用count_iterator(),对于每个哈希表都计算键值为progress的个数
  • .sum() :将迭代器里面的每个元素相加,所以这么写也是对的:
1
2
let iter = collection.iter().map(|map| count_iterator(map, value));
iter.sum()

box1.rs——智能指针

Box堆对象分配 - Rust语言圣经(Rust Course)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用Box来定义Cons,将List存到堆上,那么List的大小就固定了
pub enum List {
Cons(i32, Box<List>),
Nil,
}

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

pub fn create_non_empty_list() -> List {
List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))))
}

threads1.rs

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
fn main() {
// 创建handles,用以存储后面10个线程的句柄
let mut handles = vec![];

// 通过循环创建了10个线程,每个线程会执行一个闭包
for i in 0..10 {
// 使用move拿走当前i的所有权
handles.push(thread::spawn(move || {
let start = Instant::now();
thread::sleep(Duration::from_millis(250));

// 250ms后输出,因为i的所有权被拿到了,因此可以访问i
println!("thread {} is complete", i);
start.elapsed().as_millis()
}));
}

// 创建results,用以存储10个线程的执行时间
let mut results: Vec<u128> = vec![];
for handle in handles {
// TODO: a struct is returned from thread::spawn, can you use it?
// 等待每个线程执行完成再结束主线程
results.push(handle.join().unwrap());
}

// 输出时间
for (i, result) in results.into_iter().enumerate() {
println!("thread {} took {}ms", i, result);
}
}
  • 线程返回值是如何得到的?

    handle.join().unwrap() 中,join() 方法会等待线程执行完成并获取线程的返回值,即每个线程的执行时间(以毫秒为单位),然后通过 unwrap() 方法将其取出并存储在 results 向量中。

    Untitled

  • 线程编号和时间是如何输出的?

    results.into_iter().enumerate(): into_iter() 方法将 results 向量转换为一个拥有所有权的迭代器,enumerate() 方法对迭代器进行索引迭代,返回一个元组 (index, value),其中 index 表示元素在迭代器中的索引,value 表示元素的值。

threads3.rs——多个发送者

为什么这里需要克隆发送方?

  • 因为thread使用了闭包关键字move,这会使得在创建第一个线程时,tx的所有权已经被移到了第一个线程里面,第二个线程还想用tx发送信息自然是做不到的
  • 因此克隆一个tx,第二个线程就可以使用了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fn send_tx(q: Queue, tx: mpsc::Sender<u32>) -> () {
let qc = Arc::new(q);
let qc1 = Arc::clone(&qc);
let qc2 = Arc::clone(&qc);
// 克隆发送方
let tx_clone = tx.clone();

thread::spawn(move || {
for val in &qc1.first_half {
println!("sending {:?}", val);
tx.send(*val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});

thread::spawn(move || {
for val in &qc2.second_half {
println!("sending {:?}", val);
// 使用克隆的发送方
tx_clone.send(*val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
}

macros3.rs

  • 使用macro_rules!来定义宏
  • 宏的定义必须在调用之前
  • 通过在 my_macro 宏定义前加上 #[macro_export] 属性,使得宏可以在模块外部使用
1
2
3
4
5
6
7
8
9
10
11
12
pub mod macros {
#[macro_export]
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}

fn main() {
my_macro!();
}

macros4.rs

模式匹配:

  1. $() 中包含的是模式 $x:expr,该模式中的 expr 表示会匹配任何 Rust 表达式,并给予该模式一个名称 $x
  2. 因此 $x 模式可以跟整数 1 进行匹配,也可以跟字符串 “hello” 进行匹配: vec!["hello", "world"]
  3. $() 之后的逗号,意味着12 之间可以使用逗号进行分割,也意味着 3 既可以没有逗号,也可以有逗号:vec![1, 2, 3,]
  4. *说明之前的模式可以出现零次也可以任意次,这里出现了三次

匹配一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#[rustfmt::skip]
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
($val:expr) => {
println!("Look at this other macro: {}", $val);
};
}

fn main() {
my_macro!();
my_macro!(7777);
}

匹配多次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[rustfmt::skip]
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
($($val:expr),*) => {
$(
println!("Look at this other macro: {}", $val);
)*
};
}

fn main() {
my_macro!();
my_macro!(7777, 999, "hello");
}

tests5.rs——裸指针和unsafe

  • 在裸指针 *const T 中,这里的 * 只是类型名称的一部分,并没有解引用的含义

  • 下面的代码基于值的引用同时创建了可变和不可变的裸指针,创建裸指针是安全的行为,而解引用裸指针才是不安全的行为 :

    1
    2
    3
    4
    let mut num = 5;

    let r1 = &num as *const i32; // 不可变裸指针
    let r2 = &mut num as *mut i32; // 可变裸指针

algorithm1——合并链表

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
pub fn merge(list_a:LinkedList<T>,list_b:LinkedList<T>) -> Self
{
//TODO
let mut merged_list = LinkedList::new();
// 获取链表的开始节点
let mut node_a = list_a.start;
let mut node_b = list_b.start;

while node_a.is_some() || node_b.is_some() {
// 指针解引用,其中一个指针有可能为None
// 但是不获得val的所有权
let a_val = node_a.map(|ptr| unsafe{ &(*ptr.as_ptr()).val });
let b_val = node_b.map(|ptr| unsafe{ &(*ptr.as_ptr()).val });

// 比较大小
match (a_val, b_val) {
// 两个都非空
(Some(a), Some(b)) => {
if a < b {
merged_list.add(a.clone());
// 指针解引用且获得指针内容的所有权
node_a = unsafe{ (*node_a.unwrap().as_ptr()).next };
} else {
merged_list.add(b.clone());
node_b = unsafe{ (*node_b.unwrap().as_ptr()).next };
}
},
// a已经空了,直接把b剩下的元素全部加进链表
(None, Some(b)) => {
merged_list.add(b.clone());
node_b = unsafe{ (*node_b.unwrap().as_ptr()).next };
},
(Some(a), None) => {
merged_list.add(a.clone());
node_a = unsafe{ (*node_a.unwrap().as_ptr()).next };
},
(None, None) => {
break
}
}
}

merged_list
}

algorithm2——链表反转

  • 因为用到了clone,因此必须限定T是具有Clone特征的泛型

  • 方法一

    • 使用std::mem::replace(self, reversed_list); 交换新链表和self的所有权
  • 方法二

    • 直接将新链表的所有权交给self
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    impl<T: Clone> LinkedList<T> {
    pub fn reverse(&mut self){
    let mut reversed_list = LinkedList::new();
    // 获取链表的末尾节点
    let mut current_node = self.end;
    while current_node.is_some() {
    // 获取当前节点值的引用
    let value = current_node.map(|ptr| unsafe{ &(*ptr.as_ptr()).val });
    match value {
    Some(x) => {
    // 新链表加入x
    reversed_list.add(x.clone());
    // 指向上一个节点
    current_node = unsafe{ (*current_node.unwrap().as_ptr()).prev };
    }
    None => break
    }
    }
    // 交换新链表和原链表的所有权
    // std::mem::replace(self, reversed_list);
    *self = reversed_list;
    }
    }
  • 其中while循环那段可以这样改:

    1
    2
    3
    4
    5
    while let Some(node_ptr) = current_node {
    let value = unsafe { &(*node_ptr.as_ptr()).val }; // 因为node_ptr肯定是Some类型的,其值一定存在
    reversed_list.add(value.clone());
    current_node = unsafe { (*node_ptr.as_ptr()).prev };
    }

algorithm4——二叉查找树

  • 因为要递归实现插入和查找,所以应该将search和insert实现为TreeNode的方法,所以TreeNode方法的实现应该要写在最前面

  • 二叉树节点的方法:

    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
    // 此时self是二叉树里面的一个节点
    fn insert(&mut self, value: T) {
    match value.cmp(&self.value) {
    // 要插入的节点小于当前节点
    Ordering::Less => {
    // 应该把它往左边递归
    match &mut self.left {
    Some(left) => {
    // 如果左指针有值,就继续递归
    // 左指针是一个Box
    (*left).insert(value);
    }
    _ => {
    // 如果左指针指向None,就让它指向新建节点
    self.left = Some(Box::new(TreeNode::new(value)));
    }
    }
    }
    Ordering::Greater => {
    // 应该把它往右边递归
    match &mut self.right {
    Some(right) => {
    // 如果右指针有值,就继续递归
    // 右指针是一个Box
    (*right).insert(value);
    }
    _ => {
    // 如果右指针指向None,就让它指向新建节点
    self.right = Some(Box::new(TreeNode::new(value)));
    }
    }
    }
    _ => {} // 相等时不插入新节点
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    fn search(&self, value: T) -> bool {
    match value.cmp(&self.value) {
    Ordering::Less => {
    // 说明应该往当前节点的左边找
    match &self.left {
    Some(left) => { return left.search(value); }
    _ => { return false; }
    }
    }
    Ordering::Greater => {
    // 说明应该往当前节点的右边找
    match &self.right {
    Some(right) => { return right.search(value); }
    _ => { return false; }
    }
    }
    _ => { return true; }
    }
    }
  • 二叉树的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Insert a value into the BST
    fn insert(&mut self, value: T) {
    // 直接使用节点的插入方法
    match &mut self.root {
    Some(root_node) => {
    root_node.insert(value);
    }
    _ => {
    self.root = Some(Box::new(TreeNode::new(value)));
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    // Search for a value in the BST
    fn search(&self, value: T) -> bool {
    match &self.root {
    Some(root_node) => { root_node.search(value) }
    _ => { false }
    }
    }

    algorithm7——用Vec实现栈

    1
    2
    3
    4
    5
    6
    7
    // 坑:size记得-=1
    fn pop(&mut self) -> Option<T> {
    if self.size > 0 {
    self.size -= 1;
    }
    self.data.pop()
    }
    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
    fn bracket_match(bracket: &str) -> bool
    {
    let mut stack = Stack::new();
    // 遍历字符串里面的字符
    for chr in bracket.chars() {
    // 扫描到左括号就入栈
    if chr == '(' || chr == '{' || chr == '[' {
    stack.push(chr);
    }
    if chr == ')' || chr == '}' || chr == ']' {
    if stack.is_empty() {
    return false;
    }
    if let Some(stack_top) = stack.pop() {
    if chr == ')' {
    if stack_top != '(' { return false; }
    } else if chr == '}' {
    if stack_top != '{' { return false; }
    } else if chr == ']' {
    if stack_top != '[' { return false; }
    }
    }
    }
    }
    stack.is_empty()
    }

    algorithm8——两个队列实现栈

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 出队,出第一个元素
    pub fn dequeue(&mut self) -> Result<T, &str> {
    if !self.elements.is_empty() {
    Ok(self.elements.remove(0usize))
    } else {
    Err("Stack is empty") // 坑:记得把原来的“Queue”改成“Stack”
    }
    }

    // 看队头的元素的值
    pub fn peek(&self) -> Result<&T, &str> {
    match self.elements.first() {
    Some(value) => Ok(value),
    None => Err("Stack is empty"),
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    pub fn push(&mut self, elem: T) {
    self.q1.enqueue(elem); // 入队列q1
    }

    pub fn pop(&mut self) -> Result<T, &str> {
    // 将q1里面的除最后一个元素依次出栈存到q2
    for n in 1..self.q1.size() {
    if let Ok(value) = self.q1.dequeue() {
    self.q2.enqueue(value);
    }
    }
    std::mem::swap(&mut self.q1, &mut self.q2); // 交换q1、q2所有权
    self.q2.dequeue() // q2负责出队
    }

    pub fn is_empty(&self) -> bool {
    self.q1.is_empty() && self.q2.is_empty()
    }

    algorithm9——堆排序

    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
    impl<T> Heap<T>
    where
    T: Default + std::cmp::PartialOrd
    {
    pub fn new(comparator: fn(&T, &T) -> bool) -> Self {
    Self {
    count: 0,
    items: vec![T::default()], // 从下标为1的地方开始存堆
    comparator,
    }
    }

    pub fn len(&self) -> usize {
    self.count
    }

    pub fn is_empty(&self) -> bool {
    self.len() == 0
    }

    pub fn add(&mut self, value: T) {
    self.count += 1; // index从1开始
    self.items.push(value); // 先在堆的最后添加一个节点
    let mut index = self.count;

    while index > 1 {
    let parent_idx = self.parent_idx(index); // 获取最后一个元素的父元素
    if (self.comparator)(&self.items[index], &self.items[parent_idx]) {
    self.items.swap(index, parent_idx); // 将它和父元素交换
    index = parent_idx; // 发生交换后index移动到父元素的位置
    } else {
    break;
    }
    }
    }

    fn parent_idx(&self, idx: usize) -> usize {
    idx / 2
    }

    // 判断idx是否有孩子
    fn children_present(&self, idx: usize) -> bool {
    self.left_child_idx(idx) <= self.count
    }

    fn left_child_idx(&self, idx: usize) -> usize {
    idx * 2
    }

    fn right_child_idx(&self, idx: usize) -> usize {
    self.left_child_idx(idx) + 1
    }

    // 按堆规定的顺序返回两个孩子中应该在前面的一个的索引值
    fn smallest_child_idx(&self, idx: usize) -> usize {
    let left = self.left_child_idx(idx);
    let right = self.right_child_idx(idx);
    if right > self.count {
    left
    } else if (self.comparator)(&self.items[left], &self.items[right]) {
    left
    } else {
    right
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    fn next(&mut self) -> Option<T> {
    if self.count == 0 {
    return None;
    }
    // 相当于堆排序
    let ret = self.items.swap_remove(1); // 将堆顶元素输出,堆底元素升上来
    self.count -= 1;
    // 再次调整堆为大根堆或小根堆
    let mut index = 1;
    // 当当前元素有子元素时
    while self.children_present(index) {
    let child_index = self.smallest_child_idx(index);
    if (self.comparator)(&self.items[child_index], &self.items[index]) {
    self.items.swap(child_index, index);
    index = child_index;
    }
    else {
    break; // 如果不break会进入死循环
    }
    }

    Some(ret)
    }

    algorithm10——图

    用到了get_mut()

    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
    // 无向图结构的邻接表,用HashMap来存储,键是String,键值是一个向量,其中i32可能表示边长
    pub struct UndirectedGraph {
    adjacency_table: HashMap<String, Vec<(String, i32)>>,
    }
    impl Graph for UndirectedGraph {
    fn new() -> UndirectedGraph {
    UndirectedGraph {
    adjacency_table: HashMap::new(),
    }
    }
    // 获取邻接表的可变引用
    fn adjacency_table_mutable(&mut self) -> &mut HashMap<String, Vec<(String, i32)>> {
    &mut self.adjacency_table
    }
    // 获取邻接表的引用
    fn adjacency_table(&self) -> &HashMap<String, Vec<(String, i32)>> {
    &self.adjacency_table
    }
    // 添加边
    fn add_edge(&mut self, edge: (&str, &str, i32)) {
    // 要在三元组的第一个str和第二个str之间加边
    let (vertice1, vertice2, weight) = edge;

    // 如果第一个点查询得到
    if let Some(adj1) = self.adjacency_table_mutable().get_mut(&String::from(vertice1)) {
    adj1.push((String::from(vertice2), weight));
    } else {
    self.add_node(vertice1);
    if let Some(new_adj1) = self.adjacency_table_mutable().get_mut(vertice1) {
    new_adj1.push((String::from(vertice2), weight));
    }
    }

    // 如果第二个点查询得到
    if let Some(adj2) = self.adjacency_table_mutable().get_mut(vertice2) {
    adj2.push((String::from(vertice1), weight));
    } else {
    self.add_node(vertice2);
    if let Some(new_adj2) = self.adjacency_table_mutable().get_mut(&String::from(vertice2)) {
    new_adj2.push((String::from(vertice1), weight));
    }
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 添加成功返回true,添加失败返回false
    fn add_node(&mut self, node: &str) -> bool {
    if self.contains(node) {
    return false;
    }
    else {
    self.adjacency_table_mutable().insert(node.to_string(), Vec::new());
    return true;
    }
    }
    // 在结构体方法中实现了
    fn add_edge(&mut self, edge: (&str, &str, i32));

第一阶段 - Rust学习

前言小记

唠嗑一番:在此次开源操作系统集训营之前,我实际上已经参加过若干次了。最早的时候,是我对象在2022年看到了集训营的开营通知,并在GitHub的issue上回复来报名。然而,由于我之前没有留意到相关信息,错过了过去好几期的机会。这一次也算做是自己重来的机会吧。不过说起来,这个rustlings的确是我从去年一直做到了现在。今年直接新增10道数据结构题, 本fw直接没法也就上了亿点点unsafe, 好在之前有经验,于是三天冲刺完成,就有了本文之第一阶段总结。

Rust何物者也?一个令人安如磐石的C++ Promax (钟离老爷子看后点个赞) ,胜任WebAssembly、普通命令行应用乃至操作系统编写。语法漂亮,match与macro非常赞。具有成体系的包管理器,安装导入新模块轻松如Python,rust-analyzer也很赞,提供比Pylance更好的高亮提示。运行效率也可以,看似充满了函数,其实llvm优化的也挺不错(

算啦,后面是正经时间,将展示本人写的代码示例和正经点的Rust学习感想,再次感谢清华大学开源操作系统集训营!

示例代码

Bellman-Ford

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
struct Edge {
source: usize,
destination: usize,
weight: i32,
}

fn bellman_ford(edges: &Vec<Edge>, num_vertices: usize, source: usize) -> Vec<i32> {
let mut distances = vec![i32::MAX; num_vertices];
distances[source] = 0;

for _ in 0..num_vertices - 1 {
for edge in edges {
if distances[edge.source] + edge.weight < distances[edge.destination] {
distances[edge.destination] = distances[edge.source] + edge.weight;
}
}
}

for edge in edges {
if distances[edge.source] + edge.weight < distances[edge.destination] {
println!("图中包含负权重环");
break;
}
}

distances
}

fn main() {
let num_vertices = 5;
let edges = vec![
Edge { source: 0, destination: 1, weight: -1 },
Edge { source: 0, destination: 2, weight: 4 },
Edge { source: 1, destination: 2, weight: 3 },
Edge { source: 1, destination: 3, weight: 2 },
Edge { source: 1, destination: 4, weight: 2 },
Edge { source: 3, destination: 2, weight: 5 },
Edge { source: 3, destination: 1, weight: 1 },
Edge { source: 4, destination: 3, weight: -3 },
];
let source = 0;

let distances = bellman_ford(&edges, num_vertices, source);

println!("顶点到源顶点的距离");
for (i, &dist) in distances.iter().enumerate() {
println!("{} \t\t {}", i, dist);
}
}

学习感想

有了Rust这样一门强大而复杂的编程语言,我的学习之旅也就充满了挑战和收获。在学习Rust的过程中,我深刻体会到了其独特的设计理念和功能特性,这些特性使得Rust在系统编程领域有着独特的优势和价值。

首先,Rust的内存管理机制让我印象深刻。通过所有权(ownership)、借用(borrowing)和生命周期(lifetime)等概念,Rust实现了内存安全和线程安全,避免了常见的内存错误和数据竞争问题。虽然这些概念在开始时让我感到有些困惑,但通过不断的实践和阅读文档,我逐渐掌握了它们,并开始感受到它们带来的好处。与其他语言相比,Rust的内存管理机制让我感到更加自信,因为我知道我的程序不会因为内存错误而崩溃。

其次,Rust的模式匹配(pattern matching)和错误处理机制让我眼前一亮。模式匹配不仅可以用于解构数据结构,还可以用于控制程序的流程,使得代码更加清晰和易于理解。而错误处理方面,Rust引入了Result和Option等枚举类型,强制程序员处理可能出现的错误情况,避免了传统的异常机制带来的一些问题。学习如何利用这些特性编写健壮的代码是我学习Rust过程中的一大收获。

此外,Rust的并发编程支持也给我留下了深刻的印象。通过标准库提供的基于消息传递的并发模型,以及原子类型和同步原语等工具,Rust让并发编程变得更加安全和容易。我曾经尝试用Rust编写多线程程序,并发现在Rust的帮助下,我可以更加轻松地管理线程之间的通信和同步,避免了常见的并发错误。

2024开源操作系统训练营第一阶段总结-HCHogan

半年前开始就一直在写rust语言,写了很多练手的项目,所以第一阶段的收获有限,比较印象比较深刻的就是结构体初始化的那个语法:

1
2
3
4
User {
email: String::from("another@example.com"),
..user1
};

没有用过,这次正好复习到了。

一阶段

前言

进入这个训练营很意外,因为是在同学建的就业群里面看到的连接,等进来之后从知道是训练营,本着既来之则安之的心态,我满怀期待的开启了第一阶段的rust学习 结果差点被所有权和引用绕晕 ,所有权和引用的学习真是让我又爱又恨,真是应了群友们的那句话:“编译之前,c++是世界上最好的语言!编译之后,rust是世界上最好的语言!”。

有几天我很想尝试用rust写leetcode的算法题,结果别说写了,我用模仿c++的代码都费劲,那几天rust真是给了我重重一击,但通过对rust的不断运用和学习,我也逐渐开始理解了为什么rust的安全性会如此之高 虽然换来的是编译期间的无数BUG ,但这并不妨碍它仍是一门值得学习和认真揣摩的一种语言。

Rust 学习总结

这三个星期主要集中于对于rust基础语法的学习,以及rustlings的110题,书籍方面,只是拜读了一下《RUST圣经》。好像这110题写过来,就没有几道题是改完一次就不报错直接通过的(不过看到群友们也是如此,我便感觉好受了很多)。
在rustlings的110题中,所有权、引用和生命周期的学习与运用几乎贯彻了全部的代码(要问我为什么是几乎,因为生命周期的题开始的晚哈哈哈哈),其中最麻烦的莫过于test的最后几题,虽然我知道群友们说的对,第一次见新东西难免会很懵,但是那几题直接硬控我两天。。。,相较之下,其它的一些题无非是对知识点的巩固和对算法的实现,对我而言反而没有很大的压力,按部就班来,一切都刚刚好。

二阶段(Rcore)

Rcore 学习总结

学了 Rcore 三个星期,把文档从前到后过了一遍,令我印象最深的还是前两章的内容,移除标准库依赖、构建裸机执行环境、对内存进行布局、将应用程序链接到内核以及进行特权级转换时对Trap上下文的数据保存与恢复,通过 __alltraps__restore 初始化应用程序以及执行吓一跳应用程序的过程都令我备感兴趣。

在实现 ch3 - ch8 时,最大的难点还是对代码含义的理解以及能否按需找到相关属性的存放位置,例如在 ch4 中虚拟地址到物理地址的转换方法,ch5 中通过 forkexec 的具体实现来推测 spawn 的实现, ch6中对 sys_stat 的实现必须借助 vfs 的相关属性去进行计算, ch8中在线程创建,线程移除,上锁,解锁时都需要对自定义的检测死锁结构体 ProcessLock 进行相应改变。

通过本次学习,我对 Rcore 的实现原理有了更深刻的了解,同时也认知到自己对计算机底层的相关知识掌握的并不牢固,大有几分囫囵吞枣之态,因此,在后续的学习中,我打算对计算机底层的相关知识进行系统的学习,例如汇编语言、操作系统、计算机组成原理等,以便能够更好的理解 Rcore 的实现原理。

总结

Day1

搭建rust练习的运行环境

Day2

let声明变量(不可改变) mut让变量可改变 例:let x=5;let mut x=5;
1.基本数据类型 i32…u8,16,32…usize…char,bool
2.复合类型 数组:[1,2,3] 元组(1,”s”,true,”double”)
loop循环,变量作用域,返回值
引用和借用,slice,自定义结构体和元组结构体,vector,hashmap

第二天完成的都是基础

Day3
enum创建实例和初始化
option枚举,只有存在some()和不存在none
match控制流,类似其他语言的switch进行匹配, _ => 是math的通用符匹配
模块的声明和创建
泛型 T trait方法

生命周期(我觉得比较难的)
&i32 //引用
&’a i32 &’a mut i32 //带有显式生命周期的引用
通过使用&’a符号,声明声明周期。在函数传值的过程中,形参和实参生命周期要一样

BOX将一个值放到堆上
并发编程:(第二个觉得比较有意思的)
使用spawn创建新线程 let handle = thread::spawn(||{…….})
使用join等待所有线程结束 handle.join().unwrap()
将move闭包和线程一起使用 et handle = thread::spawn(move||{…….})

Day4
第四天完成剩下的练习题
stack,queue
排序算法,链表,哈希表

2024春季开源操作系统训练营第一阶段总结报告

一个月之前其实跟着 The Rust Programming Language 学习过 Rust,从同学那里听过 rustlings,但没有做过。这次有机会搓了一下,对理解的帮助还是挺大的。

实验

rustlings 的实验其实挺简单的。基本上跟着编译器和提示都能独立完成。它的 check 也不严格,数据点少、过编译即可。基本上完成质量全靠自觉

Rust 语言的约束,在最后 10 道算法题上还是体现得比较棘手的。一部分数据结构的实现需要套 Arc 甚至 unsafe。这也是所谓“必要的牺牲”吧。此外,生命周期的处理感觉还是不够优雅。有些奇怪的感觉。

除了这 10 题以外,其他部分基本都是语言基本语法、内容的介绍,也包括包管理器的使用,cargo test 的使用。相对来说都是比较基础的内容。只要跟着做就很简单。

总结

rustlings 是个很好的 Rust 学习项目,很适合计划做 rCore Lab 的同学入门 Rust 使用。是这样的。没错。

希望后期也能继续跟下去。

常见概念

类型

  • 类型推导与标注

      1. Rust是静态类型语言
      1. 编译器可以根据变量的值和上下文的使用方式自动推导出变量的类型
      1. 在无法推导出变量类型的情况下需要手动标注类型
  • 基本类型

    • 最小化原子类型(无法被结构)

    • 分类

      • 数值类型

        • 整数类型

          • 整数是没有小数部分的数字

          • 类型定义的形式统一为:有无符号 + 类型大小(位数)。无符号数表示数字只能取正数和0,而有符号则表示数字可以取正数、负数还有0。

          • 有符号数以补码形式存储

          • 整形字面量可以用下表的形式书写

          • 默认使用i32

          • 整数溢出

            • 当在 debug 模式编译时,Rust 会检查整型溢出,若存在这些问题,则使程序在编译时 panic(崩溃,Rust 使用这个术语来表明程序因错误而退出)

            • 在当使用 –release 参数进行 release 模式构建时,Rust 不检测溢出。相反,当检测到整型溢出时,Rust 会按照补码循环溢出(two’s complement wrapping)的规则处理。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。

            • 要显式处理可能的溢出,可以使用标准库针对原始数字类型提供的这些方法:

              • 使用 wrapping_* 方法在所有模式下都按照补码循环溢出规则处理,例如 wrapping_add

              • 如果使用 checked_* 方法时发生溢出,则返回 None 值

              • 使用 overflowing_* 方法返回该值和一个指示是否存在溢出的布尔值

              • 使用 saturating_* 方法,可以限定计算后的结果不超过目标类型的最大值或低于最小值

        • 浮点类型

          • 默认浮点类型是 f64,在现代的 CPU 中它的速度与 f32 几乎相同,但精度更高

          • 浮点数根据 IEEE-754 标准实现。f32 类型是单精度浮点型,f64 为双精度

          • 避免在浮点数上测试相等性

          • 当结果在数学上可能存在未定义时,需要格外的小心

          • 对于数学上未定义的结果,Rust 的浮点数类型使用 NaN (not a number)来处理这些情况

            • 可以使用 is_nan() 等方法,可以用来判断一个数值是否是 NaN
        • 序列

          • 用来生成连续的数值

          • 只允许用于数字或字符类型,原因是:它们可以连续,同时编译器在编译期可以检查该序列是否为空,字符和数字值是 Rust 中仅有的可以用于判断是否为空的类型

        • 运算

          • 数字运算

            • +

            • -

            • *

            • /

            • %

          • 位运算

      • 布尔类型

        • 有两个可能的值:true 和 false

        • 布尔值占用内存的大小为 1 个字节

      • 字符类型

        • Unicode编码 占4Byte
      • 单元类型,即(),唯一的值也是()

        • main 函数返回这个单元类型 ()

        • 没有返回值的函数在 Rust 中是有单独的定义的:发散函数( diverge function )

        • 内存占用为0!

      • 语句

        • 执行一些操作但是不会返回一个值

        • ;结尾

      • 表达式

        • 求值后返回一个值

        • 表达式不能包含分号

        • 表达式如果不返回任何值,会隐式地返回一个()

        • Rust函数就是表达式

      • 函数

        • 函数名和变量名使用蛇形命名法(snake case),例如 fn add_two() -> {}

        • 函数的位置可以随便放,Rust 不关心我们在哪里定义了函数,只要有定义即可

        • 每个函数参数都需要标注类型

          • Rust 是强类型语言,因此需要你为每一个函数参数都标识出它的具体类型
        • 返回

          • 函数的返回值就是函数体最后一条表达式的返回值,也可以使用 return 提前返回
        • 发散函数:返回值为!,表示永不返回

      • 方法

        • 使用impl关键字定义

        • Rust 的对象定义和方法定义是分离的

        • Self

          • self 表示 Rectangle 的所有权转移到该方法中,这种形式用的较少

          • &self 表示该方法对 Rectangle 的不可变借用

          • &mut self 表示可变借用

          • &self 其实是 self: &Self 的简写

        • 使用方法代替函数的优点

          • 不用在函数签名中重复书写 self 对应的类型

          • 代码的组织性和内聚性更强,对于代码维护和阅读来说,好处巨大

        • 关联函数

          • 定义在 impl 中且没有 self 的函数被称之为关联函数
  • 复合类型

    • 字符串

      字符串是由字符组成的连续集合
      Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4),这样有助于大幅降低字符串所占用的内存空间。

      • String

      • &str

      • String&str 的转换

        • String&str

          • &s

          • &s[..]

          • s.as_str()

        • &strString

          • String::from(“hello,world”)

          • “hello,world”.to_string()

      • 底层实现

        • 类型为u8类型的字节数组

        • 使用UTF-8编码,为了防止奇怪的返回值,不允许对字符串使用索引

        • 使用切片时要额外注意,不要切出没有意义的切片,并且会导致程序崩溃

      • 常用方法

        • push 追加字符char

        • push_str追加字符串字面量

        • insert插入单个字符char

        • insert_str插入字符串字面量

        • replace_range接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串

        • pop删除并返回字符串的最后一个字符
          存在返回值,其返回值是一个 Option 类型,如果字符串为空,则返回 None

        • remove 删除并返回字符串中指定位置的字符
          存在返回值,其返回值是删除位置的字符串
          按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误

        • truncate删除字符串中从指定位置开始到结尾的全部字符
          无返回值
          按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误

        • clear清空字符串

        • replace适用于 String 和 &str 类型。replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串

        • replacen该方法可适用于 String 和 &str 类型。replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。

        • 使用 + 或者 += 连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型

        • 使用 format! 连接字符串
          适用于 String 和 &str
          用法见格式化输出

      • 转义

        • 可以通过转义的方式 \ 输出 ASCII 和 Unicode 字符
      • 操作UTF-8字符串

        • 遍历字符

        • 遍历字节

    • 元组

      • 长度固定

      • 顺序固定

      • 可以使用模式匹配或者 . 操作符来获取元组中的值

        • 模式匹配

        • .运算符

      • 创建

    • 结构体

      • 组成

        • 通过关键字 struct 定义

        • 一个清晰明确的结构体 名称

        • 几个有名字的结构体 字段

      • 创建实例

        • 初始化实例时,每个字段都需要进行初始化

        • 初始化时的字段顺序不需要和结构体定义时的顺序一致

        • 简化创建:当函数参数和结构体字段同名时,可以直接使用缩略的方式进行初始化

      • 访问字段

        • 通过 . 操作符即可访问结构体实例内部的字段值,也可以修改它们

        • 必须要将结构体实例声明为可变的,才能修改其中的字段

        • 不支持将某个结构体某个字段标记为可变

      • 结构体更新语法..old_val

        • 必须在结构体的尾部使用

      • 内存布局

      • 元组结构体

        • 结构体必须要有名称,但是结构体的字段可以没有名称,这种结构体长得很像元组,因此被称为元组结构体
      • 单元结构体

        • 没有字段和属性

        • 不关心属性只关心行为

      • 结构体数据的所有权

        • 想要结构体拥有自己的数据而为借用别人的数据

        • 在借用数据时需要声明生命周期

      • 使用 #[derive(Debug)] 来打印结构体的信息

        • 使用格式化输出{:?}需要实现Display trait
      • 使用dbg!

    • 枚举

      • 概念

        • 允许通过列举可能的成员来定义一个枚举类型

        • 枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。

      • 枚举值

        • 通过::操作符来访问枚举类下的具体成员

        • 枚举的成员默认是从 0 开始递增的,每个成员的值都是前一个成员值加 1

      • 数据关联到枚举成员

      • `Option枚举

        • 用于处理空值
    • 数组

      • array

        • 速度很快但是长度固定

        • 三要素

          • 长度固定

          • 元素必须有相同的类型

          • 依次线性排列

        • 声明

          • 数组的元素类型要统一,长度要固定

          • 初始化一个某个值重复出现 N 次的数组

        • 访问数组元素

          • 因为数组是连续存放元素的,因此可以通过索引的方式来访问存放其中的元素

          • 越界访问

            • 会导致panic
        • 数组元素为非基础类型

          • 需要类型实现std::marker::Copy trait

          • 或者使用std::array::from_fn

        • 数组切片

          • 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置

          • 创建切片的代价非常小,因为切片只是针对底层数组的一个引用

          • 切片类型[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,&str字符串切片也同理

      • Vector

        • 可动态增长的但是有性能损耗
  • 集合类型

    • String

    • Vector

      • 创建

        • Vec::new

        • vec![]

        • Vec::from

      • 更新

        • push
      • 读取

        • 通过下标索引访问。

        • 使用 get 方法。

      • 迭代

      • 插入

        • insert

        • append

      • 删除

        • pop

        • remove

        • drain

      • 截断

        • truncate
      • 保留指定条件的元素

        • retain
      • 存储不同类型的元素

        • 通过特征对象或枚举实现
      • 排序

        • 稳定的排序

          • sort

          • sort_by

        • 非稳定排序

          • sort_unstable

          • sort_unstable_by

    • HashMap

      • 导入

        • use std::collections::HashMap;
      • 创建

        • new

        • 使用迭代器和 collect 方法创建

      • 插入

        • insert
      • 查询

        • get
      • 更新

      • 所有权

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

        • 若类型实现 Copy 特征,该类型会被复制进 HashMap,因此无所谓所有权

        • 若没实现 Copy 特征,所有权将被转移给 HashMap 中

      • 如果使用引用类型放入 HashMap 中,请确保该引用的生命周期至少跟 HashMap 活得一样久

      • f32 和 f64 浮点数,没有实现 std::cmp::Eq 特征,因此不可以用作 HashMap 的 Key

切片

  • 允许引用集合中部分连续的元素序列,而不是引用整个集合。

  • 创建切片的语法,使用方括号包括的一个序列:[开始索引..终止索引]

    • 开始索引是切片中第一个元素的索引位置

    • 终止索引是最后一个元素后面的索引位置

流程控制

  • 分支控制

    • if else

      • if语句是表达式 可以通过if表达式的返回值进行变量绑定(赋值

      • 可以使用else if实现更复杂的条件分支判断

    • match

  • 循环控制

    • for

      • 往往使用集合的引用形式

      • 不使用引用的话,所有权会被转移(move)到 for 语句块中,后面就无法再使用这个集合

      • 对于实现了copy trait的数组会对元素进行拷贝

      • 如果想在循环中,修改该元素,可以使用 mut 关键字

      • 获取元素索引:使用迭代器

    • while

    • loop

      • 简单的无限循环

      • loop 是一个表达式,因此可以返回一个值

    • 使用 continue 可以跳过当前当次的循环,开始下次的循环

    • 使用 break 可以直接跳出当前整个循环

      • break 可以单独使用,也可以带一个返回值,有些类似 return

模式匹配

match

  • match 的匹配必须要穷举出所有可能,因此这里用 _ 来代表未列出的所有可能性

  • match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同

  • X | Y,类似逻辑运算符 或,代表该分支可以匹配 X 也可以匹配 Y,只要满足一个即可

  • match 本身也是一个表达式,因此可以用它来赋值

if let

  • 只关心某一个值是否存在

  • 只要匹配一个条件,且忽略其他条件时就用 if let ,否则都用 match

matches

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

模式绑定

  • 模式匹配的另外一个重要功能是从模式中取出绑定的值

  • @ 操作符可以让我们将一个与模式相匹配的值绑定到新的变量上

穷尽匹配

  • 使用_ 通配符处理不需要匹配的值

解构Option

模式

模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,它往往和 match 表达式联用,以实现强大的模式匹配能力。

  • 组成

    • 字面值

    • 解构的数组、枚举、结构体或者元组

    • 变量

    • 通配符

    • 占位符

  • 使用情景

    • match

    • if let

    • while let

    • for

    • let

    • 函数参数

全模式列表

变量绑定与解构

变量绑定

可变性

  • 变量默认不可变

  • 使用mut关键字声明可变

  • 常量:不仅默认不可变并且自始至终不可变

    • 使用const标注

    • 必须标明类型

使用_可以使编译器忽略未被使用的变量

解构

  • let表达式可以从一个相对复杂的变量中匹配出该变量的一部分内容

解构式赋值

  • 在 Rust 1.59 版本后,可以在赋值语句的左式中使用元组、切片和结构体模式了

变量遮蔽

  • Rust允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的

所有权与借用

所有权

  • Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者

  • 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者

  • 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

  • 当把一个变量赋值(?)给另一个变量时,如果没有实现copy trait,则称为移动(move),会将所有权转移给后一个变量。

    • Rust 永远也不会自动创建数据的 “深拷贝”

    • 任何基本类型的组合可以 Copy ,不需要分配内存或某种形式资源的类型是可以 Copy 的

    • 部分move

      • 当解构一个变量时,可以同时使用 move 和引用模式绑定的方式。当这么做时,部分 move 就会发生:变量中一部分的所有权被转移给其它变量,而另一部分我们获取了它的引用。

      • 在这种情况下,原变量将无法再被使用,但是它没有转移所有权的那一部分依然可以使用,也就是之前被引用的那部分。

引用与借用

  • 引用与解引用

    • 常规引用是一个指针类型,指向了对象存储的内存地址。使用&创建引用

    • 使用*解引用

    • 引用的两种类型

      • 可变引用

        • 使用&mut创建引用

        • 只能同时存在一个

      • 不可变引用

        • 使用&创建不可变引用

        • 可变引用与不可变引用不能同时存在

    • 悬垂引用(Dangling References)

      悬垂引用也叫做悬垂指针,意思为指针指向某个值后,这个值被释放掉了,而指针仍然存在,其指向的内存可能不存在任何值或已被其它变量重新使用。在 Rust 中编译器可以确保引用永远也不会变成悬垂状态:当你获取数据的引用后,编译器可以确保数据不会在引用结束前被释放,要想释放数据,必须先停止其引用的使用。

  • 获取变量的引用,称之为借用(borrowing)

函数式编程

闭包

  • 概念

    • 是可以保存在一个变量中或作为参数传递给其他函数的匿名函数

    • 可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。

    • 不同于函数,闭包允许捕获被定义时所在作用域中的值。

  • 闭包会捕获定义其所处环境中的值并使用

  • 闭包关联于小范围的上下文,通常情况下可以自动推断值的类型,也可以手动标注

  • 捕获引用或者移动所有权

    闭包可以通过三种方式捕获其环境,它们直接对应到函数获取参数的三种方式:不可变借用,可变借用和获取所有权。闭包会根据函数体中如何使用被捕获的值决定用哪种方式捕获。

    • 不可变借用

    • 可变借用

    • 获取所有权

      即使闭包体不严格需要所有权,如果希望强制闭包获取它用到的环境中值的所有权,可以在参数列表前使用 move 关键字

  • 将被捕获的值移出闭包和 Fn trait

    一旦闭包捕获了定义它的环境中一个值的引用或者所有权(也就影响了什么会被移 进 闭包,如有),闭包体中的代码定义了稍后在闭包计算时对引用或值如何操作(也就影响了什么会被移 出 闭包,如有)。闭包体可以做以下任何事:将一个捕获的值移出闭包,修改捕获的值,既不移动也不修改值,或者一开始就不从环境中捕获值。闭包捕获和处理环境中的值的方式影响闭包实现的 trait。Trait 是函数和结构体指定它们能用的闭包的类型的方式。取决于闭包体如何处理值,闭包自动、渐进地实现一个、两个或三个 Fn trait。

    • FnOnce 适用于能被调用一次的闭包,所有闭包都至少实现了这个 trait,因为所有闭包都能被调用。一个会将捕获的值移出闭包体的闭包只实现 FnOnce trait,这是因为它只能被调用一次。

    • FnMut 适用于不会将捕获的值移出闭包体的闭包,但它可能会修改被捕获的值。这类闭包可以被调用多次。

    • Fn 适用于既不将被捕获的值移出闭包体也不修改被捕获的值的闭包,当然也包括不从环境中捕获值的闭包。这类闭包可以被调用多次而不改变它们的环境,这在会多次并发调用闭包的场景中十分重要。

迭代器

迭代器模式允许你对一个序列的项进行某些处理。迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。

在 Rust 中,迭代器是 惰性的(lazy),这意味着在调用方法使用迭代器之前它都不会有效果。

  • 创建

  • 迭代器都实现了一个叫做 Iterator 的定义于标准库的 trait

    next 是 Iterator 实现者被要求定义的唯一方法。next 一次返回迭代器中的一个项,封装在 Some 中,当迭代器结束时,它返回 None。

    从 next 调用中得到的值是 vector 的不可变引用。iter 方法生成一个不可变引用的迭代器。如果我们需要一个获取 v1 所有权并返回拥有所有权的迭代器,则可以调用 into_iter 而不是 iter。类似的,如果我们希望迭代可变引用,则可以调用 iter_mut 而不是 iter。

  • 消费迭代器的方法

    调用 next 方法的方法被称为 消费适配器(consuming adaptors),因为调用它们会消耗迭代器。

      1. collect()collect() 方法用于消费迭代器并将其转换为一个集合类型,例如 VecHashMapString。它会遍历整个迭代器,并将每个元素收集到一个集合中。
      1. count()count() 方法用于消费迭代器并计算其中元素的数量。它返回一个 usize 类型的值,表示迭代器中元素的个数。
      1. nth()nth() 方法用于消费迭代器并获取其中的第 n 个元素。它返回一个 Option 类型的值,表示可能存在的第 n 个元素。
      1. last()last() 方法用于消费迭代器并获取其中的最后一个元素。它返回一个 Option 类型的值,表示可能存在的最后一个元素。
      1. max() / min()max()min() 方法分别用于消费迭代器并获取其中的最大值和最小值。它们返回一个 Option 类型的值,表示可能存在的最大或最小值。
      1. sum() / product()sum()product() 方法分别用于消费迭代器并计算其中元素的和或乘积。它们返回一个值,表示迭代器中所有元素的和或乘积。
      1. all() / any()all()any() 方法用于消费迭代器并检查其中的元素是否满足某种条件。all() 方法要求所有元素都满足条件,而 any() 方法只要求至少有一个元素满足条件。它们返回一个布尔值,表示是否所有或任意元素满足条件。
      1. find() / position()find()position() 方法用于消费迭代器并查找满足某种条件的元素。find() 方法返回第一个满足条件的元素,而 position() 方法返回满足条件的第一个元素的索引。它们都返回一个 Option 类型的值,表示可能存在的满足条件的元素或索引。
  • 产生其他迭代器的方法

    Iterator trait 中定义了另一类方法,被称为 迭代器适配器(iterator adaptors),它们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。

    • map():map() 方法用于将迭代器中的每个元素应用一个函数,并将结果转换为另一种类型。它返回一个新的迭代器,其中包含了应用函数后的结果。

    • filter():filter() 方法用于过滤迭代器中的元素,只保留符合特定条件的元素。它接受一个闭包作为参数,该闭包返回一个布尔值,用于判断元素是否应该被保留。

    • take():take() 方法用于从迭代器中获取指定数量的元素,并返回一个新的迭代器。它接受一个usize类型的参数,表示要获取的元素数量。

    • skip():skip() 方法用于跳过迭代器中的前几个元素,并返回一个新的迭代器。它接受一个usize类型的参数,表示要跳过的元素数量。

    • enumerate():enumerate() 方法用于在迭代器中同时追踪元素的索引和值。它返回一个元组 (index, value) 的迭代器,其中 index 是索引,value 是元素的值。

    • zip():zip() 方法用于将两个迭代器的元素一一配对,并返回一个新的迭代器。它将两个迭代器的对应位置的元素组合成元组 (value1, value2)。

    • fold():fold() 方法用于将迭代器中的所有元素折叠(或归约)为一个单一的值。它接受一个初始值和一个闭包作为参数,该闭包将累积器和迭代器中的每个元素作为输入,并返回一个新的累积器。

    • flat_map():flat_map() 方法用于将每个元素转换为另一个迭代器,并将所有的结果展平成一个单一的迭代器。它的功能类似于 map(),但是它额外地将结果展平。

常见宏

  • panic!

  • todo!

  • unimplemented

智能指针

常用的智能指针

  • Box,用于在堆上分配值

    • 有单一所有者

    • 允许在编译时执行不可变或可变借用检查

  • Rc,一个引用计数类型,其数据可以有多个所有者

    • 允许相同数据有多个所有者

    • 仅允许在编译时执行不可变借用检查

  • Ref 和 RefMut,通过 RefCell 访问。( RefCell 是一个在运行时而不是在编译时执行借用规则的类型)。

    • 有单一所有者

    • 允许在运行时执行不可变或可变借用检查

  • Arc<T> 原子引用计数类型,用于多线程

  • Cow<T> Clone on Write

    写入时克隆的智能指针。

    Cow 类型是一种智能指针,具有写入时克隆的功能:它可以封装借用数据并提供不可变的访问权限,在需要突变或所有权时,可以懒惰地克隆数据。通过 “借用 “特性,该类型可与一般借用数据一起使用。

    Cow 实现了 Deref,这意味着你可以直接在它所包含的数据上调用非变异方法。如果需要进行变异,to_mut 将获取一个可变异的引用,并在必要时克隆该引用。

    如果需要引用计数指针,请注意 Rc::make_mut 和 Arc::make_mut 也可以提供写入时克隆功能。

智能指针的trait

  • Deref trait

    实现 Deref trait 允许我们重载 解引用运算符(dereference operator)*(不要与乘法运算符或通配符相混淆)。通过这种方式实现 Deref trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
    Deref trait,由标准库提供,要求实现名为 deref 的方法,其借用 self 并返回一个内部数据的引用

  • DerefMut trait

    用于重载可变引用的 * 运算符。

  • Drop trait

并发

Thread

  • 使用spawn创建新线程

    • 返回JoinHandle,JoinHandle 是一个拥有所有权的值,当对其调用 join 方法时,它会等待其线程结束。
  • 使用join阻塞线程

  • 使用move关键字来将闭包捕获的值的所有权传递给另一个线程

线程通信

  • 使用mpsc::channel信道进行通信

    • 发送者tx

      • send

        • 可以clone
    • 接收者rx

      • recv

      • try_recv

    • mpsc:mutiple producer single consumer

  • 共享内存

    • mutex并发原语

      • 使用lock获取锁

      • 使用Arc原子引用计数类型

内嵌的并发概念

  • Send trait

  • Sync

面向对象

四大特征

  • 封装

  • 抽象

  • 继承

    Rust中不存在继承
    选择继承有两个主要的原因。其一是代码复用:您可以为一种类型实现特定的行为,继承可将其复用到不同的类型上。在 Rust 代码中可以使用默认 trait 方法实现来进行有限的代码复用,就像示例 10-14 中在 Summary trait 上增加的 summarize 方法的默认实现。任何实现了 Summary trait 的类型都可以使用 summarize 方法而无须进一步实现。这类似于父类有一个方法的实现,继承的子类也拥有这个方法的实现。当实现 Summary trait 时也可以选择覆盖 summarize 的默认实现,这类似于子类覆盖从父类继承方法的实现。

  • 多态

    作为一种语言设计的解决方案,继承在许多新的编程语言中逐渐不被青睐,因为它经常有分享过多代码的风险。子类不应总是共享父类的所有特征,但是继承始终如此。它还引入了在子类上调用方法的可能性,这些方法可能没有意义,或因为方法不适用于子类而导致错误。此外,一些语言只允许单一继承(意味着子类只能从一个类继承),进一步限制了程序设计的灵活性。

trait对象

泛型

在函数中使用泛型

在结构体中使用泛型

在枚举中使用泛型

方法中使用泛型

const 泛型 针对值的泛型

默认泛型类型参数

  • 当使用泛型类型参数时,可以为其指定一个默认的具体类型,例如标准库中的 std::ops::Add 特征

  • 减少实现的样板代码

  • 扩展类型但是无需大幅修改现有的代码

Trait

定义

  • 定义了一组可以被共享的行为,只要实现了特征,你就能使用这组行为

  • 如果不同的类型具有相同的行为,那么我们就可以定义一个特征,然后为这些类型实现该特征。定义特征是把一些方法组合在一起,目的是定义一个实现某些目标所必需的行为的集合。

定义特征

  • 使用trait关键字

实现特征

默认实现

  • 可以在特征中定义具有默认实现的方法,这样其它类型无需再实现该方法,或者也可以选择重载该方法

使用特征作为函数参数

特征约束(trait bound)

Where 约束

函数返回中的 impl Trait

  • 可以通过 impl Trait 来说明一个函数返回了一个类型,该类型实现了某个特征

  • 只能有一个具体的类型

通过 derive 派生特征

  • 形如 #[derive(Debug)] 的代码已经出现了很多次,这种是一种特征派生语法,被 derive 标记的对象会自动实现对应的默认特征代码,继承相应的功能

Trait 对象

  • 特征对象指向实现了 特征的类型的实例,这种映射关系是存储在一张表中,可以在运行时通过特征对象找到具体调用的类型方法。

  • 动态分发

    • 当使用特征对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于特征对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。
  • 特征对象的限制

    • 不是所有特征都能拥有特征对象,只有对象安全的特征才行。当一个特征的所有方法都有如下属性时,它的对象才是安全的

      • 方法的返回类型不能是 Self

      • 方法没有任何泛型参数

关联类型

  • 关联类型是在特征定义的语句块中,申明一个自定义类型,这样就可以在特征的方法签名中使用该类型

生命周期

生命周期是引用的有效作用域

主要作用是避免悬垂指针

为了保证 Rust 的所有权和借用的正确性,Rust 使用了一个借用检查器(Borrow checker),来检查我们程序的借用正确性

在存在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析

unsafe

作用

  • 解引用裸指针

  • 调用一个 unsafe 或外部的函数

  • 访问或修改一个可变的静态变量

  • 实现一个 unsafe 特征

  • 访问 union 中的字段

raw pointer

  • 可以绕过 Rust 的借用规则,可以同时拥有一个数据的可变、不可变指针,甚至还能拥有多个可变的指针

  • 并不能保证指向合法的内存

  • 可以是 null

  • 没有实现任何自动的回收 (drop)

  • 创建裸指针是安全的行为,而解引用裸指针才是不安全的行为

  • 创建裸指针

    • 基于值的引用

    • 基于地址

    • 基于智能指针创建裸指针

调用 unsafe 函数或方法

用安全抽象包裹 unsafe 代码

使用 extern 函数调用外部代码

  • 原理:Foreign Function Interface (FFI)

访问或修改可变静态变量

  • 在Rust中 全局变量就是静态变量

  • 静态变量的引用的生命周期只能是’static

  • 有绝对地址

实现不安全 trait

访问联合体中的字段

学习背景

本人是一名大二学生。

去年三月自学过 rust,使用 rust 写过两三个练习的小程序,所以对 rust 的语法和所有权机制还是比较熟悉的。且从去年年底起,对操作系统相关的知识非常感兴趣,在刷了南京大学 Jyy 老师的课后对操作系统有了大概的概念(虽然并没有做 lab)。

今年通过同学,了解到这个夏令营。居然是用 rust 来写操作系统!遂叫上两三个队友一起加入了。

程序安全

内存安全一直是程序设计中一个难以解决的问题。从原始的 gets 溢出攻击,到最近的 xz 供应链后门事件。多少都利用了内存天生的不安全的特性。

而 rust 做的事情,是将天生不安全的代码世界,划出一部分 safe 区域。通过在编译器上的工作,保证了程序员只要在 safe 区域内编写代码,程序就可以保证内存安全。

虽然 safe rust 的表达能力有些时候并不能让人满意,并且在写代码的时候经常和编译器搏斗()。但在我看来,这实际上是把后续 debug 所需要的精力,提前到了编写代码的时候。

所有权

这个世界上的编程语言一般分为两种:有 gc 的,和没有 gc 的

在 c/c++中,macllo 或 new 一段内存在 heap 上后,需要程序员自己在不需要这段内存之后,释放这段内存。随后可以让别的代码块使用这段内存。

这样做的问题在于:

  1. 很多时候,程序员可能会忘记释放被分配的内存
  2. 也有些时候,已经被释放的内存会被程序员误操作,再次释放

于是我们有了 gc——

在 java 这样的语言中,gc(内存回收器)会定期暂停程序的一切行为,回收当前没有被引用的对象所占用的内存。这样的语言还有 go。

这样就不需要程序员来负责内存的释放了,也就不存在多次释放和不释放的问题了。(虽然循环依赖依旧会导致内存溢出……,但这并没有那么危险)

但 gc 其实是一个比较“重”的东西,并且会定期“暂停世界”。这在互联网服务(Java/Go)的领域并不关键,但在操作系统/嵌入式这样的领域中,底层并不喜欢 gc 这样的设计。

rust 利用所有权机制,从另一个角度缓解了内存问题。之所以说是缓解,因为 rust 也没有解决内存溢出的问题。

rust 中,一个变量可以拥有一段 heap 上的内存的“所有权”。当这个变量结束其【生命周期】后,会自动调用其 drop() 方法

而变量的“所有权”可以通过函数传递,程序员可以将一个变量的所有权移交给函数内部,然后获得函数移交出来的变量的所有权。

也可以把变量借给函数,通过 mut 关键字。让函数可以在不获得所有权的前提下,访问变量的值。

在函数需要修改变量时,也可以把变量可变借用给函数。

值得一提的是,同一时间可以存在多个借用,或者一个可变借用。这在很大程度上避免了数据竞争

在多线程程序中,也可以通过 Mutex,Arc 等数据结构,进一步避免并行程序中的数据竞争

rust 哲学

不同于 c/c++,也不同于 java/go。

rust 在自动内存管理和性能之间,选择了我全都要

rust 的 std 遵守着零成本抽象,即意味着同样的功能,使用 rust 和手动编写的代码,性能上是一致的。

这也能让人更放心的使用 rust 自带的各种数据结构和功能。

操作系统

OS 在我看来,是所有软件工程的基础,也是软件工程中的软件工程。

对硬件: 操作系统只是运行在硬件上的一个程序

对软件: 操作系统为软件创造了一个虚拟的环境,让软件程序有一种自己正掌握着整个硬件的错觉。并且,操作系统为软件提供了一系列系统调用。

状态机

jyy 老师的课给我的最大的收获,是将一切程序看作一个状态机。

而操作系统也是一个状态机: 一个管理状态机的状态机。

从这个视角看待软件世界,能很大程度上避免panic(因为看到繁杂的代码中各种函数,很难不让人头疼和恐慌)

如果你想体验这种感觉,可以 git clone 一个kernel(?)

魔法师

世界上的一切都是魔法

法师只需要喊出法咒/作出动作,世界就会回应他,在现实中展现神迹。

程序员只要敲击键盘,电脑就会回应他,屏幕上显示出对应的字母。

这中间的一切都是被封装好的。就像是魔法一样。

从普通软件的视角看,他在 systemcall 时,对于该程序来说,下一个瞬间,它所在的世界,就根据其所呼唤的 systemcall 回应了它。

编写操作系统,其实就是在编制普通软件的世界。

事实上,操作系统也只是在运用硬件和sbi提供给它的魔法罢了

最后

以上是在我眼中的 rust操作系统 ,作为一个初学者,难免出现错漏和片面。

如果读者发现什么需要指正和交流的,欢迎联系我。oveln@outlook

以及OvBlog