Tour Of Rust

第一章 - Rust 基础

原文: https://tourofrust.com/chapter_1_en.html

变量

用 let 定义变量

rust 在 99% 的情况都能推断出变量类型, 如果它不能推断出变量类型, 就需要我们手动声明变量类型

可以对一个变量重复赋值多次, 这称为变量遮蔽, 变量的类型也是可以被改变的

rust 的变量风格为下划线全小写风格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main() {
// rust infers the type of x
let x = 13;
println!("{}", x);

// rust can also be explicit about the type
let x: f64 = 3.14159;
println!("{}", x);

// rust can also declare and initialize later, but this is rarely done
let x;
x = 0;
println!("{}", x);
}

变量按照其是否可改变分为可改变和不可改变两种, 需要用 mut 关键字来声明一个可改变变量

1
2
3
4
5
6
fn main() {
let mut x = 42;
println!("{}", x);
x = 13;
println!("{}", x);
}

rust 支持多种类型的变量, 有:

bool
u8 u16 u32 u64 u128
i8 i16 i32 i64 i128
usize isize — 用来表达下标或内存中数据的大小
f32 f64
tuple — 形如(value, value, …), 栈上个数固定的一组值
arrays — 形如[value, value, …], 一组相似的元素, 个数在编译期确定
slices — 一组相似的元素, 个数在运行期确定
str (string slice) — 文本, 长度在运行期确定

对于数值型字面量, 可以附带其具体类型, 如13u32, 2u8

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let x = 12; // by default this is i32
let a = 12u8;
let b = 4.3; // by default this is f64
let c = 4.3f32;
let bv = true;
let t = (13, false);
let sentence = "hello world!";
println!(
"{} {} {} {} {} {} {} {}",
x, a, b, c, bv, t.0, t.1, sentence
);
}

rust 强调数值类型的明确性,使用者不可随意混用u8u32等类型,除非使用as关键字对数值类型进行转换

1
2
3
4
5
6
7
8
9
fn main() {
let a = 13u8;
let b = 7u32;
let c = a as u32 + b;
println!("{}", c);

let t = true;
println!("{}", t as u8);
}

常量

可以用 const 关键字声明常量,并且需要指定明确的类型

常量的命名约定是全大写下划线分隔

1
2
3
4
5
6
7
8
const PI: f32 = 3.14159;

fn main() {
println!(
"To make an apple {} from scratch, you must first create a universe.",
PI
);
}

数组

数组存储固定个相同类型的元素,数组的类型形如[T;N],T为元素类型,N为元素个数

数组的下标从0开始,为usize类型

1
2
3
4
5
fn main() {
let nums: [i32; 3] = [1, 2, 3];
println!("{:?}", nums);
println!("{}", nums[1]);
}

函数

