rust学习笔记(3):Day2 Moring

欢迎来到第二天,现在我们已经看到了相当数量的 Rust 代码,我们将继续:

  • 结构、枚举、方法。
  • 模式匹配:解构枚举、结构和数组。
  • 控制流构造:if, if let, while, while let, break, 和 continue.
  • 标准库: String, OptionResult, Vec, HashMap, RcArc.
  • 模块:可见性、路径和文件系统层次结构。

结构体

类似 C 、 C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Person {
name: String,
age: u8,
}

fn main() {
let mut peter = Person {
name: String::from("Peter"),
age: 27,
};
println!("{} is {} years old", peter.name, peter.age);

peter.age = 28;
println!("{} is {} years old", peter.name, peter.age);

let jackie = Person {
name: String::from("Jackie"),
..peter
};
println!("{} is {} years old", jackie.name, jackie.age);
}

元组结构体 / Tuple Structs

如果字段名不重要,可以使用元组结构体:

1
2
3
4
5
6
struct Point(i32, i32);

fn main() {
let p = Point(17, 23);
println!("({}, {})", p.0, p.1);
}

这通常用于单字段包装器(称为newtypes):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct PoundOfForce(f64);
struct Newtons(f64);

fn compute_thruster_force() -> PoundOfForce {
todo!("Ask a rocket scientist at NASA")
}

fn set_thruster_force(force: Newtons) {
// ...
}

fn main() {
let force = compute_thruster_force();
set_thruster_force(force); // 报错,类型不一样
}

字段简写语法

如果已经拥有具有正确名称的变量,则可以使用简写创建结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}

impl Person {
fn new(name: String, age: u8) -> Person {
Person { name, age }
}
}

fn main() {
let peter = Person::new(String::from("Peter"), 27);
println!("{peter:?}");
}

枚举

enum关键字允许创建具有几个不同变体的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn generate_random_number() -> i32 {
4 // Chosen by fair dice roll. Guaranteed to be random.
}

#[derive(Debug)]
enum CoinFlip {
Heads,
Tails,
}

fn flip_coin() -> CoinFlip {
let random_number = generate_random_number();
if random_number % 2 == 0 {
return CoinFlip::Heads;
} else {
return CoinFlip::Tails;
}
}

fn main() {
println!("You got: {:?}", flip_coin());
}

载荷变体

您可以定义更丰富的枚举,其中变量携带数据。然后可以使用match语句从每个变量中提取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum WebEvent {
PageLoad, // Variant without payload
KeyPress(char), // Tuple struct variant
Click { x: i64, y: i64 }, // Full struct variant
}

#[rustfmt::skip]
fn inspect(event: WebEvent) {
match event {
WebEvent::PageLoad => println!("page loaded"),
WebEvent::KeyPress(c) => println!("pressed '{c}'"),
WebEvent::Click { x, y } => println!("clicked at x={x}, y={y}"),
}
}

fn main() {
let load = WebEvent::PageLoad;
let press = WebEvent::KeyPress('x');
let click = WebEvent::Click { x: 20, y: 80 };

inspect(load);
inspect(press);
inspect(click);
}

枚举大小

Rust枚举被紧密地打包,考虑到由于对齐的限制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use std::mem::{align_of, size_of};

macro_rules! dbg_size {
($t:ty) => {
println!("{}: size {} bytes, align: {} bytes",
stringify!($t), size_of::<$t>(), align_of::<$t>());
};
}

enum Foo {
A,
B,
}

#[repr(u32)]
enum Bar {
A, // 0
B = 10000,
C, // 10001
}

fn main() {
dbg_size!(Foo);
dbg_size!(Bar);
dbg_size!(bool);
dbg_size!(Option<bool>);
dbg_size!(&i32);
dbg_size!(Option<&i32>);
}

方法

Rust允许您将函数与新类型关联起来,使用impl语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}

impl Person {
fn say_hello(&self) {
println!("Hello, my name is {}", self.name);
}
}

fn main() {
let peter = Person {
name: String::from("Peter"),
age: 27,
};
peter.say_hello();
}

方法接收者

上面的&self表示该方法不可变地借用对象。方法还有其他可能的接收者:

  • &self:使用共享的和不可变的引用从调用者那里借用对象。对象之后可以再次使用。
  • &mut self:使用唯一且可变的引用从调用者那里借用对象。对象之后可以再次使用。
  • self: 获取对象的所有权并将其移离调用者。方法成为对象的所有者。当方法返回时,该对象将被删除(释放),除非显式传输其所有权。
  • mut self:与上面相同,但是当方法拥有对象时,它也可以改变它。完全的所有权并不自动意味着可变性。
  • 没有接收者:这将成为结构上的静态方法。通常用于创建按照约定称为new的构造函数。

