Rust 学习笔记

本文参考自文档:Rust 程序设计语言 简体中文版,更多请看原始文档!

1. Hello world

在这里插入图片描述
在这里插入图片描述

2. Cargo(Rust 的构建系统和包管理器)

使用 Cargo 创建项目

在这里插入图片描述

Cargo 配置文件

在这里插入图片描述

Cargo 目录结构

在这里插入图片描述

构建并运行 Cargo 项目

在这里插入图片描述

发布(release)构建

在这里插入图片描述

Cargo 常用命令
  • cargo build:构建项目
  • cargo run:一步构建并运行项目
  • cargo check:在不生成二进制文件的情况下构建项目来检查错误(比 cargo run 快得多)

3. 【小项目】猜数游戏

原地址

准备一个新项目(工程目录)

在这里插入图片描述

使用 crate 来增加更多功能

在这里插入图片描述

Cargo.lock 文件确保构建是可重现的

在这里插入图片描述

更新 crate 到一个新版本

在这里插入图片描述

Code:猜数游戏
use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        println!("请输入一个数字");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to get the number!");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

4. Rust 基础

4.1 变量和可变性

在这里插入图片描述
在这里插入图片描述

常量

在这里插入图片描述

隐藏(Shadowing)

在这里插入图片描述

4.2 数据类型

Rust 必须指定数据类型

在这里插入图片描述