一个函数可以接收零个或多个参数(如下面的 add),如果函数返回一个表达式,可以省略 return 及末尾的分号不写(如下面的subtract

函数名的命名约定是全小写下划线分隔

提示,定义函数声明的形参英文叫做parameters,调用函数传递的实参英文叫做arguments

1
2
3
4
5
6
7
8
9
10
11
12
fn add(x: i32, y: i32) -> i32 {
return x + y;
}

fn subtract(x: i32, y: i32) -> i32 {
x - y
}

fn main() {
println!("42 + 13 = {}", add(42, 13));
println!("42 - 13 = {}", subtract(42, 13));
}

函数可以以元组的形式返回多个返回值

而元组的元素可以用下标访问

rust 还支持多种解构语法,允许我们用各种方法解出数据结构的一部分(如倒数第二行)

1
2
3
4
5
6
7
8
9
10
11
12
13
fn swap(x: i32, y: i32) -> (i32, i32) {
return (y, x);
}

fn main() {
// return a tuple of return values
let result = swap(123, 321);
println!("{} {}", result.0, result.1);

// destructure the tuple into two variables names
let (a, b) = swap(result.0, result.1);
println!("{} {}", a, b);
}

如果没有声明函数的返回类型,它将返回一个空元组(),也称单元(unit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn make_nothing() -> () {
return ();
}

// the return type is implied as ()
fn make_nothing2() {
// this function will return () if nothing is specified to return
}

fn main() {
let a = make_nothing();
let b = make_nothing2();

// Printing a debug string for a and b
// Because it's hard to print nothingness
println!("The value of a: {:?}", a);
println!("The value of b: {:?}", b);
}

第二章 - 条件与循环

原文: https://tourofrust.com/chapter_2_en.html

if/else

C类似,rust 的条件和逻辑运算符有 ==, !=, <, >, <=, >=, !, ||, &&

1
2
3
4
5
6
7
8
9
10
fn main() {
let x = 42;
if x < 42 {
println!("less than 42");
} else if x == 42 {
println!("is 42");
} else {
println!("greater than 42");
}
}

循环

在 rust 中,使用 loop 关键字开始一个无限循环,而 break 关键字让你可以适时跳出

1
2
3
4
5
6
7
8
9
10
fn main() {
let mut x = 0;
loop {
x += 1;
if x == 42 {
break;
}
}
println!("{}", x);
}

loop 还可以用 break 来返回一个值

1
2
3
4
5
6
7
8
9
10
fn main() {
let mut x = 0;
let v = loop {
x += 1;
if x == 13 {
break "found the 13";
}
};
println!("from loop: {}", v);
}

while 关键字根据条件来决定是否循环

1
2
3
4
5
6
7
fn main() {
let mut x = 0;
while x != 42 {
x += 1;
}
println!("x is {}", x);
}

rust 的 for 能够迭代任何表达式返回的迭代器,同时,rust 也能很轻松地创建生成整数序列的迭代器

表达式 a..b 创建一个有序返回集合 [a,b) 中元素的迭代器

表达式 a..=b 创建一个有序返回集合 [a,b] 中元素的迭代器

1
2
3
4
5
6
7
8
9
fn main() {
for x in 0..5 {
println!("{}", x);
}

for x in 0..=5 {
println!("{}", x);
}
}

模式匹配

match 类似 switch, 但更为强大,它可以依次匹配一个值所有的可能性,然后选择匹配成功的那个路径执行

match穷尽的,一个值所有的可能性都要列出来

结合解构的模式匹配是目前为止 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
fn main() {
let x = 42;

match x {
0 => {
println!("found zero");
}
// we can match against multiple values
1 | 2 => {
println!("found 1 or 2!");
}
// we can match against ranges
3..=9 => {
println!("found a number 3 to 9 inclusively");
}
// we can bind the matched number to a variable
matched_num @ 10..=100 => {
println!("found {} number between 10 to 100!", matched_num);
}
// this is the default match that must exist if not all cases are handled
_ => {
println!("found something else!");
}
}
}

从代码块返回值

if, match, 函数和代码块都有它们返回值的方式

如果 if, match, 函数或代码块的最后一条语句没有结尾的分号,rust 就会把这条表达式作为代码块的返回值,这让我们可以很简洁地把代码块的值存入变量

于是,在 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
fn example() -> i32 {
let x = 42;
// Rust's ternary expression
let v = if x < 42 { -1 } else { 1 };
println!("from if: {}", v);

let food = "hamburger";
let result = match food {
"hotdog" => "is hotdog",
// notice the braces are optional when its just a single return expression
_ => "is not hotdog",
};
println!("identifying food: {}", result);

let v = {
// This scope block lets us get a result without polluting function scope
let a = 1;
let b = 2;
a + b
};
println!("from block: {}", v);

// The idiomatic way to return a value in rust from a function at the end
v + 4
}

fn main() {
println!("from function: {}", example());
}

第三章 - 复合数据类型

原文: https://tourofrust.com/chapter_3_en.html

结构

struct 关键字定义结构

1
2
3
4
5
6
7
8
struct SeaCreature {
// String is a struct
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}

方法调用

不同于普通函数,方法是某种数据类型特有的函数

方法分为两种:

  • 静态方法 (static methods) — 静态方法属类型所有,用类型名::方法名()调用
  • 实例方法 (instance methods) — 实例方法属类型的实例所有,用变量名.方法名()调用

不难看出,以下例子 from 方法属 String 类型的静态方法,而 len 则属 String 类型的实例 s 的实例方法

1
2
3
4
5
6
fn main() {
// Using a static method to create an instance of String
let s = String::from("Hello world!");
// Using a method on the instance
println!("{} is {} characters long.", s, s.len());
}

内存

rust 程序有三个内存区域用来存放数据:

  • 数据内存 — 存放静态的(程序整个生命周期可用的)、大小固定的数据,例如程序中的文本数据”Hello World!”, 这段数据可以只存在一个地方,每次使用都从这个地方读取,编译器对这个区域做了很多优化,使得该区域内的数据访问是非常高效的(数据位置已知和大小固定)
  • 栈内存 — 存放函数的局部变量,这些变量的地址在函数调用期间是不会改变的,经过编译器优化,栈内存的数据访问也非常高效
  • 堆内存 — 存放程序运行期间动态创建的数据,该区域的数据可能随时添加,移动,移除,改变大小,正由于动态的特性,该区域的数据访问通常相对较慢一些,但堆内存支撑着内存的灵活使用,数据添加到该区域我们称为分配,数据从该区域移除我们称为释放

在内存创建数据

可以实例化一个结构使得结构的字段按照声明的布局排布于内存

实例化结构需要提供所有字段值

1
StructName { ... }

以下例子中:

  • 引号中的文本存放于数据内存区域
  • String 结构表示一个可改变的字符串
  • 结构实例 ferrissarah 存于栈内存中
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
struct SeaCreature {
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}

fn main() {
// SeaCreature's data is on stack
let ferris = SeaCreature {
// String struct is also on stack,
// but holds a reference to data on heap
animal_type: String::from("crab"),
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("claw"),
};

let sarah = SeaCreature {
animal_type: String::from("octopus"),
name: String::from("Sarah"),
arms: 8,
legs: 0,
weapon: String::from("brain"),
};

println!(
"{} is a {}. They have {} arms, {} legs, and a {} weapon",
ferris.name, ferris.animal_type, ferris.arms, ferris.legs, ferris.weapon
);
println!(
"{} is a {}. They have {} arms, and {} legs. They have no weapon..",
sarah.name, sarah.animal_type, sarah.arms, sarah.legs
);
}

元组型结构

为了便于使用,可以创建使用起来像元组的结构

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

fn main() {
// This is still a struct on a stack
let loc = Location(42, 32);
println!("{}, {}", loc.0, loc.1);
}

单元型结构

结构不一定要有任何字段

单元(unit)是空元组的别称,这种类型(无任何字段)的结构也叫做单元结构

单元结构并不常用

1
2
3
4
5
struct Marker;

fn main() {
let _m = Marker;
}

枚举

enum 关键字可以创建只能取枚举元素(tag)为值的类型(枚举类型)

结合 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
25
26
27
28
29
30
31
32
33
#![allow(dead_code)] // this line prevents compiler warnings

enum Species {
Crab,
Octopus,
Fish,
Clam
}

struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: String,
}

fn main() {
let ferris = SeaCreature {
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("claw"),
};

match ferris.species {
Species::Crab => println!("{} is a crab",ferris.name),
Species::Octopus => println!("{} is a octopus",ferris.name),
Species::Fish => println!("{} is a fish",ferris.name),
Species::Clam => println!("{} is a clam",ferris.name),
}
}

附带数据的枚举

可以为枚举的元素声明一个或多个数据类型,使枚举类型表现得如同 C 中的 union 类型

把一个枚举元素作为 match 的可能性匹配时,可为枚举元素每个数据绑定一个变量名

enum 的内存布局:

  • 枚举变量占用的内存大小为最大的元素的大小
  • 在元素数据类型之外,每个枚举变量还有一个数值表示它代表哪个枚举元素(tag)

其他细节:

  • rust 的 enum 又被称为带标签的联合 tagged union
  • 人们说 rust 有代数类型 就是说 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#![allow(dead_code)] // this line prevents compiler warnings

enum Species { Crab, Octopus, Fish, Clam }
enum PoisonType { Acidic, Painful, Lethal }
enum Size { Big, Small }
enum Weapon {
Claw(i32, Size),
Poison(PoisonType),
None
}

struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: Weapon,
}

fn main() {
// SeaCreature's data is on stack
let ferris = SeaCreature {
// String struct is also on stack,
// but holds a reference to data on heap
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: Weapon::Claw(2, Size::Small),
};

match ferris.species {
Species::Crab => {
match ferris.weapon {
Weapon::Claw(num_claws,size) => {
let size_description = match size {
Size::Big => "big",
Size::Small => "small"
};
println!("ferris is a crab with {} {} claws", num_claws, size_description)
},
_ => println!("ferris is a crab with some other weapon")
}
},
_ => println!("ferris is some other animal"),
}
}

第四章 - 泛型

https://tourofrust.com/chapter_4_en.html

泛型是 rust 中重要的概念,可以用泛型表示可为空(nullable)类型,处理错误,创建容器类型,等等…,其用途非常广泛

认识泛型

泛型让我们部分地定义结构或枚举,编译器再根据这些部分定义的的用法在编译期创建完整的类型定义(最终定义)

一般,rust 根据初始化语句就能推出最终定义,特殊情况下我们可以用 ::<T> 操作符显式帮它补全(此操作符又称 turbofish)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// A partially defined struct type
struct BagOfHolding<T> {
item: T,
}

fn main() {
// Note: by using generic types here, we create compile-time created types.
// Turbofish lets us be explicit.
let i32_bag = BagOfHolding::<i32> { item: 42 };
let bool_bag = BagOfHolding::<bool> { item: true };

// Rust can infer types for generics too!
let float_bag = BagOfHolding { item: 3.14 };

// Note: never put a bag of holding in a bag of holding in real life
let bag_in_bag = BagOfHolding {
item: BagOfHolding { item: "boom!" },
};

println!(
"{} {} {} {}",
i32_bag.item, bool_bag.item, float_bag.item, bag_in_bag.item.item
);
}

表示‘空’

