#### 11测试

##### 编写测试

测试函数的三种操作：设置数据或状态，运行测试代码，断言结果。

在`fn`前加上`#[test]`，就是给这个函数带上了`test`属性。当执行`cargo test`命令时，Rust会调用标记了`test`属性的函数，并报告测试的结果。

`assert!`宏由标准库提供，它对一个布尔值进行判断，如为真则什么也不做，如为假则调用`panic!`宏。

`assert_eq!`和`assert_ne!`宏用于测试两个值是否相等，如断言失败则打印出这两个值的内容。

可以向`assert!`、`assert_eq!`、`assert_ne!`宏传递可选的信息参数，在它们的必需参数之后的参数都会传递给`format!`宏从而打印出来。

在`fn`前加上`#[should_panic]属性`，则函数panic的时候测试通过，函数没有panic的时候测试失败。这用来测试函数是否生成了panic。还可以给`should_panic`属性增加一个可选的`expected`参数，用以输出一些信息，在测试不通过时显示它从而方便定位问题。

也可以把`Result<T, E>`作为返回值编写测试，此时测试通过则返回`Ok(())`，测试失败则返回带有`String`的`Err`。

##### 控制测试

`cargo test`生成的二进制文件默认是并行运行的，所以测试不能相互依赖、或依赖任何共享的状态。

可以在`#[test]`后面增加`#[ignore]`来忽略某些测试。

详情可参考`man cargo-test`命令。

##### 测试的组织结构

单元测试存在于`src`目录下，与被测代码放在同一文件，位于用`#[cfg(test)]`标准的`tests`模块中。

`#[cfg(test)]`在执行`cargo test`才编译和运行测试代码，在执行`cargo build`时不这么做。因为单元测试的代码和源码在同一文件中，所以才需要`#[cfg(test)]`。

在单元测试里，是允许测试私有函数的。

集成测试存在于`tests`目录，cargo会把`tests`目录下的每一个文件当作一个单独的crate来编译。

集成测试把要测的库看成是外部的，所以需要用`use`来导入它。

`tests`目录对于cargo来说是一个特殊的目录，在执行`cargo test`时会自动编译这个目录下的文件。

`tests`目录中的子目录不会作为单独的crate编译，也不会作为测试结果的一部分出现在测试输出中。这对于集成测试中的子模块来说是有用的。使用子模块和使用其它模块没有区别，都是使用`mod`关键字。

#### 13 函数式编程

##### 闭包

数学上的闭包，个人理解，就是对某种运算封闭的一些对象的**最小**集合。在数据库里也有闭包的概念。Rust里的闭包是一个匿名函数，可以以变量或参数的形式存在，闭包可以捕获调用者作用域中的值。

使用函数获取结果，这个函数一定会在确定的位置被执行。而如果使用闭包，只有当需要这个结果的时候闭包才会执行。

闭包的定义以一对竖线开始，竖线之间是闭包的参数，如有多个参数则以逗号分隔；接下来则是闭包的语句块，如果是多行语句要使用大括号，如果只有一行则可省略大括号。

使用let语句定义一个变量为闭包，意味着这个变量是此闭包的定义，**而不是**此闭包的返回值。

调用闭包和调用函数的形式是一样的。

在定义闭包的时候，不必在参数和返回值上注明类型。因为，和函数不一样，闭包的接口不是暴露在外的，而是只关联于小范围的上下文中，编译器能可靠地推断参数和返回值的类型。注意，闭包的接口类型是**固定的**，如果调用两次闭包而给它传不同类型的参数，则编译器会报错。

为了提高计算效率，可以缓存闭包的返回值，这样在多次调用闭包的时候就可以避免重复计算。Rust的解决方案是：用一个结构体来存放闭包及其调用结果。为了在结构体里定义闭包，需要使用**泛型**和**特性绑定**。

