Table of Contents:

随书的代码
https://github.com/rust-lang/book/tree/main/src

mut 与隐藏的另一个区别是,当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字

1. 入门指南

1.1. 安装
1.2. Hello, World!
1.3. Hello, Cargo!

2. 写个猜数字游戏

3. 常见编程概念

3.1. 变量与可变性

我们可以定义一个与之前变量同名的新变量。Rustacean 们称之为第一个变量被第二个 隐藏(Shadowing) 了
mut 与隐藏的另一个区别是,当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字
转换类型的时候可以用变量隐藏

3.2. 数据类型

let a = [1, 2, 3, 4, 5];
let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5];

3.3. 函数

函数体由一系列的语句和一个可选的结尾表达式构成
Rust 是一门基于表达式(expression-based)的语言
语句(Statements)是执行一些操作但不返回值的指令。表达式(Expressions)计算并产生一个值。

表达式会计算出一个值,并且你将编写的大部分 Rust 代码是由表达式组成的。考虑一个数学运算,比如 5 + 6,这是一个表达式并计算出值 11。表达式可以是语句的一部分:在示例 3-1 中,语句 let y = 6; 中的 6 是一个表达式,它计算出的值是 6。函数调用是一个表达式。宏调用是一个表达式。用大括号创建的一个新的块作用域也是一个表达式。

表达式的结尾没有分号。如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值

函数的返回值等同于函数体最后一个表达式的值

3.4. 注释

3.5. 控制流

代码中的条件 必须bool
多个else if,只会执行第一个条件为真的代码块

在 let 语句中使用 if

let condition = true;
let number = if condition { 5 } else { 6 };

let中使用循环

fn main() {
    let mut counter = 0;

    let result = loop {  //result = 20
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}

for循环

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}

fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}

4. 认识所有权

所有权(系统)是 Rust 最为与众不同的特性,对语言的其他部分有着深刻含义。它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全

4.1. 什么是所有权?
所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时有规律地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。

跟踪哪部分代码正在使用堆上的哪些数据,最大限度的减少堆上的重复数据的数量,以及清理堆上不再使用的数据确保不会耗尽空间,这些问题正是所有权系统要处理的。一旦理解了所有权,你就不需要经常考虑栈和堆了,不过明白了所有权的主要目的就是为了管理堆数据,能够帮助解释为什么所有权要以这种方式工作。

所有权规则
1. Rust 中的每一个值都有一个 所有者owner)。
2. 值在任一时刻有且只有一个所有者。
3. 当所有者(变量)离开作用域,这个值将被丢弃。

    {
        let s = String::from("hello"); // 从此处起,s 是有效的

        // 使用 s
    }                                  // 此作用域已结束,
                                       // s 不再有效

这是一个将 String 需要的内存返回给分配器的很自然的位置:当 s 离开作用域的时候。当变量离开作用域,Rust 为我们调用一个特殊的函数。这个函数叫做 drop,在这里 String 的作者可以放置释放内存的代码。Rust 在结尾的 } 处自动调用 drop

移动语义

let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);    //s1已经被移动到s2了,不能再使用,其目的是防止double free

Rust 不允许自身或其任何部分实现了 Drop trait 的类型使用 Copy trait

所有权转移的情况
所有权其实是堆中内存的所有权
带有指针的数据会产生所有权转移,在以下情况会进行转移:
1. 将一个变量的值赋给另一个变量
2. 将一个变量传递给一个函数
3. 将一个变量作为函数的返回值

4.2. 引用与借用
引用的行为称为 借用(borrowing)

默认的引用是不可变的,如果需要可变引用,需要使用 &mut

可变引用有一个很大的限制:如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。这些尝试创建两个 s 的可变引用的代码会失败:

这一限制以一种非常小心谨慎的方式允许可变性,防止同一时间对同一数据存在多个可变引用。新 Rustacean 们经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。数据竞争data race)类似于竞态条件,它可由这三个行为造成:
- 两个或更多指针同时访问同一数据。
- 至少有一个指针被用来写入数据。
- 没有同步数据访问的机制。

一如既往,可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能 同时 拥有:

    let mut s = String::from("hello");

    {
        let r1 = &mut s;
    } // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用

    let r2 = &mut s;