rust 没有 null,但它并没有忽视“表示‘空’”的重要性, 考虑如下表示方法:

1
2
3
4
5
6
7
8
9
enum Item {
Inventory(String),
// None represents the absence of an item
None,
}

struct BagOfHolding {
item: Item,
}

由于 rust 没有 null,该方法很常见,泛型让它变得更通用

Option

Option 是 rust 的内建泛型枚举类型,能够替代 null 表示空值

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

此枚举非常常用,可以随时用它的变种 Some, 或 None 创建它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// A partially defined struct type
struct BagOfHolding<T> {
// Our parameter type T can be handed to others
item: Option<T>,
}

fn main() {
// Note: A bag for i32, holding nothing! We have to specify the type
// because otherwise Rust would not know what type of bag it is.
let i32_bag = BagOfHolding::<i32> { item: None };

if i32_bag.item.is_none() {
println!("there's nothing in the bag!")
} else {
println!("there's something in the bag!")
}

let i32_bag = BagOfHolding::<i32> { item: Some(42) };

if i32_bag.item.is_some() {
println!("there's something in the bag!")
} else {
println!("there's nothing in the bag!")
}

// match lets us deconstruct Option elegantly and ensure we handle all cases!
match i32_bag.item {
Some(v) => println!("found {} in bag!", v),
None => println!("found nothing"),
}
}

Result

内建枚举 Result 允许我们返回一个可能“失败”的值,它也是 rust 惯用的错误处理手段

1
2
3
4
enum Result<T, E> {
Ok(T),
Err(E),
}

由于此枚举非常常用,可以随时用枚举变种 Ok, Err 来创建它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn do_something_that_might_fail(i:i32) -> Result<f32,String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("this is not the right number"))
}
}

fn main() {
let result = do_something_that_might_fail(12);

// match lets us deconstruct Result elegantly and ensure we handle all cases!
match result {
Ok(v) => println!("found {}", v),
Err(e) => println!("Error: {}",e),
}
}

main返回Result

main 函数可以不返回值,也可以返回一个 Result

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 do_something_that_might_fail(i: i32) -> Result<f32, String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("this is not the right number"))
}
}

// Main returns no value, but could return an error!
fn main() -> Result<(), String> {
let result = do_something_that_might_fail(12);

match result {
Ok(v) => println!("found {}", v),
Err(_e) => {
// handle this error gracefully

// return a new error from main that said what happened!
return Err(String::from("something went wrong in main!"));
}
}

// Notice we use a unit value inside a Result Ok
// to represent everything is fine
Ok(())
}

优雅错误处理

为方便 Result 的使用,语言设计者为 rust 设计了一个专门的运算符 ?,以下两个语句是等价的:

1
2
3
4
5
6
do_something_that_might_fail()?

match do_something_that_might_fail() {
Ok(v) => v,
Err(e) => return Err(e),
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("this is not the right number"))
}
}

fn main() -> Result<(), String> {
// Look at how much code we saved!
let v = do_something_that_might_fail(42)?;
println!("found {}", v);
Ok(())
}

Vector

vector 为不定数量元素的列表,在 rust 中,以结构 Vec 来表示

vec! 宏可以便捷地创建一个 vector

Veciter() 方法创建该 vector 的迭代器,方便迭代 vector 的元素

Vec 的内存布局:

  • Vec 是一个 struct, 其内部包含一个指向堆上定长列表的引用
  • 新创建的 vector 有个初始长度,在使用过程中会根据需要扩容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
// We can be explicit with type
let mut i32_vec = Vec::<i32>::new(); // turbofish <3
i32_vec.push(1);
i32_vec.push(2);
i32_vec.push(3);

// But look how clever Rust is about determining the type automatically
let mut float_vec = Vec::new();
float_vec.push(1.3);
float_vec.push(2.3);
float_vec.push(3.4);

// That's a beautiful macro!
let string_vec = vec![String::from("Hello"), String::from("World")];

for word in string_vec.iter() {
println!("{}", word);
}
}

第五章 - 数据的拥有和借用

https://tourofrust.com/chapter_5_en.html

rust 的内存管理机制有别于其他语言,下面逐一介绍编译器与此相关的校验和行为,这些规则存在的终极目标是使 rust 代码错误更少

数据的拥有

rust 编译器会在变量的整个生命周期校验它占用的内存资源。实例化一个类型,并绑定给变量,变量即成为内存资源的拥有者

1
2
3
4
5
6
7
8
9
10
struct Foo {
x: i32,
}

fn main() {
// We instantiate structs and bind to variables
// to create memory resources
let foo = Foo { x: 42 };
// foo is the owner
}

基于作用域的资源管理

rust 在作用域(scope)的结尾析构和释放资源,析构和释放在 rust 中被称作丢弃(drop)

内存布局:

  • rust 没有垃圾收集器(GC)
  • 其机制与 C++ 中的“资源获取就是初始化(RAII)”相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Foo {
x: i32,
}

fn main() {
let foo_a = Foo { x: 42 };
let foo_b = Foo { x: 13 };

println!("{}", foo_a.x);

println!("{}", foo_b.x);
// foo_b is dropped here
// foo_a is dropped here
}

丢弃是分层的

当 struct 被丢弃时,首先丢弃 struct 本身,然后,它的每个字段再一个个被丢弃

内存布局:

  • 通过自动释放内存,rust 保证更少的内存泄漏
  • 内存资源只能被丢弃一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Bar {
x: i32,
}

struct Foo {
bar: Bar,
}

fn main() {
let foo = Foo { bar: Bar { x: 42 } };
println!("{}", foo.bar.x);
// foo is dropped first
// then foo.bar is dropped
}

所有权转移

当一个所有者作为函数的实参传递,所有权将转移给函数的形参

所有权转移后,原变量不可再次使用

内存布局:

  • 转移过程中,所有者的栈内存资源被拷贝到被调函数栈内存空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Foo {
x: i32,
}

fn do_something(f: Foo) {
println!("{}", f.x);
// f is dropped here
}

fn main() {
let foo = Foo { x: 42 };
// foo is moved to do_something
do_something(foo);
// foo can no longer be used
}

所有权返回

所有权也可以从函数返回

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

fn do_something() -> Foo {
Foo { x: 42 }
// ownership is moved out
}

fn main() {
let foo = do_something();
// foo becomes the owner
// foo is dropped because of end of function scope
}

借用使用权

引用允许我们向拥有者借用使用权,通过 & 运算符创建引用

同别的资源一样,引用本身也会自动被丢弃

1
2
3
4
5
6
7
8
9
10
11
struct Foo {
x: i32,
}

fn main() {
let foo = Foo { x: 42 };
let f = &foo;
println!("{}", f.x);
// f is dropped here
// foo is dropped here
}

借用可改变变量使用权

使用 &mut 运算符创建的引用可以借用可改变变量使用权

可改变使用权被借用期间,所有者不可转移所有权或修改变量值

内存布局:

  • rust 避免有两个及以上修改变量值的途径,这样便能杜绝竞争(race)的发生
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
struct Foo {
x: i32,
}

fn do_something(f: Foo) {
println!("{}", f.x);
// f is dropped here
}

