Comprehensive Rust 🦀 - 第二天
这是由 Android 团队开发的为期四天的 Rust 课程。该课程涵盖了 Rust 的全部内容,从基本语法到高级主题,如泛型和错误处理。它还包括最后一天的 Android 特定内容。
现在我们已经了解了相当多的 Rust,我们将继续:
- 结构、枚举、方法。
- 模式匹配:解构枚举、结构和数组。
- 控制流构造:
if
、if let
、while
、while let
、break
和continue
。 - 标准库:
String
、Option
与Result
、Vec
与HashMap
、Rc
与Arc
。 - 模块:可见性、路径和文件系统层次结构。
结构体(Struct)
与 C 和 C++ 一样,Rust 支持自定义结构:
1struct Person {
2 name: String,
3 age: u8,
4}
5
6fn main() {
7 let mut peter = Person {
8 name: String::from("Peter"),
9 age: 27,
10 };
11 println!("{} is {} years old", peter.name, peter.age);
12
13 peter.age = 28;
14 println!("{} is {} years old", peter.name, peter.age);
15
16 let jackie = Person {
17 name: String::from("Jackie"),
18 ..peter
19 };
20 println!("{} is {} years old", jackie.name, jackie.age);
21}
..
语法允许我们从旧结构中复制大部分字段,而无需显式地将其全部键入。它必须始终是最后一个元素。
元组结构体(Tuple Struct)
如果字段名称不重要,您可以使用元组结构体:
1struct Point(i32, i32);
2
3fn main() {
4 let p = Point(17, 23);
5 println!("({}, {})", p.0, p.1);
6}
这通常用于单字段包装器(称为新类型):
1struct PoundOfForce(f64);
2struct Newtons(f64);
3
4fn compute_thruster_force() -> PoundOfForce {
5 todo!("Ask a rocket scientist at NASA")
6}
7
8fn set_thruster_force(force: Newtons) {
9 // ...
10}
11
12fn main() {
13 let force = compute_thruster_force();
14 set_thruster_force(force);
15}
字段速记语法
如果您已经有了具有正确名称的变量,那么您可以使用简写形式创建结构:
1#[derive(Debug)]
2struct Person {
3 name: String,
4 age: u8,
5}
6
7impl Person {
8 fn new(name: String, age: u8) -> Person {
9 Person { name, age }
10 }
11}
12
13fn main() {
14 let peter = Person::new(String::from("Peter"), 27);
15 println!("{peter:?}");
16}
枚举(Enum)
关键字 enum
允许创建具有几个不同变体(Variant)的类型:
1fn generate_random_number() -> i32 {
2 4 // Chosen by fair dice roll. Guaranteed to be random.
3}
4
5#[derive(Debug)]
6enum CoinFlip {
7 Heads,
8 Tails,
9}
10
11fn flip_coin() -> CoinFlip {
12 let random_number = generate_random_number();
13 if random_number % 2 == 0 {
14 return CoinFlip::Heads;
15 } else {
16 return CoinFlip::Tails;
17 }
18}
19
20fn main() {
21 println!("You got: {:?}", flip_coin());
22}
变体有效载荷(Variant Payloads)
您可以定义更丰富的枚举,其中变体携带数据。然后,您可以使用该 match
语句从每个变体中提取数据:
1enum WebEvent {
2 PageLoad, // 没有有效载荷的变体
3 KeyPress(char), // 元组结构体变体
4 Click { x: i64, y: i64 }, // 完整结构体变体
5}
6
7#[rustfmt::skip]
8fn inspect(event: WebEvent) {
9 match event {
10 WebEvent::PageLoad => println!("page loaded"),
11 WebEvent::KeyPress(c) => println!("pressed '{c}'"),
12 WebEvent::Click { x, y } => println!("clicked at x={x}, y={y}"),
13 }
14}
15
16fn main() {
17 let load = WebEvent::PageLoad;
18 let press = WebEvent::KeyPress('x');
19 let click = WebEvent::Click { x: 20, y: 80 };
20
21 inspect(load);
22 inspect(press);
23 inspect(click);
24}
枚举大小(Enum Size)
Rust 枚举是紧密打包的,考虑了对齐引起的约束:
1use std::mem::{align_of, size_of};
2
3macro_rules! dbg_size {
4 ($t:ty) => {
5 println!("{}: size {} bytes, align: {} bytes",
6 stringify!($t), size_of::<$t>(), align_of::<$t>());
7 };
8}
9
10enum Foo {
11 A,
12 B,
13}
14
15fn main() {
16 dbg_size!(Foo);
17}
方法(Method)
Rust 允许您将函数与新类型相关联。用一个 impl
块来做到这一点:
1#[derive(Debug)]
2struct Person {
3 name: String,
4 age: u8,
5}
6
7impl Person {
8 fn say_hello(&self) {
9 println!("Hello, my name is {}", self.name);
10 }
11}
12
13fn main() {
14 let peter = Person {
15 name: String::from("Peter"),
16 age: 27,
17 };
18 peter.say_hello();
19}
接收者
&self
表明方法不可变地借用了对象。方法还有其他可能的接收者:
&self
:使用共享且不可变的引用从调用者那里借用对象。 之后可以再次使用该对象。&mut self
:使用唯一且可变的引用从调用者那里借用对象。 之后可以再次使用该对象。self
:获取对象的所有权并将其从调用者那里移开。 该方法成为对象的所有者。 当方法返回时,该对象将被删除(释放),除非它的所有权被显式传输。mut self
:与上面相同,但是当方法拥有对象时,它也可以改变它。 完全所有权并不自动意味着可变性。没有接收者
:这成为结构上的静态方法。 通常用于创建按照约定称为 new 的构造函数。
除了 self
的变体之外,还有一些 特殊的包装器类型 可以作为接收者类型,例如Box<Self>
。
示例
1#[derive(Debug)]
2struct Race {
3 name: String,
4 laps: Vec<i32>,
5}
6
7impl Race {
8 fn new(name: &str) -> Race { // No receiver, a static method
9 Race { name: String::from(name), laps: Vec::new() }
10 }
11
12 fn add_lap(&mut self, lap: i32) { // Exclusive borrowed read-write access to self
13 self.laps.push(lap);
14 }
15
16 fn print_laps(&self) { // Shared and read-only borrowed access to self
17 println!("Recorded {} laps for {}:", self.laps.len(), self.name);
18 for (idx, lap) in self.laps.iter().enumerate() {
19 println!("Lap {idx}: {lap} sec");
20 }
21 }
22
23 fn finish(self) { // Exclusive ownership of self
24 let total = self.laps.iter().sum::<i32>();
25 println!("Race {} is finished, total lap time: {}", self.name, total);
26 }
27}
28
29fn main() {
30 let mut race = Race::new("Monaco Grand Prix");
31 race.add_lap(70);
32 race.add_lap(68);
33 race.print_laps();
34 race.add_lap(71);
35 race.print_laps();
36 race.finish();
37 // race.add_lap(42);
38}
Rust 在调用方法时支持自动引用和解引用,即Rust 自动添加
&
,*
,muts
以便该对象与方法签名匹配。
控制流
正如我们所见,if
是 Rust 中的一个表达式。它用于有条件地评估两个 块(Block)
之一,但块可以有一个值,然后成为 if
表达式的值。其他控制流表达式在 Rust 中的工作方式类似。
Blocks
Rust 中的块具有值和类型,值是块的最后一个表达式:
1fn main() {
2 let x = {
3 let y = 10;
4 println!("y: {y}");
5 let z = {
6 let w = {
7 3 + 4
8 };
9 println!("w: {w}");
10 y * w
11 };
12 println!("z: {z}");
13 z - y
14 };
15 println!("x: {x}");
16}
同样的规则也适用于函数,函数体的值就是返回值:
1fn double(x: i32) -> i32 {
2 x + x
3}
4
5fn main() {
6 println!("doubled: {}", double(7));
7}
如果最后一个表达式以
;
结尾,则结果值与类型为()
。
if 表达式
1fn main() {
2 let mut x = 10;
3 if x % 2 == 0 {
4 x = x / 2;
5 } else {
6 x = 3 * x + 1;
7 }
8}
可以将其用作表达式:
1fn main() {
2 let mut x = 10;
3 x = if x % 2 == 0 {
4 x / 2
5 } else {
6 3 * x + 1
7 };
8}
务必牢记
语句
与表达式
的区别是什么。
if let 表达式
如果要将值与模式匹配,可以使用 if let
:
1fn main() {
2 let arg = std::env::args().next();
3 if let Some(value) = arg {
4 println!("Program name: {value}");
5 } else {
6 println!("Missing name?");
7 }
8}
有关Rust 模式的更多详细信息,请参阅模式匹配。
while 表达式
1fn main() {
2 let mut x = 10;
3 while x != 1 {
4 x = if x % 2 == 0 {
5 x / 2
6 } else {
7 3 * x + 1
8 };
9 }
10 println!("Final x: {x}");
11}
while let 表达式
1fn main() {
2 let v = vec![10, 20, 30];
3 let mut iter = v.into_iter();
4
5 while let Some(x) = iter.next() {
6 println!("x: {x}");
7 }
8}
这里涉及到了迭代器相关知识点,但这并非 Rust 特有,因此不做详述。
for 表达式
1fn main() {
2 let v = vec![10, 20, 30];
3
4 for x in v {
5 println!("x: {x}");
6 }
7
8 for i in (0..10).step_by(2) {
9 println!("i: {i}");
10 }
11}
(0..10) 是实现 Iterator 特性的范围(Range)。
loop 表达式
1fn main() {
2 let mut x = 10;
3 loop {
4 x = if x % 2 == 0 {
5 x / 2
6 } else {
7 3 * x + 1
8 };
9 if x == 1 {
10 break;
11 }
12 }
13 println!("Final x: {x}");
14}
用一个值(例如
break 8
)打破循环并打印出来。
match 表达式
关键字 match
用于将值与一个或多个模式匹配。从这个意义上说,它就像一系列if let
表达式:
1fn main() {
2 match std::env::args().next().as_deref() {
3 Some("cat") => println!("Will do cat things"),
4 Some("ls") => println!("Will ls some files"),
5 Some("mv") => println!("Let's move some files"),
6 Some("rm") => println!("Uh, dangerous!"),
7 None => println!("Hmm, no program name?"),
8 _ => println!("Unknown program name!"),
9 }
10}
模式匹配(Pattern Matching)
match
关键字使您可以将一个值与一个或多个模式相匹配,匹配是从上到下进行的。
1fn main() {
2 let input = 'x';
3
4 match input {
5 'q' => println!("Quitting"),
6 'a' | 's' | 'w' | 'd' => println!("Moving around"),
7 '0'..='9' => println!("Number input"),
8 _ => println!("Something else"),
9 }
10}
_
模式是匹配任何值的通配符模式。..
可以根据需要扩展,1..=5
代表一个包含范围。
解构枚举
模式也可用于将变量绑定到您的部分值,这就是检查类型结构的方式。让我们用一个简单的 enum
类型进行演示:
1enum Result {
2 Ok(i32),
3 Err(String),
4}
5
6fn divide_in_two(n: i32) -> Result {
7 if n % 2 == 0 {
8 Result::Ok(n / 2)
9 } else {
10 Result::Err(format!("cannot divide {n} into two equal parts"))
11 }
12}
13
14fn main() {
15 let n = 100;
16 match divide_in_two(n) {
17 Result::Ok(half) => println!("{n} divided in two is {half}"),
18 Result::Err(msg) => println!("sorry, an error happened: {msg}"),
19 }
20}
解构结构体
1struct Foo {
2 x: (u32, u32),
3 y: u32,
4}
5
6#[rustfmt::skip]
7fn main() {
8 let foo = Foo { x: (1, 2), y: 3 };
9 match foo {
10 Foo { x: (1, b), y } => println!("x.0 = 1, b = {b}, y = {y}"),
11 Foo { y: 2, x: i } => println!("y = 2, x = {i:?}"),
12 Foo { y, .. } => println!("y = {y}, other fields were ignored"),
13 }
14}
解构数组
您可以通过匹配元素来解构数组、元组和切片:
1#[rustfmt::skip]
2fn main() {
3 let triple = [0, -2, 3];
4 println!("Tell me about {triple:?}");
5 match triple {
6 [0, y, z] => println!("First is 0, y = {y}, and z = {z}"),
7 [1, ..] => println!("First is 1 and the rest were ignored"),
8 _ => println!("All elements were ignored"),
9 }
10}
指出
..
将如何扩展以考虑不同数量的元素。使用模式[.., b]
和[a@..,b]
显式的与尾部进行匹配。
匹配守卫(Match Guards)
匹配的时候,可以给一个模式加上 guard。这是一个任意的布尔表达式,仅在模式匹配后才会被执行:
1#[rustfmt::skip]
2fn main() {
3 let pair = (2, -2);
4 println!("Tell me about {pair:?}");
5 match pair {
6 (x, y) if x == y => println!("These are twins"),
7 (x, y) if x + y == 0 => println!("Antimatter, kaboom!"),
8 (x, _) if x % 2 == 1 => println!("The first one is odd"),
9 _ => println!("No correlation..."),
10 }
11}
标准库
Rust 带有一个标准库,它有助于建立一组由 Rust 库和程序使用的通用类型。这样,两个库就可以顺利地协同工作,因为它们都使用相同的 String
类型。
常见的词汇类型包括:
Option
和Result
类型:用于可选值和错误处理。String
:用于自有数据的默认字符串类型。Vec
: 一个标准的可扩展向量。HashMap
:具有可配置哈希算法的哈希映射类型。Box
:堆分配数据的拥有指针。Rc
:堆分配数据的共享引用计数指针。
事实上,Rust 包含标准库的几个层次:core
、alloc
、std
。
core
包括最基本的类型和函数,它们不依赖于libc
、分配器甚至操作系统的存在。alloc
包括需要全局堆分配器的类型,例如Vec
、Box
和Arc
。- 嵌入式 Rust 应用程序通常只使用
core
,有时使用alloc
。
Option & Result
Option 与 Result 代表可选数据:
1fn main() {
2 let numbers = vec![10, 20, 30];
3 let first: Option<&i8> = numbers.first();
4 println!("first: {first:?}");
5
6 let idx: Result<usize, usize> = numbers.binary_search(&10);
7 println!("idx: {idx:?}");
8}
String
String
是标准的堆分配可增长 UTF-8 字符串缓冲区:
1fn main() {
2 let mut s1 = String::new();
3 s1.push_str("Hello");
4 println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity());
5
6 let mut s2 = String::with_capacity(s1.len() + 1);
7 s2.push_str(&s1);
8 s2.push('!');
9 println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity());
10
11 let s3 = String::from("🇨🇭");
12 println!("s3: len = {}, number of chars = {}", s3.len(),
13 s3.chars().count());
14}
String
实现了 Deref<Target = str>
,这意味着您可以直接在 String
调用 str
方法。
当人们提到字符串时,他们可能在谈论
&str
或String
。
Vec
Vec
是标准的可调整大小的堆分配缓冲区:
1fn main() {
2 let mut v1 = Vec::new();
3 v1.push(42);
4 println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity());
5
6 let mut v2 = Vec::with_capacity(v1.len() + 1);
7 v2.extend(v1.iter());
8 v2.push(9999);
9 println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity());
10
11 // Canonical macro to initialize a vector with elements.
12 let mut v3 = vec![0, 0, 1, 2, 3, 4];
13
14 // Retain only the even elements.
15 v3.retain(|x| x % 2 == 0);
16 println!("{v3:?}");
17
18 // Remove consecutive duplicates.
19 v3.dedup();
20 println!("{v3:?}");
21}
Vec
实现了 Deref<Target = [T]>
,这意味着您可以直接在 Vec
上调用 Slice
方法。
vec![...]
是一个可以代替Vec::new()
使用的规范宏,它支持向向量添加初始元素。
HashMap
具有针对 HashDoS 攻击保护的标准哈希映射:
1use std::collections::HashMap;
2
3fn main() {
4 let mut page_counts = HashMap::new();
5 page_counts.insert("Adventures of Huckleberry Finn".to_string(), 207);
6 page_counts.insert("Grimms' Fairy Tales".to_string(), 751);
7 page_counts.insert("Pride and Prejudice".to_string(), 303);
8
9 if !page_counts.contains_key("Les Misérables") {
10 println!("We know about {} books, but not Les Misérables.",
11 page_counts.len());
12 }
13
14 for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
15 match page_counts.get(book) {
16 Some(count) => println!("{book}: {count} pages"),
17 None => println!("{book} is unknown.")
18 }
19 }
20
21 // Use the .entry() method to insert a value if nothing is found.
22 for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
23 let page_count: &mut i32 = page_counts.entry(book.to_string()).or_insert(0);
24 *page_count += 1;
25 }
26
27 println!("{page_counts:#?}");
28}
Box
Box
是指向堆上数据的拥有指针:
1fn main() {
2 let five = Box::new(5);
3 println!("five: {}", *five);
4}
Box<T>
实现了 Deref<Target = T>
,这意味着您可以直接在 Box<T>
上调用 T
的方法。
Box
类似于 C++ 中的std::unique_ptr
,只是它提供了不为空的保证。
递归数据结构
递归数据类型或具有动态大小的数据类型需要使用 Box
:
1#[derive(Debug)]
2enum List<T> {
3 Cons(T, Box<List<T>>),
4 Nil,
5}
6
7fn main() {
8 let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
9 println!("{list:?}");
10}
如果这里没有使用 Box 并且我们试图将一个 List 直接嵌入到 List 中,编译器将不会计算内存中结构的固定大小,它看起来是无限的。
Box 解决了这个问题,因为它与常规指针具有相同的大小,并且仅指向堆中 List 的下一个元素。
删除 List 定义中的 Box 并显示编译器错误。 “间接递归”暗示您可能想要使用 Box 或某种引用,而不是直接存储值。
利基优化(Niche Optimization)
1#[derive(Debug)]
2enum List<T> {
3 Cons(T, Box<List<T>>),
4 Nil,
5}
6
7fn main() {
8 let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
9 println!("{list:?}");
10}
Box 不能为空,因此指针始终有效且非空。 这允许编译器优化内存布局:
RC
Rc
是引用计数的共享指针,其确保只要有引用,它包含的值就有效,就像 C++ 的 std::shared_ptr
。
当您需要从多个地方引用相同的数据时使用它:
1use std::rc::Rc;
2
3fn main() {
4 let mut a = Rc::new(10);
5 let mut b = a.clone();
6
7 println!("a: {a}");
8 println!("b: {b}");
9}
如果需要改变 Rc 中的数据,则需要将数据包装在 Cell 或 RefCell 等类型中。如果您处于多线程上下文中,请参阅 Arc。您可以将共享指针降级为 弱指针(Weak
pointer),以创建将被丢弃的循环。
您可以调用 downgrade()
将 RC
降级为弱引用计数对象,以创建将被正确删除的循环(可能与 RefCell 结合使用)。
模块
crates 是一棵模块树,其中二进制(Binary) crate 创建可执行文件,而库(Lib) crate 编译为库。
模块定义组织、范围,并且是本节的重点。
1mod foo {
2 pub fn do_something() {
3 println!("In the foo module");
4 }
5}
6
7mod bar {
8 pub fn do_something() {
9 println!("In the bar module");
10 }
11}
12
13fn main() {
14 foo::do_something();
15 bar::do_something();
16}
可见性
默认情况下,模块项是私有的(隐藏实现细节)。
父项和同级项始终可见。换句话说,如果一个项目在 module 中可见foo
,那么它在 foo
的所有后代中都是可见的。
1mod outer {
2 fn private() {
3 println!("outer::private");
4 }
5
6 pub fn public() {
7 println!("outer::public");
8 }
9
10 mod inner {
11 fn private() {
12 println!("outer::inner::private");
13 }
14
15 pub fn public() {
16 println!("outer::inner::public");
17 super::private();
18 }
19 }
20}
21
22fn main() {
23 outer::public();
24}
使用
pub
关键字使模块公开。此外,还有高级pub(...)
说明符来限制可见性的范围。不太常见的是,您可以提供特定路径的可见性。
路径
作为相对路径解析:
foo
或self::foo
引用当前模块中的foo
。super::foo
引用父模块中的foo
。
作为绝对路径解析:
crate::foo
引用当前 crate 根目录中的foo
,bar::foo
指的是 bar crate 中的foo
。
文件系统层次结构
模块内容可以省略:
1mod garden;
模块 garden
内容位于:
src/garden.rs
(现代 Rust 2018 风格)src/garden/mod.rs
(较旧的 Rust 2015 风格)
garden::vegetables
同样,可以在以下位置找到一个模块:
src/garden/vegetables.rs
(现代 Rust 2018 风格)src/garden/vegetables/mod.rs
(较旧的 Rust 2015 风格)
根 crate
在:
src/lib.rs
(对于库 crate)src/main.rs
(对于二进制 crate)