引用的规则
- 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
- 引用必须总是有效的。

4.3. Slice 类型

slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一类引用,所以它没有所有权。
“字符串 slice” 的类型声明写作 &str
字符串字面值就是 slice
定义一个获取字符串 slice 而不是 String 引用的函数使得我们的 API 更加通用并且不会丢失任何功能

其他的slice还有数组的、其他集合的

5. 使用结构体组织相关联的数据

5.1. 结构体的定义和实例化
结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};
//使用字段初始化简写语法
fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}
//使用结构体更新语法从其他实例创建实例
let user2 = User {
    email: String::from("another@example.com"),
    ..user1        // ..user1 必须放在最后,其中user1中的String字段被移动到了user2
};

元组结构体

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

没有任何字段的类单元结构体
我们也可以定义一个没有任何字段的结构体!它们被称为 类单元结构体unit-like structs)因为它们类似于 (),即[“元组类型”]一节中提到的 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

5.2. 结构体示例程序

5.3. 方法语法

在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文),并且它们第一个参数总是 self,它代表调用该方法的结构体实例。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

关联函数
所有在 impl 块中定义的函数被称为 关联函数associated functions),因为它们与 impl 后面命名的类型相关。

6. 枚举和模式匹配

6.1. 枚举的定义

枚举给予你将一个值成为一个集合之一的方法,只能是其中之一

enum IpAddrKind {
    V4,
    V6,
}

struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

let home = IpAddr {
    kind: IpAddrKind::V4,
    address: String::from("127.0.0.1"),
};

let loopback = IpAddr {
    kind: IpAddrKind::V6,
    address: String::from("::1"),
};

// 另一种简单写法,我们直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。
// 用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据。结构体的成员则都只能是一样的数据类型
enum IpAddr {
    V4(String),
    V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));

Option 枚举和其相对于空值的优势
Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。
编程语言的设计经常要考虑包含哪些功能,但考虑排除哪些功能也很重要。Rust 并没有很多其他语言中有的空值功能。空值Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值

空值的问题在于当你尝试像一个非空值那样使用一个空值,会出现某种形式的错误。因为空和非空的属性无处不在,非常容易出现这类错误。

只要一个值不是 Option<T> 类型,你就 可以 安全的认定它的值不为空。这是 Rust 的一个经过深思熟虑的设计决策,来限制空值的泛滥以增加 Rust 代码的安全性。

6.2. match 控制流结构

每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值。
如果分支代码较短的话通常不使用大括号
分支后的逗号是可选的
匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值的。

匹配是穷尽的,分支必须覆盖了所有的可能性
我们必须将通配分支放在最后,因为模式是按顺序匹配的。如果我们在通配分支后添加其他分支,Rust 将会警告我们,因为此后的分支永远不会被匹配到。

6.3. if let 简洁控制流

let config_max = Some(3u8);
if let Some(max) = config_max {
    println!("The maximum is configured to be {}", max);
}

可以认为 if letmatch 的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。

let mut count = 0;
match coin {
    Coin::Quarter(state) => println!("State quarter from {:?}!", state),
    _ => count += 1,
}
// 等同于下面
let mut count = 0;
if let Coin::Quarter(state) = coin {
    println!("State quarter from {:?}!", state);
} else {
    count += 1;
}

7. 使用包、Crate 和模块管理不断增长的项目

Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能。这有时被称为 “模块系统(the module system)”,包括:

7.1. 包和 Crate

crate 有两种形式:二进制项和库。
大多数时间 Rustaceans 说的 crate 指的都是库,这与其他编程语言中 library 概念一致。
crate root 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块
包(package) 是提供一系列功能的一个或者多个 crate。
包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。

Cargo 遵循的一个约定:src/main.rs 就是一个与包同名的二进制 crate 的 crate 根。同样的,Cargo 知道如果包目录中包含 src/lib.rs,则包带有与其同名的库 crate,且 src/lib.rs 是 crate 根。

7.2. 定义模块来控制作用域与私有性

7.3. 引用模块项目的路径