fn main() {
let mut foo = Foo { x: 42 };
let f = &mut foo;

// FAILURE: do_something(foo) would fail because
// foo cannot be moved while mutably borrowed

// FAILURE: foo.x = 13; would fail here because
// foo is not modifiable while mutably borrowed

f.x = 13;
// f is dropped here because it's no longer used after this point

println!("{}", foo.x);

// this works now because all mutable references were dropped
foo.x = 7;

// move foo's ownership to a function
do_something(foo);
}

解引用

对于可变变量引用,可以用 * 运算符设置所引用变量的值

也可以使用 * 运算符获得所引用变量值的一份拷贝(前提是其值可拷贝)

1
2
3
4
5
6
7
8
fn main() {
let mut foo = 42;
let f = &mut foo;
let bar = *f; // get a copy of the owner's value
*f = 13; // set the reference's owner's value
println!("{}", bar);
println!("{}", foo);
}

传递借用数据

rust 的引用规则可总结如下:

  • rust 仅允许变量有一个可改变引用或多个不可改变引用
  • 引用的生命周期不会长于拥有者

传递引用到一个函数也不会有什么问题

内存布局:

  • 第一条规则避免了数据竞争
  • 第二条规则避免了引用不存在数据(C 中的“悬挂指针”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Foo {
x: i32,
}

fn do_something(f: &mut Foo) {
f.x += 1;
// mutable reference f is dropped here
}

fn main() {
let mut foo = Foo { x: 42 };
do_something(&mut foo);
// because all mutable references are dropped within
// the function do_something, we can create another.
do_something(&mut foo);
// foo is dropped here
}

从引用创建引用

可以从引用创建引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Foo {
x: i32,
}

fn do_something(a: &Foo) -> &i32 {
return &a.x;
}

fn main() {
let mut foo = Foo { x: 42 };
let x = &mut foo.x;
*x = 13;
// x is dropped here allow us to create a non-mutable reference
let y = do_something(&foo);
println!("{}", y);
// y is dropped here
// foo is dropped here
}

生命周期标识符

尽管没有在代码中显式声明,但 rust 编译器总是知道一个变量的生命周期的,以确保它的引用不会“活得”比它更久

函数体的生命周期可以明确,通过参数化(译注:可按泛型参数理解)函数的签名,可以区分哪些形参和返回值共享同样的生命周期

生命周期标识符(译注:可按泛型参数理解)以 ' 开头,如 'a, 'b, 'c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Foo {
x: i32,
}

// the parameter foo and return value share the same lifetime
fn do_something<'a>(foo: &'a Foo) -> &'a i32 {
return &foo.x;
// 译注:foo就不会在这里被丢弃了?
}

fn main() {
let mut foo = Foo { x: 42 };
let x = &mut foo.x;
*x = 13;
// x is dropped here, allowing us to create a non-mutable reference
let y = do_something(&foo);
println!("{}", y);
// y is dropped here
// foo is dropped here
}

多个生命周期标识符

一些rust 编译器不能通过区分函数签名所有元素的生命周期来解析的情形下,我们可以用生命周期标识符以明确

译注:原文:Lifetime specifiers allow us to be explicit with certain scenarios the compiler cannot resolve itself by distinguishing all of a function signature component’s lifetimes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Foo {
x: i32,
}

// foo_b and the return value share the same lifetime
// foo_a has an unrelated lifetime
fn do_something<'a, 'b>(foo_a: &'a Foo, foo_b: &'b Foo) -> &'b i32 {
println!("{}", foo_a.x);
println!("{}", foo_b.x);
return &foo_b.x;
}

fn main() {
let foo_a = Foo { x: 42 };
let foo_b = Foo { x: 12 };
let x = do_something(&foo_a, &foo_b);
// foo_a is dropped here because only foo_b's lifetime exist beyond here
println!("{}", x);
// x is dropped here
// foo_b is dropped here
}

译注:以下搜集了 lifetime 的一些资料

https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/lifetimes.html
https://blog.logrocket.com/understanding-lifetimes-in-rust/

静态生命周期

静态变量是在编译期创建的内存资源, 它的生命周期贯穿进程始终, 必须显式声明一个静态变量

静态生命周期资源是一直持续到进程结束的内存资源。透过该定义, 我们知道, 静态生命周期资源可以在运行时创建

静态生命周期资源有一个特殊标识符 'static,该类型资源永远不会被丢弃

静态生命周期资源引用的所有资源必须也是静态的

内存细节:

  • 原则上说,修改静态变量具有危险性,因为静态变量全局可访问,这可能引起竞争
  • rust 支持 unsafe { ... } 代码块,可以在里面执行一些编译器无法保证安全的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static PI: f64 = 3.1415;

fn main() {
// static variables can also be scoped to a function
static mut SECRET: &'static str = "swordfish";

// string literals have a 'static lifetime
let msg: &'static str = "Hello World!";
let p: &'static f64 = &PI;
println!("{} {}", msg, p);

// You can break some rules, but you must be explicit
unsafe {
// we can set SECRET to a string literal because it is also `static
SECRET = "abracadabra";
println!("{}", SECRET);
}
}

数据类型的生命周期

与函数一样,数据类型也可以被成员变量的生命周期标识符参数化

rust 会检验包含引用的 struct 不会比被引用的所有者活得更久

因此,不会有结构包含指向“虚无”的引用

1
2
3
4
5
6
7
8
9
10
11
struct Foo<'a> {
i:&'a i32
}

fn main() {
let x = 42;
let foo = Foo {
i: &x
};
println!("{}",foo.i);
}

第六章 - 文本处理

原文:https://tourofrust.com/chapter_6_en.html

在本章,我们将深入讨论 rust 中的文本。你将看到,rust 有许多工具帮助管理国际化、字节表示等问题

字符串

rust 中的字符串永远以 Unicode 编码, 且类型总是 &'static str

  • & 表示该变量指向内存中的一块区域,是 & 而不是 &mut 意味着编译器不允许我们修改其内容
  • 'static 表示字符串数据在进程运行期间都是有效的
  • str 表示该变量指向一段字节序列,它一定是有效的 utf-8

内存布局:

  • rust 编译器一般会将字符串放在程序数据段中
1
2
3
4
fn main() {
let a: &'static str = "hi 🦀";
println!("{} {}", a, a.len());
}

什么是utf-8

(略)

可访问原文查看:https://tourofrust.com/60_en.html

转义字符

rust 支持C风格语言常见的转义字符,如:

  • \n - 换行符
  • \r - 回车符
  • \t - 制表符
  • \\ - 反斜线
  • \0 - 空字符
  • \' - 单引号

完整列表: https://doc.rust-lang.org/reference/tokens.html

1
2
3
4
fn main() {
let a: &'static str = "Ferris says:\t\"hello\"";
println!("{}",a);
}

多行字符串

rust 的字符串默认就是多行的,如果不想要行尾的换行符,就在行尾添加一个 \

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let haiku: &'static str = "
I write, erase, rewrite
Erase again, and then
A poppy blooms.
- Katsushika Hokusai";
println!("{}", haiku);