除了self上的变量之外,还有一些特殊的包装器类型允许作为接收器类型,例如Box<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
#[derive(Debug)]
struct Race {
name: String,
laps: Vec<i32>,
}

impl Race {
fn new(name: &str) -> Race { // 没有接收者, 一个静态方法
Race { name: String::from(name), laps: Vec::new() }
}

fn add_lap(&mut self, lap: i32) { // 借用调用者而且允许修改,后面调用者可继续使用
self.laps.push(lap);
}

fn print_laps(&self) { // 借用而且只获取读权限给self
println!("Recorded {} laps for {}:", self.laps.len(), self.name);
for (idx, lap) in self.laps.iter().enumerate() {
println!("Lap {idx}: {lap} sec");
}
}

fn finish(self) { // 独占所有权
let total = self.laps.iter().sum::<i32>();
println!("Race {} is finished, total lap time: {}", self.name, total);
}
}

fn main() {
let mut race = Race::new("Monaco Grand Prix");
race.add_lap(70);
race.add_lap(68);
race.print_laps();
race.add_lap(71);
race.print_laps();
race.finish();
// race.add_lap(42); // 这里不能再使用,所有权已经被转移
}

模式匹配 / pattern matching

match关键字允许您根据一个或多个模式匹配一个值。从上到下进行比较,第一个匹配的胜出。

模式可以是简单的值,类似于C和C++中的switch:

1
2
3
4
5
6
7
8
9
10
fn main() {
let input = 'x';

match input {
'q' => println!("Quitting"),
'a' | 's' | 'w' | 'd' => println!("Moving around"),
'0'..='9' => println!("Number input"),
_ => println!("Something else"),
}
}

_ 模式是匹配任何值的通配符模式。

解构枚举

模式还可以用于将变量绑定到值的一部分。这是检查类型结构的方法。让我们从一个简单的枚举类型开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum Result {
Ok(i32),
Err(String),
}

fn divide_in_two(n: i32) -> Result {
if n % 2 == 0 {
Result::Ok(n / 2)
} else {
Result::Err(format!("cannot divide {n} into two equal parts"))
}
}

fn main() {
let n = 100;
match divide_in_two(n) {
Result::Ok(half) => println!("{n} divided in two is {half}"),
Result::Err(msg) => println!("sorry, an error happened: {msg}"),
}
}

这里的解构和ES6的解构非常像,这里如果Result如果是Ok则将Ok里的i32类型的值提取出来放到half。

解构结构体

同样可以对结构体进行解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Foo {
x: (u32, u32),
y: u32,
}

#[rustfmt::skip]
fn main() {
let foo = Foo { x: (1, 2), y: 3 };
match foo {
Foo { x: (1, b), y } => println!("x.0 = 1, b = {b}, y = {y}"),
Foo { y: 2, x: i } => println!("y = 2, i = {i:?}"),
Foo { y, .. } => println!("y = {y}, other fields were ignored"),
}
}

解构数组

可以通过匹配数组、元组和切片的元素来解构数组、元组和切片

1
2
3
4
5
6
7
8
9
10
#[rustfmt::skip]
fn main() {
let triple = [0, -2, 3];
println!("Tell me about {triple:?}");
match triple {
[0, y, z] => println!("First is 0, y = {y}, and z = {z}"),
[1, ..] => println!("First is 1 and the rest were ignored"),
_ => println!("All elements were ignored"),
}
}

匹配守卫

在匹配时,您可以向模式添加保护。这是一个任意的布尔表达式,如果模式匹配就会执行:

1
2
3
4
5
6
7
8
9
10
11
#[rustfmt::skip]
fn main() {
let pair = (2, -2);
println!("Tell me about {pair:?}");
match pair {
(x, y) if x == y => println!("These are twins"),
(x, y) if x + y == 0 => println!("Antimatter, kaboom!"),
(x, _) if x % 2 == 1 => println!("The first one is odd"),
_ => println!("No correlation..."),
}
}

练习题

我们将在两种上下文中讨论方法的实现:

  • 跟踪健康统计数据的简单结构。
  • 绘图库的多结构体和枚举

健康统计

您正在实施一个健康监测系统。作为其中的一部分,需要跟踪用户的健康统计信息。

您将从impl块中的一些存根函数和User结构定义开始。您的目标是在impl块中定义的User结构上实现存根方法。

补上缺少的代码:

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
// TODO: remove this when you're done with your implementation.
#![allow(unused_variables, dead_code)]

struct User {
name: String,
age: u32,
weight: f32,
}