`Fn`是由`std::ops`提供的特性。所有的闭包都要实现特性`Fn`、`FnMut`或`FnOnce`中的一个。

在结构体中使用闭包的形式：需要定义一个泛型，这泛型要用where语法特性绑定`Fn`(或者`FnMut`、`FnOnce`)。在结构体中需要定义一个此泛型的字段用以代表闭包的实例。在结构体中还需要定义一个`Option`类型的字段来保存闭包的结果。

在结构体中使用闭包的逻辑过程：在结构体里的闭包执行前，结构体里`Option`类型的字段值是`None`。第一次请求闭包，`Option`类型的字段值会存储闭包的结果。如果再次请求闭包的结果，则闭包不再执行，而是返回该`Option`字段的值。

在结构体里使用闭包，结构体里的字段应该是私有的，这样可以避免调用者不合理地改变字段的值。应创建`new`方法接收闭包并返回该结构体的实例。应该创建一个方法检查`Option`类型字段的值，如果是`None`则执行闭包并保存它的返回值，如果有值则直接返回该值。

使用值缓存的方法有两个小问题：１，一旦`Option`字段里有了值，则不管给闭包传入什么参数，返回值都不会改变。解决方法是用哈希映射来取代单独一个值。２，闭包传入的值和返回的值类型要一致。解决方法是引入范型。

闭包可以捕获它的调用者里的变量，而函数则不行。因为闭包和它的调用者在相同的作用域里。

闭包捕获其调用者环境的三种方式，对应三个特性：

1. `FnOnce` - 获取所有权。因为同一变量的所有权只能转移一次，所以叫Once。
2. `FnMut` - 获取可变的借用值。
3. `Fn` - 获取不可变的借用值。

`move`关键字可以强制闭包获取其调用者环境中值的所有权。

##### 迭代器

迭代器都实现了`Iterator`特性，其定义如下：

```rust
pub trait Iterator{
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // 方法的实现
}
```

`type Item`是说`Iterator`特性要求定义一个`Item`类型，作为`next`方法的返回值类型。`next`方法用于返回迭代器中的一个项。

`Iterator`特性中调用了`next`方法的方法被称为消费适配器。

`Iterator`特性中把当前迭代器变为其它类型迭代器的方法称为迭代器适配器。

只要实现了迭代器的`next`方法，就可以使用标准库中定义的其它`Iterator`特性的方法了，因为它们都使用了`next`方法的功能。

##### 循环VS迭代器

闭包和迭代器是Rust的**零成本抽象**之一。

#### 15 智能指针

**引用**是最简单的指针，**智能指针**是带有额外数据的引用。

智能指针与普通结构体的区别：`Deref`和`Drop`特性。`Deref`特性使智能指针表现得像引用一样。`Drop`特性定义了当智能指针离开作用域时运行的代码。

标准库中常用的智能指针有：

- `Box<T>`，用于在堆上分配值
- `Rc<T>`，一个引用计数类型，其数据可以有多个所有者
- `Ref<T>`和`RefMut<T>`，通过`RefCell<T>`访问。

**内部可变性**模式，把不可变类型暴露出来以改变其内部值。**引用循环**会泄漏内存。

##### Box\<T>

智能指针`Box<T>`把值放在堆上，把指向数据的指针留在栈上。

##### Deref特性

重载**解引用运算符**，把智能指针当作常规引用来对待。

##### Drop特性

`Drop`特性包含在prelude中，所以无需导入。

##### Rc\<T>

rc是引用计数的缩写，用于启动多所有权。

##### RefCell\<T>

**内部可变性**是Rust中的一个设计模式，它允许改变不可变引用的数据。

##### 引用循环

#### 16 无畏并发

**并发编程**是指程序的不同部分独立执行，**并行编程**是指程序的不同部分同时执行。

##### 使用线程实现并发

使用`tread::spawn`创建新线程。

使用`handle.join()`方法阻塞当前线程。

在闭包之前使用`move`，强制闭包获取其使用的值的所有权。