println!("hello \
world") // notice that the spacing before w is ignored
}

原始字符串

原始字符串以 r#" 开始,"# 结束,编译器将不加修改地保留中间所有字符

1
2
3
4
5
6
7
8
fn main() {
let a: &'static str = r#"
<div class="advice">
Raw strings are useful for some situations.
</div>
"#;
println!("{}", a);
}

从文件加载字符串

可以使用 include_str! 宏从文件加载字符串

1
let hello_html = include_str!("hello.html");

字符串切片

字符串切片是对内存中一段有效的 utf-8 字节序列的引用

&str 常见的方法有:

  • len — 返回字符串占用的字节个数
  • starts_with, ends_with — 判断字符串是否以特定的序列开始/结尾
  • is_empty — 判断字符串是否为空
  • find — 查找字符串,返回 Option<usize>,指代待查字符串所在的第一个位置
1
2
3
4
5
6
7
8
9
fn main() {
let a = "hi 🦀";
println!("{}", a.len());
let first_word = &a[0..2];
let second_word = &a[3..7];
// let half_crab = &a[3..5]; FAILS
// Rust does not accept slices of invalid unicode characters
println!("{} {}", first_word, second_word);
}

char向量

为了方便操纵 Unicode 文本,rust 提供一种从 utf-8 字节中提取 char 向量形式的序列的方法

char 类型的长度为 4 字节(方便迭代到单个字符)

1
2
3
4
5
6
7
fn main() {
// collect the characters as a vector of char
let chars = "hi 🦀".chars().collect::<Vec<char>>();
println!("{}", chars.len()); // should be 4
// since chars are 4 bytes we can convert to u32
println!("{}", chars[3] as u32);
}

String

String 是一个 struct, 它引用堆内存中一个 utf-8 字节序列

正由于它的内容存放在堆中,我们能够实现无法应用于字符串字面量的修改、扩充等操作

常用的方法有:

  • push_str — 在字符串末尾追加 utf-8 字节序列
  • replace — 用其他 utf-8 字节序列替换
  • to_lowercase / to_uppercase — 大小写转换
  • trim — 消除空格

String 被丢弃时,它占用的堆内存也被丢弃

String 有一个 + 操作符,它将 &str 追加给自身然后返回自身,不过它可能没想象中那么好用

1
2
3
4
5
6
fn main() {
let mut helloworld = String::from("hello");
helloworld.push_str(" world");
helloworld = helloworld + "!";
println!("{}", helloworld);
}

函数的文本参数

文本字面量和 String 一般都是以字符串切片形式传给函数参数的,因为多数情况,你并不一定要转移所有权

1
2
3
4
5
6
7
8
9
10
fn say_it_loud(msg:&str){
println!("{}!!!",msg.to_string().to_uppercase());
}

fn main() {
// say_it_loud can borrow &'static str as a &str
say_it_loud("hello");
// say_it_loud can also borrow String as a &str
say_it_loud(&String::from("goodbye"));
}

构造字符串

concatjoin 是两种简单但实用的构造字符串的方法

1
2
3
4
5
6
fn main() {
let helloworld = ["hello", " ", "world", "!"].concat();
let abc = ["a", "b", "c"].join(",");
println!("{}", helloworld);
println!("{}",abc);
}

格式化字符串

format! 宏接收带有占位符的字符串,然后创建新字符串,占位符定义了在哪里和如何格式化所需的值(例如: {}

它接收的字符串格式和 println! 一致

该宏功能非常庞大,详细的文档可以跳转阅读:https://doc.rust-lang.org/std/fmt/

1
2
3
4
5
fn main() {
let a = 42;
let f = format!("secret to life: {}",a);
println!("{}",f);
}

转换为字符串

许多其他类型都可以调用 to_string 转换为字符串

泛型函数 parse 也可以将字符串转换为其他类型

1
2
3
4
5
6
7
fn main() -> Result<(), std::num::ParseIntError> {
let a = 42;
let a_string = a.to_string();
let b = a_string.parse::<i32>()?;
println!("{} {}", a, b);
Ok(())
}

第七章 - 面向对象编程

原文:https://tourofrust.com/chapter_7_en.html

本章,介绍 rust 面向对象编程范式

什么是 OOP

(略)

原文:https://tourofrust.com/74_en.html

Rust 不是 OOP

rust 缺少继承,因此不满足 OOP 的所有特性

  • struct 不能从父 struct 继承成员变量
  • struct 不能从父 struct 继承方法

方法的封装

struct 添加方法时,第一个参数必须是 struct 实例的引用

rust 使用:

  • &self - 代表实例的不可变引用
  • &mut self - 代表实例的可变引用

方法应该定义在以 impl 关键字标注的实现块中:

1
2
3
4
5
6
impl MyStruct { 
...
fn foo(&self) {
...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct SeaCreature {
noise: String,
}

impl SeaCreature {
fn get_sound(&self) -> &str {
&self.noise
}
}

fn main() {
let creature = SeaCreature {
noise: String::from("blub"),
};
println!("{}", creature.get_sound());
}

可选暴露范围

rust 可以隐藏对象的内部实现,字段和方法默认对所属模块可见

pub 关键字可以向模块外暴露字段和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct SeaCreature {
pub name: String,
noise: String,
}

impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}

fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
println!("{}", creature.get_sound());
}

用 trait 实现的多态

rust 的多态是以 trait 实现的,trait 让我们可以为一个 struct 类型关联一组方法

首先,在 trait 中定义方法签名:

1
2
3
4
trait MyTrait {
fn foo(&self);
...
}

如果一个 struct 实现了某 trait, 我们就可以通过 trait (例如:&dyn MyTrait)间接地与 struct 交互,而无需知道 struct 真正的类型是什么

struct 实现的 trait 方法定义在实现块中

1
2
3
4
5
6
impl MyTrait for MyStruct { 
fn foo(&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
struct SeaCreature {
pub name: String,
noise: String,
}

impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}

trait NoiseMaker {
fn make_noise(&self);
}

impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}

fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
creature.make_noise();
}

trait已实现方法

trait 可拥有已实现方法。这些方法不能直接访问 struct 的内部字段,但它们可以在不同的 trait 实现中共享一些行为。

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
struct SeaCreature {
pub name: String,
noise: String,
}

impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}

trait NoiseMaker {
fn make_noise(&self);

fn make_alot_of_noise(&self){
self.make_noise();
self.make_noise();
self.make_noise();
}
}

impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}

fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
creature.make_alot_of_noise();
}

trait继承

一个 trait 可以继承另外一个 trait 的方法

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
struct SeaCreature {
pub name: String,
noise: String,
}

impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}

trait NoiseMaker {
fn make_noise(&self);
}

trait LoudNoiseMaker: NoiseMaker {
fn make_alot_of_noise(&self) {
self.make_noise();
self.make_noise();
self.make_noise();
}
}

impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}

impl LoudNoiseMaker for SeaCreature {}

fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
creature.make_alot_of_noise();
}

静态绑定与动态绑定

方法有两种执行路线:

静态绑定 - 当对象类型已知时,我们也了解将要调用的方法
动态绑定 - 当对象类型未知时,我们必须寻找一种手段调用到正确的方法

Trait 类型 &dyn MyTrait 让我们可以通过动态绑定间接与对象交互

对于动态绑定,rust 建议在 trait 类型前标注上 dyn 以提醒人们注意

内存细节:

  • 由于采用指针解析找到正确的方法,动态绑定比静态绑定略慢
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
struct SeaCreature {
pub name: String,
noise: String,
}

impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}

trait NoiseMaker {
fn make_noise(&self);
}

impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}

fn static_make_noise(creature: &SeaCreature) {
// we know the real type
creature.make_noise();
}

fn dynamic_make_noise(noise_maker: &dyn NoiseMaker) {
// we don't know the real type
noise_maker.make_noise();
}

fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
static_make_noise(&creature);
dynamic_make_noise(&creature);
}

Trait 对象

当我们传递对象实例给一个 &dyn MyTrait 类型形参时,我们实际传递的是一个 trait 对象。

trait 对象让我们可以间接地调用对象实例正确的方法。一个 trait 对象包含我们对象实例的指针和我们对象方法指针的列表

内存细节:

  • 在 C++ 中,方法指针列表对应 vtable

处理尺寸不确定数据

当我们尝试将 trait 对象放进另一个 struct 时,就引出一个有趣的问题,trait 混淆了原始 struct 同时也混淆了它的尺寸。在 rust 中,有两种处理 struct 中这种尺寸不确定数据的方法

  • 泛型 - 使用类型参数创造类型和尺寸都确定的函数和 struct
  • 间接引用 - 将实例放在堆内存,就产生了一层间接,我们只需要保存实例的指针,就不用关心它具体的类型和尺寸大小

除以上外,其实还有其他方法。

泛型函数

rust 中的泛型和 trait 关系紧密。我们在声明类型参数 T 的同时,可以限制哪些实际类型能够满足这个类型参数,限制的方法就是指定实际类型必须实现的 trait 列表

以下例子说明 T 的实际类型必须实现trait Foo

1
2
3
4
5
6
fn my_function<T>(foo: T)
where
T:Foo
{
...
}

通过使用泛型,我们在编译器创建了类型和尺寸均确定的函数,方便我们使用静态绑定,也可以将其存储为确定尺寸的值。

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
struct SeaCreature {
pub name: String,
noise: String,
}

impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}

trait NoiseMaker {
fn make_noise(&self);
}

impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}

fn generic_make_noise<T>(creature: &T)
where
T: NoiseMaker,
{
// we know the real type at compile-time
creature.make_noise();
}

fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
generic_make_noise(&creature);
}

泛型函数语法糖

rust 在使用 trait 声明类型限制时,有如下语法糖:

1
2
3
fn my_function(foo: impl Foo) {
...
}

它与下面的写法等价:

1
2
3
4
5
6
fn my_function<T>(foo: T)
where
T:Foo
{
...
}
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
struct SeaCreature {
pub name: String,
noise: String,
}

impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}

trait NoiseMaker {
fn make_noise(&self);
}

impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}

fn generic_make_noise(creature: &impl NoiseMaker)
{
// we know the real type at compile-time
creature.make_noise();
}

fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
generic_make_noise(&creature);
}

Box

Box 是一种数据结构,允许我们将数据从栈内存移到堆内存(译注:更准确地说,是在堆内存存储数据)

Box,也称为智能指针,是一个包含指向我们堆内存数据指针的 struct

由于 Box 是一个尺寸确定的 struct (它只包含一个指针),它常常用在尺寸确定的 struct 里,作为间接存储某东西的方法。

可以在任何地方看到 Box 的身影

1
Box::new(Foo { ... })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct SeaCreature {
pub name: String,
noise: String,
}

impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}

trait NoiseMaker {
fn make_noise(&self);
}

impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}

struct Ocean {
animals: Vec<Box<dyn NoiseMaker>>,
}

fn main() {
let ferris = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
let sarah = SeaCreature {
name: String::from("Sarah"),
noise: String::from("swish"),
};
let ocean = Ocean {
animals: vec![Box::new(ferris), Box::new(sarah)],
};
for a in ocean.animals.iter() {
a.make_noise();
}
}

再谈泛型结构

也可以为泛型 struct 添加由 trait 列表表示的限制

1
2
3
4
5
6
7
struct MyStruct<T>
where
T: MyTrait
{
foo: T
...
}

在泛型 struct 的实现块,也能看到类型参数

1
2
3
impl<T> MyStruct<T> {
...
}

第八章 - 智能指针

原文:https://tourofrust.com/chapter_8_en.html

在本章,我们将揭开智能指针神秘的面纱

再谈引用

引用实质上只是反映内存一段字节起始位置的数字,它的唯一用途就是代表某类型的一个实例所在的位置。

它与数字的不同点是 rust 会校验引用的生命周期不会比被它引用的内存更长,以防我们使用它时出错。

原始指针

引用可以被转换为更基础的类型 - 原始指针,它和一个数字差不多,并可以高自由度地被移动和拷贝。

rust 不对它指向内存的有效性做任何假定和保证。

有两种不同的原始指针:

  • *const T - 指向 T 类型不变数据的原始指针
  • *mut T - 指向 T 类型可变数据的原始指针

原始指针可以与数值类型(如usize)相互转换

原始指针可以在不安全代码块中访问数据

内存细节:

  • rust 的引用在用途上和 C 的指针是相似的,但在编译期,存储和传递它会有更多限制
  • rust 的原始指针很像是 C 指针,保存的是一个可以被拷贝和传递的数字,甚至可以转型为数值类型,并且做指针运算
1
2
3
4
5
fn main() {
let a = 42;
let memory_location = &a as *const i32 as usize;
println!("Data is here {}", memory_location);
}

解引用

访问/操作被引用(例如:&i32)指向的数据的过程称为解引用

采用引用去访问/操作数据的表现形式有两种:

  • 通过变量赋值访问被引用的数据
  • 访问被引用数据的字段或方法

rust 有一些强大的操作符供我们使用

*运算符

*运算符显式解引用一个引用

1
2
3
4
let a: i32 = 42;
let ref_ref_ref_a: &&&i32 = &&&a;
let ref_a: &i32 = **ref_ref_ref_a;
let b: i32 = *ref_a;

内存细节:

  • 由于 i32 是一个基本类型并且实现了 Copy trait,变量 a 在栈上的所有字节被拷贝到了变量 b 在栈上的字节
1
2
3
4
5
6
7
fn main() {
let a: i32 = 42;
let ref_ref_ref_a: &&&i32 = &&&a;
let ref_a: &i32 = **ref_ref_ref_a;
let b: i32 = *ref_a;
println!("{}", b)
}

.运算符

点运算符用于访问引用的字段或方法, 它工作得更聪明一些:

1
2
3
let f = Foo { value: 42 };
let ref_ref_ref_f = &&&f;
println!("{}", ref_ref_ref_f.value);