impl User {
pub fn new(name: String, age: u32, weight: f32) -> Self {
unimplemented!()
}

pub fn name(&self) -> &str {
unimplemented!()
}

pub fn age(&self) -> u32 {
unimplemented!()
}

pub fn weight(&self) -> f32 {
unimplemented!()
}

pub fn set_age(&mut self, new_age: u32) {
unimplemented!()
}

pub fn set_weight(&mut self, new_weight: f32) {
unimplemented!()
}
}

fn main() {
let bob = User::new(String::from("Bob"), 32, 155.2);
println!("I'm {} and my age is {}", bob.name(), bob.age());
}

#[test]
fn test_weight() {
let bob = User::new(String::from("Bob"), 32, 155.2);
assert_eq!(bob.weight(), 155.2);
}

#[test]
fn test_set_age() {
let mut bob = User::new(String::from("Bob"), 32, 155.2);
assert_eq!(bob.age(), 32);
bob.set_age(33);
assert_eq!(bob.age(), 33);
}

我的答案:

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
struct User {
name: String,
age: u32,
weight: f32,
}

impl User {
pub fn new(name: String, age: u32, weight: f32) -> Self {
User {
name,
age,
weight,
}
}

pub fn name(&self) -> &str {
&self.name
}

pub fn age(&self) -> u32 {
self.age
}

pub fn weight(&self) -> f32 {
self.weight
}

pub fn set_age(&mut self, new_age: u32) {
self.age = new_age
}

pub fn set_weight(&mut self, new_weight: f32) {
self.weight = new_weight
}
}

fn main() {
let mut bob = User::new(String::from("Bob"), 32, 155.2);
println!("I'm {} and my age is {}, weight is {}", bob.name(), bob.age(), bob.weight());
bob.set_age(16);
bob.set_weight(101 as f32);
println!("I'm {} and my age is {}, weight is {}", bob.name(), bob.age(), bob.weight());
}

#[test]
fn test_weight() {
let bob = User::new(String::from("Bob"), 32, 155.2);
assert_eq!(bob.weight(), 155.2);
}

#[test]
fn test_set_age() {
let mut bob = User::new(String::from("Bob"), 32, 155.2);
assert_eq!(bob.age(), 32);
bob.set_age(33);
assert_eq!(bob.age(), 33);
}

多边形结构

我们将创建一个多边形结构,其中包含一些点:

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
101
102
103
104
105
// TODO: remove this when you're done with your implementation.
#![allow(unused_variables, dead_code)]

pub struct Point {
// add fields
}

impl Point {
// add methods
}

pub struct Polygon {
// add fields
}

impl Polygon {
// add methods
}

pub struct Circle {
// add fields
}

impl Circle {
// add methods
}

pub enum Shape {
Polygon(Polygon),
Circle(Circle),
}

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

fn round_two_digits(x: f64) -> f64 {
(x * 100.0).round() / 100.0
}

#[test]
fn test_point_magnitude() {
let p1 = Point::new(12, 13);
assert_eq!(round_two_digits(p1.magnitude()), 17.69);
}

#[test]
fn test_point_dist() {
let p1 = Point::new(10, 10);
let p2 = Point::new(14, 13);
assert_eq!(round_two_digits(p1.dist(p2)), 5.00);
}

#[test]
fn test_point_add() {
let p1 = Point::new(16, 16);
let p2 = p1 + Point::new(-4, 3);
assert_eq!(p2, Point::new(12, 19));
}

#[test]
fn test_polygon_left_most_point() {
let p1 = Point::new(12, 13);
let p2 = Point::new(16, 16);

let mut poly = Polygon::new();
poly.add_point(p1);
poly.add_point(p2);
assert_eq!(poly.left_most_point(), Some(p1));
}

#[test]
fn test_polygon_iter() {
let p1 = Point::new(12, 13);
let p2 = Point::new(16, 16);

let mut poly = Polygon::new();
poly.add_point(p1);
poly.add_point(p2);

let points = poly.iter().cloned().collect::<Vec<_>>();
assert_eq!(points, vec![Point::new(12, 13), Point::new(16, 16)]);
}

#[test]
fn test_shape_perimeters() {
let mut poly = Polygon::new();
poly.add_point(Point::new(12, 13));
poly.add_point(Point::new(17, 11));
poly.add_point(Point::new(16, 16));
let shapes = vec![
Shape::from(poly),
Shape::from(Circle::new(Point::new(10, 20), 5)),
];
let perimeters = shapes
.iter()
.map(Shape::perimeter)
.map(round_two_digits)
.collect::<Vec<_>>();
assert_eq!(perimeters, vec![15.48, 31.42]);
}
}

#[allow(dead_code)]
fn main() {}