##### 使用消息传递实现并发

##### 使用共享状态实现并发

##### 使用Sync和Send特性实现并发

`Send`特性允许在线程间转移所有权。

`Sync`特性允许多线程访问。

#### 17 面向对象

##### 面向对象语言的特征

面向对象编程要求：对象中包含数据和行为。Rust语言可以使用`impl`块为结构体和枚举带上方法，从这个角度来看，Rust是面向对象的。

面向对象编程的思想：封装，即对象的实现细节不能被外部代码获取。Rust语言可以使用`pub`使模块、类型、函数和方法是公有的，默认情况下其它一切都是私有的。从这个角度来看，Rust是面向对象的。

面向对象编程的机制：继承，即对象之间可以相互继承。Rust语言使用的是**特性**而不是继承。从这个角度来看，Rust不是面向对象的。

##### trait对象

#### 18 模式

##### 模式的位置

`match`分支里包含了模式。

`if let`表达式里包含了模式。

`while let`表达式里包含了模式。

`for`循环里包含了模式。

`let`语句里包含了模式。

函数参数也可以是模式。

##### 模式的两种形式

模式的两种形式：可反驳的，不可反驳的。

不可反驳的：能匹配任何值的模式。函数参数、let语句和for循环只能接受此种模式。

可反驳的：匹配某些值的时候会失败的模式。if let、while let只能接受此种模式。

match匹配里前面的分支必须使用可反驳模式，最后一个则要支持不可反驳模式。

##### 模式的语法

匹配字面值。

匹配命名变量。

匹配多个模式。

匹配值的范围。

解构结构体、枚举、嵌套、元组。

忽略模式中的值。

匹配守护。

绑定。

#### 19 高级特征

##### 不安全性

使用`unsafe`关键字开启一个不安全的代码块。它将允许五类超级操作：

- 解引用裸指针
- 调用不安全的函数或方法
- 访问或修改可变静态变量
- 实现不安全特性
- 访问`union`的字段

##### 高级特性

关联类型。

为泛型指定一个默认的类型。

使用完全限定语法，以消除歧义。

超特性。

使用newtype模式在外部类型上实现外部特性。

##### 高级类型

使用newtype模式。

使用`type`关键字给现有类型另一个名字。

使用`!`在函数从不返回的时候充当返回值。

使用Sized特性来处理动态大小类型。

##### 高级函数和闭包

使用函数指针把函数传给函数。

使用trait对象来返回闭包。

##### 宏

- 宏包括了声明宏(*declarative* macros)和过程宏( *procedural* macros)。

  声明宏用`macro_rules!`。

  过程宏包含自定义宏`#[derive]`、类属性宏(Attribute-like macros)和类函数宏(Function-like macros)。

- 函数和宏的区别

  函数必须声明参数个数与类型，而宏能接受不同数量的参数。

  宏可以在预编译阶段展开，而函数是在编译阶段被编译的。

  宏定义比函数定义更难阅读、理解和维护。

  使用宏之前必须先有它的定义，而函数的定义可以出现在使用之后。

- 定义声明宏

  ```rust
  #[macro_export]
  macro_rules! vec {
      ( $( $x:expr ),* ) => {
          {
              let mut temp_vec = Vec::new();
              $(
                  temp_vec.push($x);
              )*
              temp_vec
          }
      };
  }
  ```

  `#[macro_export]`使用宏可用。没有该属性宏不能被引入作用域。

  `macro_rules! <name> {() => {};}`是宏定义的基本结构。

  `=>`之前是要匹配的模式。$()用于捕获满足模式的值，模式`$x:expr`的意思是匹配任意的表达式，并给表达式取名为`$x`。随后的逗号表示在满足模式的代码之后可以出现逗号(也可以没有逗号)，`*`的意思是可以匹配0次或多次。

  `=>`之后是匹配到该模式后要执行的代码。