可以看到,我们并不需要在 ref_ref_ref_f 前面添加三个 *, 这是由于 . 运算符可以自动解引用一连串引用,最后一行自动被编译器转化为:

1
println!("{}", (***ref_ref_ref_f).value);
1
2
3
4
5
6
7
8
9
struct Foo {
value: i32
}

fn main() {
let f = Foo { value: 42 };
let ref_ref_ref_f = &&&f;
println!("{}", ref_ref_ref_f.value);
}

智能指针

除了使用 & 运算符创建一个有类型数据的引用外,rust 让我们可以创建称为智能指针的类似引用的 struct

我们可以在更高层次认为引用就是让我们可以访问另一个类型的类型,智能指针在行为上和普通引用有所不同。不同地方在于,智能指针工作的逻辑是人赋予的,是作为程序编写的。

一般,智能指针都会实现 Deref, DerefMut, 和Drop trait, 去描述当指针被 .* 解引用时应该产生的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use std::ops::Deref;
struct TattleTell<T> {
value: T,
}
impl<T> Deref for TattleTell<T> {
type Target = T;
fn deref(&self) -> &T {
println!("{} was used!", std::any::type_name::<T>());
&self.value
}
}
fn main() {
let foo = TattleTell {
value: "secret message",
};
// dereference occurs here immediately
// after foo is auto-referenced for the
// function `len`
println!("{}", foo.len());
}

不安全代码实现

智能指针经常使用不安全代码,它们是 rust 中与最底层内存交互的工具。

然而,不安全代码是什么?不安全代码和常规代码大体上差不多,只是多出了一些 rust 编译器无法保证安全的能力。

一个主要能力是解引用原始指针,这样的解引用需要在不安全代码块中使用

智能指针广泛地使用原始指针解引用,但它们能很好地保证安全性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn main() {
let a: [u8; 4] = [86, 14, 73, 64];
// this is a raw pointer. Getting the memory address
// of something as a number is totally safe
let pointer_a = &a as *const u8 as usize;
println!("Data memory location: {}", pointer_a);
// Turning our number into a raw pointer to a f32 is
// also safe to do.
let pointer_b = pointer_a as *const f32;
let b = unsafe {
// This is unsafe because we are telling the compiler
// to assume our pointer is a valid f32 and
// dereference it's value into the variable b.
// Rust has no way to verify this assumption is true.
*pointer_b
};
println!("I swear this is a pie! {}", b);
}

Vec,String深入

考虑我们已经见过的两个智能指针 Vec<T>String

Vec<T> 是一个拥有一些字节区域的智能指针,rust 编译器并不清楚这些区域内有什么,智能指针负责阐释它们并从中获取元素,跟踪这段区域内数据结构的开始点和结束点,最后提供一个方便的接口(如my_vec[3])从原始指针解引用到具体的数据类型。

同样地,String 也管理一段内存区域,并在程序上限制写往区域的数据永远是合法的 utf-8 序列,并且能够将这段内存解引用为 &str 类型

这两种智能指针都使用了原始指针去实现它们的功能

内存细节:

  • rust 有类似 C 的 malloc 机制,allocLayout 让你能掌控希望管理的内存区域
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
use std::alloc::{alloc, Layout};
use std::ops::Deref;

struct Pie {
secret_recipe: usize,
}

impl Pie {
fn new() -> Self {
// let's ask for 4 bytes
let layout = Layout::from_size_align(4, 1).unwrap();

unsafe {
// allocate and save the memory location as a number
let ptr = alloc(layout) as *mut u8;
// use pointer math and write a few
// u8 values to memory
ptr.write(86);
ptr.add(1).write(14);
ptr.add(2).write(73);
ptr.add(3).write(64);

Pie { secret_recipe: ptr as usize }
}
}
}
impl Deref for Pie {
type Target = f32;
fn deref(&self) -> &f32 {
// interpret secret_recipe pointer as a f32 raw pointer
let pointer = self.secret_recipe as *const f32;
// dereference it into a return value &f32
unsafe { &*pointer }
}
}
fn main() {
let p = Pie::new();
// "make a pie" by dereferencing our
// Pie struct smart pointer
println!("{:?}", *p);
}

堆上分配的内存

Box 是一个让我们能够将数据从栈内存移动到堆内存的智能指针

解引用它让我们可以像访问原始类型一样访问在堆上分配的数据

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

impl Pie {
fn eat(&self) {
println!("tastes better on the heap!")
}
}

fn main() {
let heap_pie = Box::new(Pie);
heap_pie.eat();
}

再谈main返回Result

rust 有很多表示错误值的方法,但标准库有一个通用的 std::error::Error trait 来描述错误

借助 Box,我们可以定义 Box<dyn std::error::Error> 作为通用类型来返回错误,因为它让我们借助堆内存向上传递错误,并且在上层在不用知道具体类型的情况下处理错误

之前我们知道 main 函数可以返回一个错误,我们现在可以返回另一个错误类型,它几乎兼容任何会在我们程序中产生的错误,只要一个错误类型实现了 Error trait,就像下面这样

1
fn main() -> Result<(), Box<dyn std::error:Error>>
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
use core::fmt::Display;
use std::error::Error;

struct Pie;

#[derive(Debug)]
struct NotFreshError;

impl Display for NotFreshError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "This pie is not fresh!")
}
}

impl Error for NotFreshError {}

impl Pie {
fn eat(&self) -> Result<(), Box<dyn Error>> {
Err(Box::new(NotFreshError))
}
}

fn main() -> Result<(), Box<dyn Error>> {
let heap_pie = Box::new(Pie);
heap_pie.eat()?;
Ok(())
}

引用计数

Rc 也是一个将数据从栈内存移动到堆内存的智能指针,它让我们可以克隆另外一个 Rc 智能指针,并且都可以只读借用存放到堆内存上的数据

仅当最后一个智能指针被丢弃,堆上内存才会被回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::rc::Rc;

struct Pie;

impl Pie {
fn eat(&self) {
println!("tastes better on the heap!")
}
}

fn main() {
let heap_pie = Rc::new(Pie);
let heap_pie2 = heap_pie.clone();
let heap_pie3 = heap_pie2.clone();

heap_pie3.eat();
heap_pie2.eat();
heap_pie.eat();

// all reference count smart pointers are dropped now
// the heap data Pie finally deallocates
}

共享使用权

RefCell 是一个经常被智能指针拥有的容器类型,它保存数据,并允许被不可变或可变地借用,当你借用它内部数据使用权时,它在运行时应用如下 rust 内存安全规则防止借用被破坏

仅允许一个可变引用,或多个不可变引用,无法两者兼备

如果违法上述规则,RefCell 将会 panic

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
use std::cell::RefCell;

struct Pie {
slices: u8
}

impl Pie {
fn eat(&mut self) {
println!("tastes better on the heap!");
self.slices -= 1;
}
}

fn main() {
// RefCell validates memory safety at runtime
// notice: pie_cell is not mut!
let pie_cell = RefCell::new(Pie{slices:8});

{
// but we can borrow mutable references!
let mut mut_ref_pie = pie_cell.borrow_mut();
mut_ref_pie.eat();
mut_ref_pie.eat();

// mut_ref_pie is dropped at end of scope
}

// now we can borrow immutably once our mutable reference drops
let ref_pie = pie_cell.borrow();
println!("{} slices left",ref_pie.slices);
}