Rust 中默认所有项(函数、方法、结构体、枚举、模块和常量)都是私有的。父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用他们父模块中的项。这是因为子模块封装并隐藏了他们的实现详情,但是子模块可以看到他们定义的上下文。

Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部实现细节。这样一来,你就知道可以更改内部代码的哪些部分而不会破坏外部代码。你还可以通过使用 pub 关键字来创建公共项,使子模块的内部部分暴露给上级模块。

7.4. 使用 use 关键字将路径引入作用域

使用 use 引入结构体、枚举和其他项时,习惯是指定它们的完整路径。

7.5. 将模块拆分成多个文件

8. 常见集合

8.1. 使用 Vector 储存列表

丢弃 vector 时也会丢弃其所有元素

8.2. 使用字符串储存 UTF-8 编码的文本

8.3. 使用 Hash Map 储存键值对

9. 错误处理

Rust 将错误分为两大类:可恢复的recoverable)和 不可恢复的unrecoverable)错误。对于一个可恢复的错误,比如文件未找到的错误,我们很可能只想向用户报告问题并重试操作。不可恢复的错误总是 bug 出现的征兆,比如试图访问一个超过数组末端的位置,因此我们要立即停止程序。

大多数语言并不区分这两种错误,并采用类似异常这样方式统一处理他们。Rust 没有异常。相反,它有 Result<T, E> 类型,用于处理可恢复的错误,还有 panic! 宏,在程序遇到不可恢复的错误时停止执行。

9.1. 用 panic! 处理不可恢复的错误

9.2. 用 Result 处理可恢复的错误

传播错误
当编写一个其实先会调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定该如何处理。这被称为 传播propagating)错误,这样能更好的控制代码调用,因为比起你代码所拥有的上下文,调用者可能拥有更多信息或逻辑来决定应该如何处理错误。

传播错误的简写:? 运算符

9.3. 要不要 panic!

10. 泛型、Trait 和生命周期

10.1. 泛型数据类型
10.2. Trait:定义共同行为
10.3. 生命周期确保引用有效
11. 编写自动化测试
11.1. 如何编写测试
11.2. 控制测试如何运行
11.3. 测试的组织结构
12. 一个 I/O 项目:构建命令行程序
12.1. 接受命令行参数
12.2. 读取文件
12.3. 重构以改进模块化与错误处理
12.4. 采用测试驱动开发完善库的功能
12.5. 处理环境变量
12.6. 将错误信息输出到标准错误而不是标准输出
13. Rust 中的函数式语言功能:迭代器与闭包
13.1. 闭包:可以捕获其环境的匿名函数
13.2. 使用迭代器处理元素序列
13.3. 改进之前的 I/O 项目
13.4. 性能比较:循环对迭代器
14. 更多关于 Cargo 和 Crates.io 的内容
14.1. 采用发布配置自定义构建
14.2. 将 crate 发布到 Crates.io
14.3. Cargo 工作空间
14.4. 使用 cargo install 从 Crates.io 安装二进制文件
14.5. Cargo 自定义扩展命令
15. 智能指针
15.1. 使用Box 指向堆上数据
15.2. 使用Deref Trait 将智能指针当作常规引用处理
15.3. 使用Drop Trait 运行清理代码
15.4. Rc 引用计数智能指针
15.5. RefCell 与内部可变性模式
15.6. 引用循环会导致内存泄漏
16. 无畏并发
16.1. 使用线程同时地运行代码
16.2. 使用消息传递在线程间通信
16.3. 共享状态并发
16.4. 使用Sync 与 Send Traits 的可扩展并发
17. Rust 的面向对象编程特性
17.1. 面向对象语言的特点
17.2. 为使用不同类型的值而设计的 trait 对象
17.3. 面向对象设计模式的实现
18. 模式与模式匹配
18.1. 所有可能会用到模式的位置
18.2. Refutability(可反驳性): 模式是否会匹配失效
18.3. 模式语法
19. 高级特征
19.1. 不安全的 Rust
19.2. 高级 trait
19.3. 高级类型
19.4. 高级函数与闭包
19.5. 宏
20. 最后的项目: 构建多线程 web server
20.1. 建立单线程 web server
20.2. 将单线程 server 变为多线程 server
20.3. 优雅停机与清理