标量类型
  1. 整型
    在这里插入图片描述
    在这里插入图片描述

  2. 浮点型(整数除法为地板除)
    在这里插入图片描述

  3. 布尔型
    在这里插入图片描述

  4. 字符型(size为4个字节;字符单引号',字符串双引号"
    在这里插入图片描述

复合类型
  1. 元组
    在这里插入图片描述

  2. 数组
    在这里插入图片描述

4.3 函数

在这里插入图片描述

  1. 参数
    在这里插入图片描述

  2. 语句和表达式

    • 语句不返回值;而表达式会计算出一个值
    • 注意:表达式的结尾没有分号。如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值

在这里插入图片描述
在这里插入图片描述
3. 函数返回值
在这里插入图片描述

4.4 注释

  • //

4.5 控制流:if与循环

if 表达式
  1. 基础
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 在 let 语句中使用 if
    在这里插入图片描述
loop 循环
  1. 基础
    在这里插入图片描述
  2. 带标签的 loop 循环
    • 用于在嵌套循环中 break 或 continue 指定循环

在这里插入图片描述
3. 从 loop 循环返回值
在这里插入图片描述

while 循环

在这里插入图片描述

for 循环(遍历集合)

在这里插入图片描述

5. 所有权

5.1 什么是所有权

栈(Stack)与堆(Heap)

在这里插入图片描述

所有权规则

在这里插入图片描述

变量作用域

在这里插入图片描述

String 类型

在这里插入图片描述

内存与分配

在这里插入图片描述

变量与数据交互的方式(一):移动(move,直接转移)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

变量与数据交互的方式(二):克隆(clone,深复制)

在这里插入图片描述
在这里插入图片描述

只在栈上的数据:拷贝

在这里插入图片描述

所有权与函数
  • 向函数传递值可能会移动或者复制:对于实现 copy 的是复制,否则为转移

在这里插入图片描述

返回值与作用域

在这里插入图片描述

5.2 引用与借用

  • 在任意给定时间,下面情况只能存在一种:1、要么 只能有一个可变引用;2、要么 只能有多个不可变引用
  • 引用必须总是有效的(不能制造悬垂引用)
引用(&ref)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可变引用(&mut ref)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

悬垂引用(Dangling References)

在这里插入图片描述
在这里插入图片描述

5.3 Slice 类型

引入

在这里插入图片描述
在这里插入图片描述

字符串 Slice

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

字符串字面值就是 slice(牢记此特性!)

在这里插入图片描述

字符串 slice 作为参数(而不是将 String 引用作为参数)
  • 传入 slice 作为参数:my_fn(&my_string[idx1..idx2])

在这里插入图片描述

其他类型的 slice

在这里插入图片描述
在这里插入图片描述

6. 结构体

6.1 结构体的定义和实例化

结构体定义、创建实例

在这里插入图片描述
在这里插入图片描述

创建实例:字段初始化简写语法

在这里插入图片描述

创建实例:结构体更新语法
  • 注意:结构体中的“移动”(用到了任意一个未实现 copy trait类型的数据)与“克隆”(只使用了实现 copy trait类型的数据)特性!!!
    在这里插入图片描述
元组结构体

在这里插入图片描述

类单元结构体

在这里插入图片描述

结构体数据的所有权

在这里插入图片描述

6.2 结构体示例程序(打印结构体的内容,dbg)

示例:打印矩形的面积

在这里插入图片描述

通过派生 trait 来打印结构体的内容

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.3 方法(method)

在这里插入图片描述

6.3.1 定义方法

在这里插入图片描述

6.3.2 与结构体字段同名的方法
  • 读取:&self
  • 修改:&mut self
  • 获取所有权:self

在这里插入图片描述

6.3.3 带有多个参数的方法

在这里插入图片描述

6.3.4 多个 impl 块

在这里插入图片描述

6.3.5 关联函数
  • 应用:(不是方法的)关联函数经常被用作返回一个结构体新实例的构造函数,这些函数的名称通常为 new
  • 举例:在 String 类型上定义的 String::from 函数

在这里插入图片描述

6.4 小结

在这里插入图片描述

7. 枚举和模式匹配

7.1 枚举的定义

7.1.1 基本概念

在这里插入图片描述
在这里插入图片描述

7.1.2 枚举的简洁用法:构造函数

在这里插入图片描述

7.1.3 枚举的优势:处理不同类型和数量的数据
  • 枚举成员的类型:字符串数字类型结构体枚举
  • 注意:在未将标准库枚举引入当前作用域中时,可以创建与标准库中同名的枚举!

在这里插入图片描述
在这里插入图片描述

7.1.4 在枚举中定义方法

在这里插入图片描述

7.2 Option 枚举

  • T 表示不会存在空值的情况;Option<T> 表示存在空值的情况,需要考虑对空值的处理
  • Option<T>T 是不同类型的,无法直接进行计算(也就是说,在对 Option<T> 进行运算之前必须将其转换为 T
  • Option 已经引入 prelude 中,无需前缀 Option::,可直接使用 SomeNone

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.3 match 控制流结构

7.3.1 match 基础
  • match 的分支组成结构:匹配模式 Coin::Penny + => + 代码块{some_code} 组成;match 可以有多个分支
  • match 的匹配模式类型:字面值、变量、通配符、其他内容
  • 每个分支相关联的代码作为一个表达式,而表达式的结果值将作为整个 match 表达式的返回值
  • 如果想要在分支中运行多行代码,可以使用大括号,而分支后的逗号是可选的
    在这里插入图片描述
    在这里插入图片描述
7.3.2 match 绑定值的模式

在这里插入图片描述

7.3.3 match 匹配 Option<T>

在这里插入图片描述

7.3.4 match 必须是穷尽的

在这里插入图片描述

7.3.5 match 中的通配模式和 _ 占位符
  • 需要利用变量:通配模式 other => fn(other),,且必须作为最后一个分支
  • 不需要利用变量_ 占位符,_ => fn(),
  • 不需要利用变量,且不做任何事_ => (),,返回单元值

在这里插入图片描述
在这里插入图片描述

7.4 if let 简洁控制流

  • if let:只匹配一个模式的值而忽略其他模式的情况
  • if let xxx else xxx:匹配两个模式的值,并分别处理
    在这里插入图片描述
    在这里插入图片描述

7.5 小结

在这里插入图片描述

8. 包、Crate和模块管理

在这里插入图片描述

8.1 包和 Crate

8.1.1 基本概念
  • crate 是 Rust 在编译时最小的代码单位;crate 有两种形式:二进制项(可以被编译为可执行程序)和库(没有 main 函数,也不会编译为可执行程序,而是提供一些诸如函数之类的东西,使其他项目也能使用这些东西)
  • **包(package)**是提供一系列功能的一个或者多个 crate;中可以包含至多一个库 crate(library crate),也可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)

在这里插入图片描述

8.2 模块的作用域与私有性

8.2.1 模块的相关概念

在这里插入图片描述
在这里插入图片描述

8.2.1 使用模块对相关代码进行分组

在这里插入图片描述
在这里插入图片描述

8.3 模块的路径

8.3.1 绝对路径与相对路径

在这里插入图片描述
在这里插入图片描述

8.3.2 使用 pub 关键字暴露路径
  • 模块公有并不使其内容也是公有的:模块上的 pub 关键字只允许其父模块引用它,而不允许访问内部代码;模块是一个容器,只是将模块变为公有能做的其实并不太多,同时需要更深入地选择将一个或多个项变为公有(即添加 pub 前缀)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.3.3 使用 super 起始的相对路径

在这里插入图片描述

8.3.4 创建公有的结构体和枚举
  • 结构体:在一个结构体定义的前面使用了 pub ,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的(其字段默认未私有的,可以根据情况决定每个字段是否公有
  • 枚举:其成员默认就是公有的
    在这里插入图片描述
    在这里插入图片描述

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

8.4.1 use 的作用域
  • 注意:use 只能创建 use 当前所在的特定作用域内的短路径(比如:可用于在当前模块,切换到其他模块则需重新导入)

在这里插入图片描述
在这里插入图片描述

8.4.2 use 的使用习惯
  • 使用 use 时,一般是引入一个模块(而不是直接引入某些函数),在调用函数时需要指定父模块

在这里插入图片描述

8.4.3 as 关键字

在这里插入图片描述
在这里插入图片描述

8.4.4 pub use:重导出名称
  • use some_mod:仅在当前作用域生效,对外仍是私有的
  • pub use some_mod:不仅在当前作用域生效,还可导入到其他作用域生效

在这里插入图片描述

8.4.5 使用外部包
  • 对于外部包,需要先在 Cargo.toml 文件中添加所需的包,再使用 use 来导入包
  • 对于标准库,则直接 use 导入包即可

在这里插入图片描述

8.4.6 使用 {} 嵌套路径来简化 use 的使用
  • 举例:use std::{cmp::Ordering, io};use std::io::{self, Write};

在这里插入图片描述

8.4.7 通过 glob 运算符将所有的公有定义引入作用域

在这里插入图片描述

8.5 将模块拆分成多个文件

在这里插入图片描述
在这里插入图片描述

9. 常见集合

9.1 Vector

9.1.1 创建 vector
  • 创建空的 vector:Vec::new()
  • 创建有值的 vector:vec!

在这里插入图片描述

9.1.2 向 vector 中添加元素

在这里插入图片描述

9.1.3 读取 vector 的元素
  • 索引语法:当引用一个不存在的元素时 Rust 会造成 panic
  • get 方法:当 get 方法被传递了一个数组外的索引时,它不会 panic 而是返回 None,可后续用于 match 进行对应的分支处理
  • 注意:不要同时读取并修改 vector!

在这里插入图片描述
在这里插入图片描述

9.1.4 遍历 vector 的元素

在这里插入图片描述

9.1.5 在 vector 中使用枚举来储存多种类型

在这里插入图片描述

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

在这里插入图片描述

9.2 String

9.2.1 为什么字符串较为复杂?

在这里插入图片描述

9.2.2 什么是字符串

在这里插入图片描述

9.2.3 创建 String
  • String::new():创建一个空的 String
  • "xxx".to_string():根据字符数据xxx创建一个 String(或者理解为向一个 String 中添加字符数据)
  • String::from("xxx"):根据字符数据xxx创建一个 String

在这里插入图片描述

9.2.4 更新字符串(一):向 String 中追加字符(串)
  • push:获取一个单独的字符作为参数,并附加到 String 中
  • push_str:附加字符串 slice 到 String 中;此方法不获取参数的所有权

在这里插入图片描述

9.2.5 更新字符串(二):拼接 String
  • + 运算符:第一个参数必须使用原变量(该变量会被移动,夺去其所有权),后面的参数必须使用引用形式(若为 &String 则会被强制转换&str
  • format! 宏:使用参数的引用,因此不会获取任何参数的所有权

在这里插入图片描述

9.2.6 Rust 的字符串不支持索引

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

9.2.7 字符串 slice

在这里插入图片描述

9.2.8 遍历字符串的方法
  • chars():拆分为单个字符,返回 char 类型的值
  • bytes():返回每一个原始字节

在这里插入图片描述

9.2.9 字符串并不简单

在这里插入图片描述

9.3 Hash Map

9.3.1 创建 Hash Map
  • 与 vector 类似,hash map 是同质的:所有的键必须是相同类型,值也必须都是相同类型
  • HashMap::new():创建一个空的 hash map,使用 insert(k, v) 方法往里面添加元素

在这里插入图片描述

9.3.2 访问 Hash Map

在这里插入图片描述

9.3.3 更新 Hash Map
  • 直接覆盖一个值:最后一次更新的值为最终值
  • 只在键没有对应值时插入键值对entryor_insert 方法,返回一个可变引用
  • 根据旧值更新一个值:先获取旧值的可变引用,再更新(解引用 *

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

9.3.4 Hash Map 与所有权
  • 对于有所有权的值,插入 hash map 后将会被移动;而对于像 i32 这样的实现了 Copy trait 的类型,其值是直接拷贝进 hash map
  • 如果将值的引用插入哈希 map,这些值本身将不会被移动进 hash map (但是这些引用指向的值必须至少在 hash map 有效时也是有效的)

在这里插入图片描述

9.3.5 哈希函数

在这里插入图片描述

9.4 小结

在这里插入图片描述

10. 错误处理(Rust 没有异常)

10.1 处理不可恢复的错误:panic!

10.1.1 什么是 panic!

在这里插入图片描述

10.1.2 panic! 的 backtrace
  • 阅读 backtrace 的关键:从头开始读直到发现你编写的文件(这就是问题的发源地),这一行往上是你的代码所调用的代码,往下则是调用你的代码的代码

在这里插入图片描述
在这里插入图片描述

10.2 处理可恢复的错误:Result

10.2.1 什么是 Result
  • Result<T, E>:该枚举含有 TE 两个成员,T 代表成功时返回的 Ok 成员中的数据的类型,E 代表失败时返回的 Err 成员中的错误的类型
  • Result 通常配合 match 使用

在这里插入图片描述
在这里插入图片描述

10.2.2 Result 配合 match 来匹配不同的错误

在这里插入图片描述

10.2.3 Result 与闭包等方法的结合(简化 match)

在这里插入图片描述

10.2.4 失败时 panic 的简写:unwrap 和 expect
  • unwrap:提供默认的 panic! 信息;expect:提供自定义的错误信息

  • expectunwrap 使用更多

  • 在这里插入图片描述

10.2.5 传播错误
  • 传播错误:遇到错误时可提前返回,并结束函数

在这里插入图片描述

10.2.6 传播错误的简写:? 运算符
  • ? 运算符与 match 的工作方式类似:若 Result 的值为 Ok,则正常返回值且程序继续执行;若 Result 的值为 Err,则将 Err 中的值将作为整个函数的返回值,提前结束函数
  • ? 运算符与 match 的不同点:? 运算符所使用的错误值被传递给了 from 函数,它定义于标准库的 From trait 中,其用来将错误从一种类型转换为另一种类型(收到的错误类型被转换为由当前函数返回类型所指定的错误类型,可自定义实现错误类型,如:OurError
  • ? 运算符可使用链式方法调用

在这里插入图片描述
在这里插入图片描述

10.2.7 ? 运算符的使用场景
  • ? 运算符只能被用于返回值与 ? 运算符作用的值相兼容的函数
  • ? 运算符的适用返回值类型:ResultOption<T>、实现了 FromResidual 的返回值函数
  • ? 运算符遇到错误时常用的修改方法:1、修改函数的返回值类型;2、使用 matchResult<T, E> 的方法来处理
  • 注意:可以在返回 Result 的函数中对 Result 使用 ? 运算符,可以在返回 Option 的函数中对 Option 使用 ? 运算符,但是不可以混合搭配。? 运算符不会自动将 Result 转化为 Option,反之亦然;在这些情况下,可以使用类似 Resultok 方法或者 Optionok_or 方法来显式转换

在这里插入图片描述
在这里插入图片描述

10.3 使用 panic! 还是返回 Result

10.3.1 使用 panic! 的场景

在这里插入图片描述

10.3.2 返回 Result 的场景

在这里插入图片描述

10.3.3 错误处理指导原则

在这里插入图片描述

10.3.4 创建自定义类型进行有效性验证

在这里插入图片描述
在这里插入图片描述

10.4 小结

在这里插入图片描述

11. 泛型、Trait 和生命周期

11.1 泛型数据类型

11.1.1 函数的泛型
  • 注意:泛型的比较适用于实现了 std::cmp::PartialOrd trait 的数据类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

11.1.2 结构体的泛型
  • 结构体的泛型可以使用多个泛型类型参数

在这里插入图片描述
在这里插入图片描述

11.1.3 枚举的泛型
  • 类似于结构体,枚举的泛型也可以使用多个泛型类型参数

在这里插入图片描述

11.1.4 方法的泛型
  • 定义方法时可以为泛型指定限制(只有指定类型的数据可以使用该方法
  • 结构体定义的泛型参数可以与结构体方法签名的泛型参数不同,从而可以交叉使用不同的泛型类型

在这里插入图片描述
在这里插入图片描述

11.1.5 泛型代码的性能

在这里插入图片描述

11.2 Trait:定义共同行为

11.2.1 定义 trait
  • trait 定义了不同类型的共享功能
  • trait可以只提供方法签名(后跟分号),而提供具体的实现是可选的
  • trait具体实现由每个实现该 trait 的类型来定义并提供,且每个实现都使用相同的方法签名
  • trait 体中可以有多个方法,一行一个方法签名且都以分号结尾

在这里插入图片描述
在这里插入图片描述

11.2.2 实现 trait
  • impl Xxx_trait for Yyy:为 Yyy 类型实现 Xxx_trait(使用 for
  • 实现 trait 的限制:只有当至少一个 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait;不能为外部类型实现外部 trait

在这里插入图片描述
在这里插入图片描述

11.2.3 trait 的默认实现
  • 默认实现是可选的
  • 默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现(也就是只需重新实现对应部分即可)

在这里插入图片描述
在这里插入图片描述

11.2.4 trait 作为参数(一):impl Trait 语法
  • impl Bound 语法用于(不)相同类型的参数

在这里插入图片描述

11.2.5 trait 作为参数(二):Trait Bound 语法
  • trait bound 语法用于相同类型的参数
  • trait_1 + trait_2 + ...:实现多个不同 trait 相加(同时实现)
  • 可通过 where 从句简化 trait bound

在这里插入图片描述
在这里插入图片描述

11.2.6 返回实现了 trait 的类型

在这里插入图片描述

11.2.7 使用 trait bound 有条件地实现方法
  • 可以有条件地只为那些实现了特定 trait 的类型来实现方法、trait
  • 举例:带【Display】trait 条件实现【xxx】方法 fnimpl<T: Display> Pair<T> { fn xxx() }
  • 举例:带【Display】trait 条件实现【ToString】traitimpl<T: Display> ToString for T

在这里插入图片描述

11.3 生命周期:确保引用是有效的

在这里插入图片描述

11.3.1 悬垂引用与借用检查器
  • 生命周期避免了悬垂引用

在这里插入图片描述
在这里插入图片描述

11.3.2 函数中的泛型生命周期

在这里插入图片描述
在这里插入图片描述

11.3.3 生命周期注解语法
  • 生命周期注解并不改变任何引用的生命周期的长短,而是用于描述了多个引用生命周期相互的关系
  • 生命周期注解:生命周期参数名称以撇号(')开头,其名称通常全是小写
  • &i32:引用
  • &'a i32:带有显式生命周期的引用
  • &'a mut i32:带有显式生命周期的可变引用

在这里插入图片描述

11.3.4 函数签名中的生命周期注解
  • 函数签名中的生命周期注解:在函数名和参数列表间的尖括号中声明泛型生命周期(lifetime)参数,就像泛型类型(type)参数一样
  • 当在函数中使用生命周期注解时,这些注解出现在函数签名中,而不存在于函数体中的任何代码中
  • 类似于函数 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str 中,泛型生命周期 'a 的具体生命周期等同于 x 和 y 的生命周期中较小的那一个(核心概念!)

在这里插入图片描述
在这里插入图片描述

11.3.5 函数返回引用时需要关联生命周期
  • 注意:若返回引用,则需要将多个参数与其返回值的生命周期进行关联,否则最好返回有所有权的值!

在这里插入图片描述

11.3.6 结构体中的生命周期注解
  • 定义包含引用的结构体:需要为结构体定义中的每一个引用添加生命周期注解

在这里插入图片描述

11.3.7 生命周期省略(Lifetime Elision)
  • 生命周期省略规则(一):编译器为每一个(输入、输出)引用参数都分配了一个生命周期参数
  • 生命周期省略规则(二):如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
  • 生命周期省略规则(三):如果方法有多个输入生命周期参数并且其中一个参数是 &self&mut self,那么所有输出生命周期参数被赋予 self 的生命周期(该规则只适用于方法 fn 中)

在这里插入图片描述
在这里插入图片描述

11.3.8 方法(fn)中的生命周期注解
  • 实现带有生命周期的结构体实现方法 fn 时,类似于泛型类型参数的语法,比如:impl<'a> xxx_struct<'a>
  • 根据生命周期省略(三),无需在方法 fn 签名中使用生命周期注解

在这里插入图片描述

11.3.9 静态生命周期
  • 静态生命周期 'static:其生命周期能够存活于整个程序期间
  • 所有的字符串字面值都拥有 'static 生命周期
  • 使用 'static 前,思考这个引用是否真的在整个程序的生命周期里都有效

在这里插入图片描述

11.3.10 同时使用:泛型类型参数、trait bounds 和生命周期

在这里插入图片描述

11.4 小结

在这里插入图片描述

12. 自动化测试

12.1 编写测试

12.1.1 测试函数
  • 测试函数:在一个函数前加上一行 #[test] 注解将普通函数变成测试函数
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
12.1.2 assert! 宏

在这里插入图片描述
在这里插入图片描述

12.1.3 assert_eq! 与 assert_ne!
  • assert_eq!(left, right)assert_eq!(left, right) 在失败时会返回 leftright 两个值,比 assert!(xxx) 传递的信息更完善
  • 使用 assert_eq!assert_eq! 宏的值必须实现 PartialEq(用于断言两个值是否相等)与 Debug (在断言失败时打印他们的值)这两个派生 trait
  • 对于自定义的结构体与枚举,通常可以添加 #[derive(PartialEq, Debug)] 注解,来使用 assert_eq!assert_eq!

在这里插入图片描述
在这里插入图片描述

12.1.4 在断言中自定义失败信息
  • assert!assert_eq!assert_ne! 宏中,除了必需参数外,后面所有的参数都会传递给 format! 宏,作为失败时的输出进行打印

在这里插入图片描述
在这里插入图片描述

12.1.5 使用 should_panic 检查 panic
  • should_panic:在函数中的代码 panic 时会通过,而在其中的代码没有 panic 时失败
  • should_panic 属性有一个可选的 expected 参数:测试工具会确保错误信息中包含其提供的文本#[should_panic(expected = "Some text for debug...")]

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

12.1.6 使用 Result<T, E> 编写测试
  • 使用 Result<T, E> 编写测试,在通过时返回 Ok 成员,在失败时返回 Err 成员
  • 不能对这些使用 Result<T, E> 的测试使用 #[should_panic] 注解
  • 为了断言一个操作返回 Err 成员,不要使用对 Result<T, E> 值使用问号表达式(?),而是使用 assert!(value.is_err())

在这里插入图片描述

12.2 控制测试如何运行

12.2.1 命令行参数分别传递
  • cargo test --help
  • cargo test -- --help

在这里插入图片描述

12.2.2 并行或连续的运行测试
  • $ cargo test -- --test-threads=1

在这里插入图片描述

12.2.3 显示函数输出
  • $ cargo test -- --show-output

在这里插入图片描述
在这里插入图片描述

12.2.4 运行指定的一部分测试(一):运行单个测试
  • cargo test xxx_fn:指定 xxx_fn 方法运行;
  • 注意:不能指定多个测试名称,只有传递给 cargo test 的第一个值才会被使用

在这里插入图片描述

在这里插入图片描述

12.2.5 运行指定的一部分测试(二):运行多个测试
  • cargo test xxx:指定所有带有 xxx 方法运行
  • 此外,也可以通过指定模块名来运行一个模块中的所有测试

在这里插入图片描述

12.2.6 忽略某些测试
  • 若不想执行某个测试,则在前面加上一行 #[ignore] 来忽略此测试
  • cargo test -- --ignored:只运行被忽略的测试
  • cargo test -- --include-ignored:运行全部测试(不管是否存在忽略的测试)

在这里插入图片描述
在这里插入图片描述

12.3 测试的组织结构

在这里插入图片描述

12.3.1 单元测试
  • 单元测试与他们要测试的代码共同存放在位于 src 目录下相同的文件中,具体规范为在每个文件中创建包含测试函数的 mod tests 模块,并且使用 #[cfg(test)] 注解来标注模块

在这里插入图片描述
在这里插入图片描述

12.3.2 集成测试(一):tests 目录

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

12.3.3 集成测试(二):子模块
  • tests 目录中的子目录不会被作为单独的 crate 编译或作为一个测试结果部分出现在测试输出中(即将公共部分放在一个子文件夹中)

在这里插入图片描述
在这里插入图片描述

12.3.4 集成测试(三):二进制 crate

在这里插入图片描述

12.4 小结

在这里插入图片描述

13. 【项目】构建一个简易的命令行程序 grep

13.1 接受命令行参数

在这里插入图片描述

13.1.1 读取参数值
  • env::args().collect():将获取的命令行参数,形成一个集合

在这里插入图片描述
在这里插入图片描述

13.1.2 将参数值保存进变量

在这里插入图片描述

13.2 读取文件

在这里插入图片描述
在这里插入图片描述

13.3 代码重构

13.3.1 二进制项目的关注分离
  • main.rs 只需处理程序运行:无法直接测试 main 函数,且保留在 main.rs 中的代码将足够小以便阅读就可以验证其正确性
  • lib.rs 处理所有的真正的任务逻辑:将所有的程序逻辑移动到 lib.rs 的函数中使得我们可以测试它们

在这里插入图片描述

13.3.2 项目重构(一):提取参数解析器

在这里插入图片描述

13.3.3 项目重构(二):组合配置值

在这里插入图片描述
在这里插入图片描述

13.3.4 项目重构(三):创建一个 Config 的构造函数

在这里插入图片描述

13.3.5 项目重构(四):修复错误处理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

13.3.6 项目重构(五):从 main 提取逻辑

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

13.3.7 项目重构(六):将代码拆分到库 crate

在这里插入图片描述
在这里插入图片描述

13.4 采用测试驱动开发(TDD)完善库的功能

在这里插入图片描述

13.4.1 编写失败测试

在这里插入图片描述
在这里插入图片描述

13.4.2 编写使测试通过的代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

13.5 处理环境变量

在这里插入图片描述

13.5.1 编写一个大小写不敏感 search 函数的失败测试

在这里插入图片描述

13.5.2 实现 search_case_insensitive 函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

13.6 将错误信息输出到标准错误(stderr)而不是标准输出(stdout)

在这里插入图片描述

13.6.1 检查错误应该写入何处
  • 目的:期望将错误信息发送到标准错误流,这样即便选择将标准输出流重定向到文件中时仍然能看到错误信息

在这里插入图片描述

13.6.2 将错误打印到标准错误(stderr)
  • eprintln! 宏可以打印错误到标准错误(stderr)

在这里插入图片描述

14. Rust 中的函数式语言功能:迭代器与闭包

14.1 闭包:捕获环境的匿名函数

在这里插入图片描述

14.1.1 闭包会捕获其环境

在这里插入图片描述
在这里插入图片描述

14.1.2 闭包类型推断和注解
  • 闭包并不总是要求像 fn 函数那样在参数和返回值上注明类型
  • 闭包通常很短,并只关联于小范围的上下文而非任意情境
  • 如果尝试对同一闭包使用不同类型则就会得到类型错误!

在这里插入图片描述
在这里插入图片描述

14.1.3 捕获引用或者移动所有权
  • 闭包可以有3种参数捕获方式:1、不可变借用;2、可变借用;3、获取所有权
  • 对于不可变借用:可多处使用(如:打印)
  • 对于可变借用:在闭包定义和调用之间不能有不可变引用来使用(如:打印)!
  • 对于获取所有权:可使用 move 关键字来强制闭包获取它用到的环境中值的所有权

在这里插入图片描述
在这里插入图片描述

14.1.4 将被捕获的值移出闭包和 Fn trait
  • 闭包可以做3种事:1、将一个捕获的值移出闭包;2、修改捕获的值,但不移出闭包;3、既不移动也不修改值,或者一开始就不从环境中捕获值。与之对应的,闭包取决于应用场景可以实现下面3种 trait(可同时实现多个 trait
  • FnOnce适用于能被调用一次的闭包,所有闭包都至少实现了这个 trait,因为所有闭包都能被调用。一个会将捕获的值移出闭包体的闭包只实现 FnOnce trait,这是因为它只能被调用一次比如:xxx_vec.push(value) 将会移出 value 的所有权给到闭包外部的向量 xxx_vec,因此该闭包就会被实现为一个 FnOnce 闭包
  • FnMut适用于不会将捕获的值移出闭包体的闭包,但它可能会修改被捕获的值。这类闭包可以被调用多次(==闭包内部可以做一些其他的计算操作 或者说 修改值,但这些操作不能返回值 或者说 不能将值移出闭包体,比如示例13-9所示的操作:num_sort_operations += 1; ==)
  • Fn适用于既不将被捕获的值移出闭包体也不修改被捕获的值的闭包,当然也包括不从环境中捕获值的闭包。这类闭包可以被调用多次而不改变它们的环境,这在会多次并发调用闭包的场景中十分重要
  • 注意:上面这些 trait 会根据代码的实现方式来自动对应!!!

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.2 迭代器:处理元素序列

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

在这里插入图片描述

14.2.2 Iterator trait 和 next 方法
  • Iterator trait 要求同时定义一个 Item 类型,这个 Item 类型被用作 next 方法的返回值类型
  • 迭代器的消费(重要概念):在迭代器上调用 next 方法改变了迭代器中用来记录序列位置的状态,每调用一次 next 都会从迭代器中消费(去掉)一个项
  • iter:调用中得到的值是不可变引用
  • iter_mut:调用中得到的值是可变引用
  • into_iter:获取所有权并返回拥有所有权的迭代器

在这里插入图片描述

14.2.3 消费适配器:调用 next 方法会消费迭代器
  • 迭代器的消费(重要概念):在迭代器上调用 next 方法改变了迭代器中用来记录序列位置的状态,每调用一次 next 都会从迭代器中消费(去掉)一个项
  • 调用 next 方法的方法被称为 消费适配器(consuming adaptors),因为调用这些方法会获取迭代器的所有权并反复调用 next 来遍历迭代器(也就是会消耗迭代器)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.2.4 迭代适配器:改变迭代器类型
  • Iterator trait 中定义了另一类方法,被称为 迭代器适配器(iterator adaptors),他们不消耗迭代器,而是将当前迭代器变为不同功能的迭代器(如:通过 map 方法来转换一个迭代器)
  • 可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果(如:collect() 方法)!

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.2.5 使用闭包来获取上下文环境
  • filter:判断一个使用迭代器的每一个项并返回布尔值的闭包,如果闭包返回 true,其值将会包含在 filter 提供的新迭代器中。如果闭包返回 false,其值不会包含在结果迭代器中

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.2.6 实现 Iterator trait 来创建自定义迭代器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.3 改进 I/O 项目

14.3.1 使用迭代器并去掉 clone

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.3.2 使用迭代适配器来使代码更简明

在这里插入图片描述

14.4 性能对比:循环 VS 迭代器

在这里插入图片描述
在这里插入图片描述

14.5 小结

在这里插入图片描述

15. 进一步认识 Cargo 和 Crates.io

15.1 采用发布配置自定义构建

在这里插入图片描述

15.2 将 crate 发布到 Crates.io

15.2.1 文档注释

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

未完待续。。。

将 crate 发布到 Crates.io

15.3 Cargo 工作空间

15.3.1 创建工作空间

在这里插入图片描述

15.3.2 在工作空间中创建第二个包

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

15.4 使用 cargo install 安装二进制文件

在这里插入图片描述

15.5 Cargo 自定义扩展命令

在这里插入图片描述

15.6 小结

在这里插入图片描述

16. 智能指针

  • 智能指针(smart pointers)是一类数据结构,他们的表现类似指针,但是也拥有额外的元数据和功能
  • 引用是一类只借用数据的指针;相反,在大部分情况下,智能指针拥有他们指向的数据

在这里插入图片描述

16.1 Box<T>:指向堆上的数据

  • box 允许将一个值放在堆上而不是栈上,box 是一个本身留在栈上但指向堆数据的指针
  • box 的作用:提供固定大小、提供堆分配、间接存储

在这里插入图片描述

16.1.1 使用 Box<T> 在堆上储存数据
  • box:在离开作用域时,将被释放,释放过程作用于 box 本身(位于栈上)和它所指向的数据(位于堆上)

在这里插入图片描述

16.1.2 Box 的应用:创建递归类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16.1.3 计算非递归类型的大小
  • enum:实际上只会使用其中的一个成员,所以枚举值所需的空间等于储存其最大成员的空间大小

在这里插入图片描述

16.1.4 使用 Box<T> 给递归类型一个已知的大小
  • Box<T> 是一个指针,我们总是知道它需要多少空间:指针的大小并不会根据其指向的数据量而改变
  • 核心思想:建议中的 “indirection” 意味着不同于直接储存一个值,应该间接的储存一个指向值的指针!
  • 通过使用 box,打破了这无限递归的连锁,这样编译器就能够计算出储存 List 值需要的大小了

在这里插入图片描述
在这里插入图片描述

16.2 通过 Deref trait 将智能指针当作常规引用处理

  • 实现 Deref trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针

在这里插入图片描述

16.2.1 解引用 *:追踪指针的值
  • 解引用运算符 * 可用于追踪引用 & 所指向的值

在这里插入图片描述

16.2.2 像引用一样使用 Box<T>
  • Box<T> 可用于生成拷贝一个值的引用(注意是引用,而不是值)的实例(而不是指向该值的引用,因为生成的是 Box 引用,不是常规引用,二者不可比

在这里插入图片描述

16.2.3 自定义智能指针(一):引入

在这里插入图片描述

16.2.4 自定义智能指针(二):实现 Deref trait 将某类型像引用一样处理
  • 对于实现 Deref trait 的数据类型,要求实现 deref 方法,该方法借用 self 并返回一个内部数据的引用、并可用于后续的解引用
  • 没有 Deref trait 的话,编译器只会解引用 & 引用类型
  • *y 的底层运行为 *(y.deref()):Rust 将 * 运算符替换为先调用 deref 方法再进行普通解引用的操作

在这里插入图片描述

16.2.5 函数和方法的隐式 Deref 强制转换
  • Deref 强制转换(deref coercions)将实现了 Deref trait 的类型的引用转换为另一种类型的引用
  • 当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时将自动进行,这时会有一系列的 deref 方法被调用,把我们提供的类型转换成了参数所需的类型

在这里插入图片描述

16.2.6 Deref 强制转换如何与可变性交互
  • Deref trait 重载不可变引用* 运算符;DerefMut trait 用于重载可变引用的 * 运算符
  • Rust 在发现类型和 trait 实现满足三种情况时会进行 Deref 强制转换,如下所示:
    1. T: Deref<Target=U> 时从 &T&U:如果有一个 &T,而 T 实现了返回 U 类型的 Deref,则可以直接得到 &U
    1. T: DerefMut<Target=U> 时从 &mut T&mut U:与 1 类似(同上)
    1. T: Deref<Target=U> 时从 &mut T&URust 也会将可变引用强转为不可变引用,但与之相反的,不可变引用永远也不能强转为可变引用!

在这里插入图片描述

16.3 Drop Trait:运行清理代码

16.3.1 Drop Trait 的基本概念

在这里插入图片描述
在这里插入图片描述

16.3.2 std::mem::drop:提前丢弃值
  • std::mem::drop 可显式调用来丢弃值,该方法已经存在于 prelude 中
  • 所有权系统确保引用总是有效的,也会确保 drop 只会在值不再被使用时被调用一次

在这里插入图片描述
在这里插入图片描述

16.4 Rc<T>:引用计数智能指针

  • 引用计数(reference counting):意味着记录一个值引用的数量来知晓这个值是否仍在被使用,如果某个值有零个引用,就代表没有任何有效引用并可以被清理
  • 如果确实知道哪部分是最后一个结束使用的话,就可以令其成为数据的所有者,正常的所有权规则就可以在编译时生效
  • 为了启用多所有权需要显式地使用 Rust 类型 Rc<T>,注意 Rc<T> 只能用于单线程场景

在这里插入图片描述

16.4.1 使用 Rc<T> 共享数据
  • Rc<T> 可通过克隆的方式 Rc::clone 来增加引用计数,直到有零个引用之前其数据都不会被清理
  • Rc::clone 的实现并不像大部分类型的 clone 实现那样对所有数据进行深拷贝,只会增加引用计数,这并不会花费多少时间,可以明显的区别深拷贝类的克隆和增加引用计数类的克隆(在下面的示例中也可以调用 a.clone()

在这里插入图片描述
在这里插入图片描述

16.4.2 克隆 Rc 会增加引用计数
  • Rc::strong_count:返回引用计数的值

在这里插入图片描述

16.5 RefCell<T> 和内部可变性模式

  • 内部可变性(Interior mutability)是 Rust 中的一个设计模式,它允许即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 unsafe 代码来模糊 Rust 通常的可变性和借用规则

在这里插入图片描述

16.5.1 RefCell<T>:在运行时检查借用规则
  • 对于引用和 Box<T>:借用规则的不可变性作用于编译时;如果违反这些规则会得到一个编译错误
  • 对于 RefCell<T>:这些不可变性作用于运行时;而对于 RefCell<T>,如果违反这些规则程序会 panic 并退出

在这里插入图片描述

16.5.2 内部可变性:不可变值的可变借用
  • 借用规则推论:对于一个不可变值,不能可变的借用它

在这里插入图片描述

16.5.3 内部可变性的示例:mock 对象
  • std::cell::RefCell:内部可变性相关模块
  • 在下面的示例中,对于要修改的结构体字段 sent_messages 字段的类型是 RefCell<Vec<String>> 而不是 Vec<String>;在 new 函数中新建了一个 RefCell<Vec<String>> 实例替代空 vector
  • RefCell::borrow_mut 方法来获取 RefCell 中值的可变引用;RefCell::borrow 方法来获取 RefCell 中值的不可变引用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16.5.4 RefCell<T>:在运行时记录借用
  • 不可变引用:一般使用 & 语法;对于 RefCell<T> 使用 borrow 方法,返回一个 Ref<T> 类型的智能指针,且实现了 Deref trait
  • 可变引用:一般使用 &mut 语法;对于 RefCell<T> 使用 borrow_mut 方法,返回一个 RefMut<T> 类型的智能指针,且实现了 Deref trait
  • RefCell<T> 记录当前有多少个活动的 Ref<T>RefMut<T> 智能指针
  • 每次调用 borrowRefCell<T> 将活动的不可变借用计数加一;当 Ref<T> 值离开作用域时,不可变借用计数减一
  • 像编译时借用规则一样,RefCell<T> 在任何时候只允许有多个不可变借用或一个可变借用
  • 如果违反借用规则,相比引用时的编译时错误,RefCell<T> 的实现会在运行时出现 panic

在这里插入图片描述
在这里插入图片描述

16.5.5 结合 Rc<T> 和 RefCell<T> 来拥有多个可变数据所有者

在这里插入图片描述
在这里插入图片描述

16.6 引用循环与内存泄漏

在这里插入图片描述

16.6.1 制造引用循环
  • 创建引用循环并不容易,但也不是不可能:如果你有包含 Rc<T>RefCell<T> 值或类似的嵌套结合了内部可变性和引用计数的类型,请小心检查有没有形成一个引用循环
  • 另一个解决方案是重新组织数据结构,使得一部分引用拥有所有权而另一部分没有(换句话说,循环将由一些拥有所有权的关系和一些无所有权的关系组成,只有所有权关系才能影响值是否可以被丢弃)
  • 代码解析:*link.borrow_mut() = Rc::clone(&b); 表示将取出的变量 link 引用通过 borrow_mut() 方法来获得其可变引用,再用 * 来解引用指针,解引用后将这个可变引用重新指向 b 的克隆引用(*link.borrow_mut() 应该等价于 *(link.borrow_mut())

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16.6.2 避免引用循环:将 Rc<T> 变为 Weak<T>
  • 创建弱引用(weak reference):调用 Rc::downgrade 并传递 Rc<T> 实例的引用来创建其值的弱引用,并会得到 Weak<T> 类型的智能指针,会将 weak_count 加 1。创建弱引用的例子:*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
  • 弱引用的 weak_count:用来记录存在多少个 Weak<T> 引用,与 strong_count 的区别在于,weak_count 无需计数为 0 就能使 Rc<T> 实例被清理
  • 使用 upgrade 方法用于判断指向的值是否被丢弃:因为 Weak<T> 引用的值可能已经被丢弃了,为了使用 Weak<T> 所指向的值,我们必须确保其值仍然有效,为此可以调用 Weak<T> 实例的 upgrade 方法,这会返回 Option<Rc<T>>
  • 强、若引用的区别:强引用代表如何共享 Rc<T> 实例的所有权(引用并拥有),但弱引用并不属于所有权关系(仅引用,但不拥有)!
  • 弱引用不会造成引用循环,因为任何弱引用的循环会在其相关的强引用计数为 0 时被打断

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16.7 小结

在这里插入图片描述

17. Rust 中的并发

在这里插入图片描述

17.1 线程

在这里插入图片描述

17.1.1 spawn:创建新线程
  • thread::spawn:创建一个新线程,需要传递一个闭包,并在其中包含希望在新线程运行的代码
  • thread::sleep:调用强制线程停止执行一小段时间。比如:thread::sleep(Duration::from_millis(1));

在这里插入图片描述

17.1.2 join:等待所有线程结束
  • thread::spawn 的返回值类型是 JoinHandleJoinHandle 是一个拥有所有权的值
  • JoinHandle.join:通过调用 handlejoin 会阻塞当前线程直到 handle 所代表的线程结束(也就是等待所关联的线程运行结束)
  • join 的调用位置会影响线程的运行顺序(比如:影响线程是否同时运行)

在这里插入图片描述
在这里插入图片描述

17.1.3 线程与 move 闭包
  • 可以在参数列表前使用 move 关键字强制闭包获取其使用的环境值的所有权。比如:使用 main 函数中的外部数据,这样就无法知道这些外部数据的生命周期,因此无法编译
  • move 关键字经常用于传递给 thread::spawn 的闭包,因为闭包会获取从环境中取得的值的所有权,因此会将这些值的所有权从一个线程传送到另一个线程
  • move 关键字覆盖了 Rust 默认保守的借用,但它不允许我们违反所有权规则

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

17.2 使用消息传递在线程间传送数据

17.2.1 消息传递(message passing)与信道(channel)
  • use std::sync::mpsc;mpsc多个生产者,单个消费者(multiple producer, single consumer)的缩写。简而言之,Rust 标准库实现信道的方式意味着一个信道可以有多个产生值的 发送(sending)端,但只能有一个消费这些值的 接收(receiving)端
  • mpsc::channel 函数返回一个元组:第一个元素是发送端(发送者,tx),而第二个元素是接收端(接收者,rx,具有迭代器特性)
  • 发送者方法send转移所有权
  • send转移所有权):用来获取需要放入信道的值;该方法返回一个 Result<T, E> 类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误
  • 接收者方法recvtry_recv
  • recv:该方法会阻塞主线程执行直到从信道中接收一个值(即:一直等待消息,直到接收到一个值才会结束)。一旦发送了一个值,recv 会在一个 Result<T, E> 中返回它。当信道发送端关闭,recv 会返回一个错误表明不会再有新的值到来了
  • try_recv不会阻塞,相反它立刻返回一个 Result<T, E>:Ok 值包含可用的信息,而 Err 值代表此时没有任何消息。如果线程在等待消息过程中还有其他工作时使用 try_recv 很有用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

17.2.2 信道与所有权转移
  • send:函数获取其参数的所有权并移动这个值归接收者所有

在这里插入图片描述

17.2.3 发送多个值并观察接收者的等待
  • 接收者 rx 是一个迭代器

在这里插入图片描述

17.2.4 通过克隆发送者来创建多个生产者
  • 注意:下面的例子,是不确定顺序的实现!可以添加 join 来阻塞线程,进而控制接收消息的先后顺序

在这里插入图片描述
在这里插入图片描述

17.3 共享状态并发

在这里插入图片描述

17.3.1 互斥器一次只允许一个线程访问数据
  • 互斥器(mutex)在任意时刻只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过**获取互斥器的 锁(lock)**来表明其希望访问数据

在这里插入图片描述

17.3.2 互斥器 Mutex<T>
  • use std::sync::Mutex;
  • Mutex<T>:使用关联函数 new 来创建一个 Mutex<T>Mutex<T> 是一个智能指针;Mutex<T> 提供了内部可变性,就像 Cell 系列类型那样
  • lock获取锁,以访问互斥器中的数据,返回一个叫做 MutexGuard 的智能指针(这个智能指针实现了 Deref 来指向其内部数据;其也提供了一个 Drop 实现当 MutexGuard 离开作用域时自动释放锁)。这个调用会阻塞当前线程,直到我们拥有锁为止
  • 锁的特性,如下所示
  • 如果另一个线程拥有锁,并且那个线程 panic 了,则 lock 调用会失败。在这种情况下,没人能够再获取锁(所以这里可以选择 unwrap 并在遇到这种情况时使线程 panic
  • 一旦获取了锁,就可以将返回值视为一个其内部数据的可变引用了(在示例中,m 的类型是 Mutex<i32> 而不是 i32,所以必须获取锁才能使用这个 i32 值)

在这里插入图片描述

17.3.3 在线程间共享 Mutex<T>(一):不能将锁的所有权移动到多个线程中

在这里插入图片描述

17.3.4 在线程间共享 Mutex<T>(二):多线程和多所有权
  • Rc<T> 并不能安全的在线程间共享
    在这里插入图片描述
    在这里插入图片描述
17.3.5 在线程间共享 Mutex<T>(三):原子引用计数 Arc<T>
  • Arc<T>:原子引用计数(atomically reference counted)类型,一个类似 Rc<T> 并可以安全的用于并发环境的类型Arc<T>Rc<T> 有着相同的 API
  • Mutex<T> 提供了内部可变性,就像 Cell 系列类型那样,因此如同使用 RefCell<T> 可以改变 Rc<T> 中的内容那样,同样的可以使用 Mutex<T> 来改变 Arc<T> 中的内容

在这里插入图片描述

17.3.6 RefCell<T>/Rc<T> 与 Mutex<T>/Arc<T> 的相似性

在这里插入图片描述

17.4 使用 Sync 和 Send trait 的可扩展并发

17.4.1 通过 Send 允许在线程间转移所有权
  • Send trait(所有权):一个实现了 Send 的类型值的所有权可以在线程间传送
  • Send 的类型:几乎所有的 Rust(基本)类型都是 Send 的、完全由 Send 的类型组成的类型也会自动被标记为 SendArc<T>
  • Send 的类型::Rc<T>、裸指针(raw pointer)

在这里插入图片描述

17.4.2 Sync 允许多线程访问
  • Sync trait(引用):一个实现了 Sync 的类型可以安全的在多个线程中拥有其值的引用
  • Sync 的类型:基本类型是 Sync 的、完全由 Sync 的类型组成的类型也是 Sync 的、Mutex<T>
  • Sync 的类型:Rc<T>RefCell<T>Cell<T>

在这里插入图片描述

17.4.3 手动实现 Send 和 Sync 是不安全的
  • 注意:通常并不需要手动实现 SendSync trait!

在这里插入图片描述

17.5 小结

在这里插入图片描述

18. Rust 的面向对象特性

18.1 面向对象语言的特征

18.1.1 对象:数据 + 行为

在这里插入图片描述

18.1.2 封装隐藏了实现细节
  • 在 Rust 中,在代码中不同的部分考虑使用 pub 可以封装其实现细节

在这里插入图片描述
在这里插入图片描述

18.1.3 继承,作为类型系统与代码共享
  • 在 Rust 中,不存在继承的机制,而是使用 trait 对象来提供相关的功能

在这里插入图片描述

18.2 trait 对象

  • 已知全部的数据类型时,可以使用固定集合(枚举);但当全部的数据类型不是已知时(会动态变化),就需要使用 trait 对象来实现了

在这里插入图片描述

18.2.1 trait 对象(是一个实例):定义通用行为
  • trait 对象:指向一个实现了指定 trait 类型的实例,以及一个用于在运行时查找该类型的 trait 方法的表
  • 通过指定某种**指针(& 引用或 Box<T> 智能指针,还有 dyn 关键字)**来创建 trait 对象
  • 可以使用 trait 对象代替泛型或具体类型,无需在编译时就知晓所有可能的类型
  • trait 对象将数据和行为两者相结合(不同于传统的对象,不能向 trait 对象增加数据),trait 对象的作用是允许对通用行为进行抽象
  • 对比泛型类型:泛型类型参数一次只能替代一个具体类型,而 trait 对象则允许在运行时替代多种具体类型

在这里插入图片描述
在这里插入图片描述

18.2.2 实现 trait
  • 将一个或多个类型的实例放入 Box<T>就可以转换得到 trait 对象!
  • 使用 trait 对象和 Rust 类型系统来进行类似鸭子类型操作的优势是无需在运行时检查一个值是否实现了特定方法或者担心在调用时因为值没有实现方法而产生错误。如果值没有实现 trait 对象所需的 trait 则 Rust 不会编译这些代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

18.2.3 trait 对象执行动态分发

在这里插入图片描述

18.2.4 trait 对象需要类型安全
  • 只有**对象安全(object-safe)**的 trait 可以实现为特征对象
  • 如果一个 trait 中定义的所有方法都符合以下规则,则该 trait 是对象安全的:1、返回值不是 Self;2、没有泛型类型的参数
  • 核心原因:一旦使用 trait 对象,Rust 将不再知晓该实现的返回类型!

在这里插入图片描述

18.3 面向对象设计模式的实现

18.3.1 状态模式
  • 状态模式(state pattern):是一个面向对象设计模式,该模式的关键在于定义一系列值的内含状态,这些状态体现为一系列的状态对象,同时值的行为随着其内部状态而改变
  • 状态对象共享功能
  • 每一个状态对象负责其自身的行为,以及该状态何时应当转移至另一个状态。而持有一个状态对象的值,对于不同状态的行为以及何时状态转移毫不知情

在这里插入图片描述
在这里插入图片描述

18.3.2 博客项目(一):定义 Post 并新建一个草案状态的实例

在这里插入图片描述

18.3.3 博客项目(二):存放博文内容的文本
  • 对于需要修改的字段需要传入可变引用 &mut self

在这里插入图片描述

18.3.4 博客项目(三):确保博文草案的内容是空的

在这里插入图片描述

18.3.5 博客项目(四):请求审核博文来改变其状态
  • 代码解析:if let Some(s) = self.state.take() 表示先取出当前 state 的值及其所有权,再给到中间变量 ss 再根据实际获取到的 state 值调用对应的 request_review 方法(即判断是属于 Draft 还是 PendingReviewrequest_review
  • 在这里来看看状态模式的优势:无论 state 是何值,Post 的 request_review 方法都是一样的。每个状态只负责它自己的规则

在这里插入图片描述
在这里插入图片描述

18.3.6 博客项目(五):增加改变 content 行为的 approve 方法
  • 调用 Optionas_ref 方法是因为需要 Option值的引用而不是获取其所有权
  • 在下面的示例中,获取到的 &Box<dyn State>,当调用其 content 时,Deref 强制转换会作用于 &Box
  • 在下面的示例中,获取 post 的引用作为参数,并返回 post 一部分的引用,所以返回的引用的生命周期与 post 参数相关

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

18.3.7 状态模式的权衡取舍
  • 状态模式的优点:Post 的方法和使用 Post 的位置无需 match 语句,同时增加新状态只涉及到增加一个新 struct 和为其实现 trait 的方法
  • 状态模式的缺点:因为状态实现了状态之间的转换,一些状态会相互联系会有一些重复的逻辑(可通过默认方法、定义宏等方法来解决)

在这里插入图片描述

18.3.8 博客项目-改造(一):将状态和行为编码为类型

在这里插入图片描述
在这里插入图片描述

18.3.9 博客项目-改造(二):实现状态转移为不同类型的转换

在这里插入图片描述
在这里插入图片描述

18.4 小结

在这里插入图片描述

19. 模式与模式匹配

在这里插入图片描述

19.1 所有可能会用到模式的位置

19.1.1 match 分支

在这里插入图片描述

19.1.2 if let 条件表达式
  • 可以组合并匹配 if letelse ifelse if let 表达式,优势在于可以将多个值与模式比较match 表达式一次只能将一个值与模式比较),且各个分支并不要求其条件相互关联
  • if let 也可以像 match 分支那样引入覆盖变量
  • if let 表达式的缺点在于其穷尽性没有为编译器所检查(不保证覆盖的完备性),而 match 表达式则检查了(保证覆盖的完备性)

在这里插入图片描述

19.1.3 while let 条件循环

在这里插入图片描述

19.1.4 for 循环
  • for 可以获取一个模式:模式是 for 关键字直接跟随的值,正如 for x in y 中的 x

在这里插入图片描述

19.1.5 let 语句
  • 如果希望忽略元组中一个或多个值,也可以使用 _..

在这里插入图片描述

19.1.6 函数参数

在这里插入图片描述

19.2 Refutability(可反驳性): 模式是否会匹配失效

  • 不可反驳的(irrefutable):能匹配任何传递的可能值的模式
  • 可反驳的(refutable):对某些可能的值进行匹配会失败的模式
  • 在不可反驳模式的地方使用可反驳模式通常是意义不大的
  • 例子:match 匹配分支必须使用可反驳模式,除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式(其实也可以在只有一个匹配分支的 match 中使用不可反驳模式,不过这么做不是特别有用,并可以被更简单的 let 语句替代)

在这里插入图片描述
在这里插入图片描述

19.3 所有的模式语法

19.3.1 匹配字面值

在这里插入图片描述

19.3.2 匹配命名变量
  • 变量覆盖问题:存在外部变量的,则会优先使用外部变量;在新作用域中的新变量(会忽略外部的同名变量)会匹配任何 Some 中的值!

在这里插入图片描述

19.3.3 match 中的多个模式(|)
  • match 表达式中,可以使用 | 语法匹配多个模式,它代表 或(or)的意思

在这里插入图片描述

19.3.4 通过 ..= 匹配值的范围
  • ..= 语法允许你匹配一个闭区间范围内的值,但仅适用于数字char(仅这两种类型可以判断范围是否为空的类型)

在这里插入图片描述

19.3.5 解构并分解值(一):解构结构体
  • 匹配结构体字段的简写模式(1):只需列出结构体字段的名称,则模式创建的变量会有相同的名称
  • 匹配结构体字段的简写模式(2):也可以使用字面值作为结构体模式的一部分进行解构,而不是为所有的字段创建变量(这允许我们测试一些字段是特定值,同时会创建其他字段的变量

在这里插入图片描述
在这里插入图片描述

19.3.6 解构并分解值(二):解构枚举

在这里插入图片描述

19.3.7 解构并分解值(三):解构嵌套的结构体和枚举

在这里插入图片描述

19.3.8 解构并分解值(四):解构结构体和元组

在这里插入图片描述

19.3.9 忽略模式中的值(一):使用 _ 忽略整个值
  • _:作为匹配但不绑定任何值的通配符模式,可以将其用于任意模式。通常作为 match 表达式最后的分支,也可以用作函数参数

在这里插入图片描述

19.3.10 忽略模式中的值(二):使用嵌套的 _ 忽略部分值
  • 可以在一个模式内部使用 _ 忽略部分值
  • 可以在一个模式中的多处使用 _ 来忽略特定值

在这里插入图片描述

19.3.11 忽略模式中的值(三):通过在名字前以一个下划线开头来忽略未使用的变量
  • 告诉 Rust 不要警告未使用的变量:可以用下划线作为变量名的开头(比如:_x
  • 注意:下划线开头的变量 _x 仍会将值绑定到变量,而 _ 则完全不会绑定

在这里插入图片描述

19.3.12 忽略模式中的值(四):用 … 忽略剩余值
  • .. 模式会忽略模式中剩余的任何没有显式匹配的值部分,必须是无歧义

在这里插入图片描述

19.3.13 匹配守卫提供的额外条件
  • 匹配守卫(match guard)是一个指定于 match 分支模式之后的额外 if 条件,它也必须被满足才能选择此分支;匹配守卫用于表达比单独的模式所能允许的更为复杂的情况(需要同时满足更多条件
  • 匹配守卫可以解决模式中变量覆盖的问题

在这里插入图片描述
在这里插入图片描述

19.3.14 @ 绑定
  • @ 运算符:允许在创建一个存放值的变量的同时测试其值是否匹配模式,也即先进行匹配测试,若通过则保存变量值到一个新建的变量中

在这里插入图片描述

19.4 小结

在这里插入图片描述

20. Rust 的高级特征

在这里插入图片描述

20.1 不安全 Rust

在这里插入图片描述

20.1.1 不安全的超能力

在这里插入图片描述

20.1.2 解引用裸指针
  • 裸指针(raw pointers):类似于引用类型;和引用一样,裸指针是不可变或可变的,分别写作 *const T*mut T,这里的星号不是解引用运算符,它是类型名称的一部分
  • 在裸指针的上下文中,不可变意味着指针解引用之后不能直接赋值
  • 直接从保证安全的引用来创建他们(比如使用 as 来强转为某类型),可以知道这些特定的裸指针是有效,但是不能对任何裸指针做出如此假设
  • 裸指针与引用和智能指针的区别,如下 4 点
  • 允许忽略借用规则:可以同时拥有不可变和可变的指针(若通过可变指针修改数据,则可能潜在造成数据竞争!),或多个指向相同位置的可变指针
  • 不保证指向有效的内存
  • 允许为空
  • 不能实现任何自动清理功能

在这里插入图片描述
在这里插入图片描述

20.1.3 调用不安全函数或方法(一):基本概念

在这里插入图片描述

20.1.4 调用不安全函数或方法(二):创建不安全代码的安全抽象
  • slice::from_raw_parts_mut 函数是不安全的因为它获取一个裸指针,并必须确信这个指针是有效的
  • 裸指针上的 add 方法也是不安全的,因为其必须确信此地址偏移量也是有效的指针
  • 注意无需将 split_at_mut 函数的结果标记为 unsafe,并可以在安全 Rust 中调用此函数。我们创建了一个不安全代码的安全抽象,其代码以一种安全的方式使用了 unsafe 代码

在这里插入图片描述
在这里插入图片描述

20.1.5 调用不安全函数或方法(三):使用 extern 函数调用外部代码

在这里插入图片描述

20.1.6 访问或修改可变静态变量
  • 常量:
  • 可变静态变量:静态变量中的值有一个固定的内存地址(使用这个值总是会访问相同的地址);访问不可变静态变量是安全的
  • 可变静态变量:使用 mut 关键字来指定可变性;访问和修改可变静态变量都是 不安全 的,必须位于 unsafe 模块内

在这里插入图片描述

20.1.7 实现不安全 trait

在这里插入图片描述

20.1.8 访问联合体中的字段

在这里插入图片描述

20.1.9 何时使用不安全代码

在这里插入图片描述

20.2 高级 trait

20.2.1 关联类型:在 trait 定义中指定占位符类型
  • **关联类型(associated types)**是一个将类型占位符与 trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型
  • 关联类型也会成为 trait 契约的一部分:trait 的实现必须提供一个类型来替代关联类型占位符
  • 作用:用于类型占位,不用重复多次仅修改类型来实现同一个功能

在这里插入图片描述

20.2.2 默认泛型类型参数和运算符重载
  • 默认参数类型主要应用:1、扩展类型而不破坏现有代码;2、在大部分用户都不需要的特定情况进行自定义

在这里插入图片描述
在这里插入图片描述

20.2.3 完全限定语法与消歧义:调用相同名称的方法
  • 同名问题:Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait,甚至直接在类型上实现开始已经有的同名方法也是可能的
  • 对于方法(有 self 参数):编译器默认调用直接实现在类型上的方法
  • 对于关联函数(没有 self 参数):直接调用定义于实现在类型中的关联函数
  • 完全限定语法:<Type as Trait>::function(receiver_if_method, next_arg, ...);,唯一地指定出哪个对象类型(type)所实现的哪个 trait 方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

20.2.4 父 trait 用于在另一个 trait 中使用某 trait 的功能
  • 父(超)trait(supertrait):在实现 trait 时通过 子模块: 父模块 指定其依赖的上一级父 trait,即可获得父 trait 的方法。比如:OutlinePrint: fmt::Display
  • 注意:需要同时满足子、父 trait 的实现要求!

在这里插入图片描述

20.2.5 newtype 模式用以在外部类型上实现外部 trait

在这里插入图片描述

20.3 高级类型

20.3.1 为了类型安全和抽象而使用 newtype 模式

在这里插入图片描述

20.3.2 类型别名用来创建类型同义词
  • 类型别名(type alias):使用 type 关键字来给予现有类型另一个名字,主要用途是减少重复

在这里插入图片描述
在这里插入图片描述

20.3.3 从不返回的 never type
  • ! 空类型 / 不返回类型:在函数从不返回的时候充当返回值,可以强转为任何其他类型

在这里插入图片描述
在这里插入图片描述

20.3.4 动态大小类型和 Sized trait
  • Rust 中动态大小类型的常规用法:他们有一些额外的元信息来储存动态信息的大小
  • 动态大小类型的黄金规则:必须将动态大小类型的值置于某种指针之后
  • Sized trait 来决定一个类型的大小是否在编译时可知;这个 trait 自动为编译器在编译时就知道大小的类型实现;另外,Rust 隐式的为每一个泛型函数增加了 Sized bound

在这里插入图片描述

20.4 高级函数与闭包

20.4.1 函数指针
  • 函数指针(function pointer):通过函数指针允许使用函数作为另一个函数的参数函数满足类型 fn(小写的 f),不要与闭包 trait 的 Fn(大写)相混淆
  • 不同于闭包,fn 是一个类型而不是一个 trait,所以直接指定 fn 作为参数而不是声明一个带有 Fn 作为 trait bound 的泛型参数
  • 函数指针实现了所有三个闭包 trait(FnFnMutFnOnce,所以总是可以在调用期望闭包的函数时传递函数指针作为参数

在这里插入图片描述
在这里插入图片描述

20.4.2 返回闭包

在这里插入图片描述

20.5 宏

20.5.1 宏和函数的区别
  • 从根本上来说,宏是一种为写其他代码而写代码的方式,即所谓的 元编程(metaprogramming)
  • 宏与函数的区别,如下 3 点
  • 宏能够接收不同数量的参数,但函数签名必须声明函数参数个数和类型
  • 在一个文件里调用宏 之前 必须定义它,或将其引入作用域;而函数则可以在任何地方定义和调用
  • 宏定义通常要比函数定义更难阅读、理解以及维护

在这里插入图片描述
在这里插入图片描述

20.5.2 macro_rules! :声明宏用于通用元编程

在这里插入图片描述
在这里插入图片描述

20.5.3 用于从属性生成代码的过程宏

在这里插入图片描述

20.5.4 如何编写自定义 derive 宏

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

20.5.5 类属性宏
  • 自定义派生宏derive 属性生成代码,derive 只能用于结构体和枚举
  • 类属性宏:类属性宏与自定义派生宏相似,能创建新的属性,属性还可以用于其它的项(比如:函数),也更为灵活

在这里插入图片描述

20.5.6 类函数宏
  • 类函数(Function-like)宏:定义看起来像函数调用的宏,类似于 macro_rules!,它们比函数更灵活(例如,可以接受未知数量的参数)

在这里插入图片描述

21.【项目】构建多线程 web server

构建多线程 web server

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值