注意:上面 pie_cell 本身是不可变的,但我们可以向其借用 Pie 的可变使用权

在线程间共享

Mutex 是常常被智能指针拥有的一个容器类型,它保存数据,并允许被不可变或可变地借用。

它强制同时只有一个 CPU 线程能访问其中的数据,来防止借用被破坏,其他 CPU 线程在一个线程借用期间必须等待。

Mutex 是 rust 并发编程的一个基本类型

还有一个和 Rc 类似的特殊的智能指针 Arc,它和 Rc 不同的是会线程安全地维护引用计数,它通常被用来多次引用同一个 Mutex(译注:原文:. It’s often used to have many references to the same Mutex.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::sync::Mutex;

struct Pie;

impl Pie {
fn eat(&self) {
println!("only I eat the pie right now!");
}
}

fn main() {
let mutex_pie = Mutex::new(Pie);
// let's borrow a locked immutable reference of pie
// we have to unwrap the result of a lock
// because it might fail
let ref_pie = mutex_pie.lock().unwrap();
ref_pie.eat();
// locked reference drops here, and mutex protected value can be used by someone else
}

组合智能指针

智能指针看似是有穷的,不过它们可以组合成更强大的功能

Rc<Vec<Foo>> - 允许多个智能指针的克隆借用堆上同一个不变数据结构 FooVec

Rc<RefCell<Foo>> - 允许多个智能指针不可变/可变地借用同一个 Foo

Arc<Mutex<Foo>> - 允许多个智能指针 CPU 线程互斥地不可变/可变地短暂借用同一个 Foo

内存细节:

  • 从这些组合中可以发现一个主题,使用不可变数据类型(可能被多个智能指针拥有)去修改内部数据,这称为 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
use std::cell::RefCell;
use std::rc::Rc;

struct Pie {
slices: u8,
}

impl Pie {
fn eat_slice(&mut self, name: &str) {
println!("{} took a slice!", name);
self.slices -= 1;
}
}

struct SeaCreature {
name: String,
pie: Rc<RefCell<Pie>>,
}

impl SeaCreature {
fn eat(&self) {
// use smart pointer to pie for a mutable borrow
let mut p = self.pie.borrow_mut();
// take a bite!
p.eat_slice(&self.name);
}
}

fn main() {
let pie = Rc::new(RefCell::new(Pie { slices: 8 }));
// ferris and sarah are given clones of smart pointer to pie
let ferris = SeaCreature {
name: String::from("ferris"),
pie: pie.clone(),
};
let sarah = SeaCreature {
name: String::from("sarah"),
pie: pie.clone(),
};
ferris.eat();
sarah.eat();

let p = pie.borrow();
println!("{} slices left", p.slices);
}

第九章 - 项目组织与结构

原文:https://tourofrust.com/chapter_9_en.html

本章,我们讨论如何更好地组织和共享我们的代码

模块

每个 rust 程序和库都称为一个 crate

每个 crate 都由有层次的模块组成

每个 crate 都有一个根模块

模块可以包含全局变量、函数、结构、trait、甚至其他模块

在 rust 中,文件和模块层次不是一一对应的关系,我们必须在代码中手动构建我们的模块

编写程序

一个程序有一个位于 main.rs 文件的根模块

编写库

一个库有一个位于 lib.rs 文件的根模块

访问其他模块和crate

模块中的东西可以通过它的全模块路径来访问,如std::f64::consts::PI

一个更简单的方法是使用 use 关键字,我们用它声明在整个程序中想省去全路径使用的模块项,例如,声明 use std::f64::consts::PI,我们就可以在程序中直接使用 PI 访问了

std 是 rust 标准库的 crate,它包含许多有用的与操作系统交互的数据结构和函数

由社区创建的 crate 集合如下:https://crates.io.

1
2
3
4
5
6
use std::f64::consts::PI;

fn main() {
println!("Welcome to the playground!");
println!("I would love a slice of {}!", PI);
}

访问多个模块项

可在单个模块路径声明多个模块项: use std::f64::consts::{PI,TAU}

创建模块

我们通常认为,一个项目就是有组织的目录下的许多源文件,rust 让我们可以创建与文件结构紧密相关的模块

有两种声明模块的方式,例如,一个 foo 模块可用两种方式声明:

  • 一个命名为 foo.rs 的文件
  • 一个命名为 foo 的文件夹,其中有一个 mod.rs 文件

模块层次结构

模块可以依赖另一个模块,为了建立模块和模块之间的层次关系,你必须在父模块中写下:

mod foo;

以上声明会查找 foo.rs 或者 foo/mod.rs,然后会将其内容插入到这个作用域下名为 foo 的模块。

译注:这段我不太理解

内联模块

一个子模块可以直接内联进另一个模块的代码中

内联模块的一个常用场景是创建单元测试,我们创建仅仅当单元测试时才存在的内联模块

1
2
3
4
5
6
7
8
9
10
// This macro removes this inline module when Rust 
// is not in test mode.
#[cfg(test)]
mod tests {
// Notice that we don't immediately get access to the
// parent module. We must be explicit.
use super::*;

... tests go here ...
}

内部模块访问

use 路径中有许多关键字可以用来快速找到想要的模块项:

crate - 你 crate 的根模块
super - 你模块的父模块
self - 当前模块

导出

缺省情况下,模块的成员是不能在模块外访问的(包括其子模块也不能),我们必须使用 pub 关键字使模块的成员可访问

缺省情况下,crate 的成员也是不能在 crate 外部访问的,要使其成员可以访问,需要在 crate 的根模块(lib.rsmain.rs)将它们标记为 pub

结构的可见性

与函数一样,结构可以用 pub 关键字声明想要暴露给模块外的东西

1
2
3
4
5
6
7
8
9
// SeaCreature struct will be usable outside of our module
pub struct SeaCreature {
pub animal_type: String,
pub name: String,
pub arms: i32,
pub legs: i32,
// let's keep our weapon private
weapon: String,
}

Prelude

你可能想知道,我们在任何地方使用 Vec, Box, 却没有使用 use 导入它们,是怎么做到的?这是因为它们来自标准库中的 prelude 模块

在 rust 标准库中,任何导出为 std::prelude::* 的东西都可在 rust 任意地方使用,而不必导入,对于 VecBox 是这样,对于标准库中其他东西也是这样(如OptionCopy,等等)

你自己的Prelude

受标准库 prelude 启发,对于自己的库,通常也定义一个 prelude 模块作为一切的出发点,使用你模块的用户应该能从这里导入大部分常用的数据结构(例:use my_library::prelude::*),它并不会和标准库一样在使用你 crate 的程序或库中自动导入,但它可以作为一个约定俗成的规范,好帮助用户知道从哪里开始。

第十章 - 结束

原文:https://tourofrust.com/chapter_10_en.html

Tour Of Rust的内容到这里就结束了,可访问The Official Rust Programming Book深入了解更多相关内容