摘录于 Rust 程序设计语言 中文版

零、Why Rust

  • Rust 是一种令人兴奋的新编程语言,它可以让每个人编写可靠且高效的软件。
  • 它可以用来替换C/C++Rust和他们具有同样的性能,但是很多常见的bug在编译时就可以被消灭。
  • Rust是一种通用的编程语言,但是他更善于以下场景:
    • 需要运行时的速度
    • 需要内存安全
    • 更好的利用多处理器
  • Rust安全、无需GC、易于维护、调试、代码安全高效。
  • Rust优点,性能、安全、无所畏惧的并发。
  • Rust特别擅长的领域
    • 高性能的 Web Service
    • WebAssembly
    • 命令行工具
    • 网络编程
    • 嵌入式设备
    • 系统编程
  • Google:新操作系统Fuschia,其中Rust代码量大约占30%
  • Amazon:基于Linux开发的直接可以在裸机、虚拟机上运行容器的操作系统
  • System76:纯Rust开发了下一代安全操作系统Redox
  • 蚂蚁金服:库操作系统Occlum
  • 微软:正在用Rust重写Windows系统中的一些低级组件。 WINRT/Rust项目

Linux 6.1 正式发布,带有 MGLRU、初始 Rust 支持

2022 年,Rust 将成为 Linux 内核第二官方语言?

谷歌宣布支持使用 Rust 开发 Chromium

为什么Rust连续三年成为最受欢迎的语言?

谷歌宣布Android 开源项目(AOSP)现在支持Rust 编程语言来开发OS

一、环境

1.1 安装配置

一般大家都是使用 rustup 进行 Rust 工具链的安装和管理,只需要根据官方网站上的说明先安装 rustup 即可~

# 可以先执行这两行,rsproxy 是我们维护的镜像,可以加速下载
$ export RUSTUP_DIST_SERVER="https://rsproxy.cn"
$ export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"

# 或者把这两行加入到你的 ~/.zshrc 或者 ~/.profile 文件中,并 source 一次
$ echo 'export RUSTUP_DIST_SERVER="https://rsproxy.cn"' >> ~/.zshrc
$ echo 'export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"' >> ~/.zshrc
$ source ~/.zshrc

# 执行安装命令
$ curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh

安装完成后,需要配置一下 cargo 使用的源:

$ mkdir -p ~/.cargo
$ vim ~/.cargo/config

输入以下内容并保存退出:

# 加速下载
[source.crates-io]
replace-with = 'rsproxy'

[source.rsproxy]
registry = "https://rsproxy.cn/crates.io-index"

[registries.rsproxy]
index = "https://rsproxy.cn/crates.io-index"

# 公司内 Registry
[source.crates-byted]
registry = "https://code.byted.org/rust-lang/crates.byted.org-index"

[registries.crates-byted]
index = "https://code.byted.org/rust-lang/crates.byted.org-index"

[net]
git-fetch-with-cli = true

基本命令

$ rustup update
$ rustup self uninstall
$ rustc main.rs

$ cargo --version
$ cargo new hello_cargo
$ cargo new add-one --lib
$ cd hello_cargo
$ cargo build
$ cargo run
$ cargo check

可以使用 cargo build 构建项目。
可以使用 cargo run 一步构建并运行项目。
可以使用 cargo check 构建项目而无需生成二进制文件来检查错误。
有别于将构建结果放在与源码相同的目录,Cargo 会将其放到 target/debug 目录。

二、猜数字

#![allow(unused)]

use std::{io, process};
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    let mut guess = 0;
    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        let mut input_str = String::new();
        println!("Please input a number : ");

        io::stdin()
            .read_line(&mut input_str)
            .expect("failed read line");

        println!("your input str is {}", input_str);

        let input_str = input_str.trim();

        match input_str.parse() {
            Ok(t) => {
                guess = t;
            }
            Err(e) => {
                println!(
                    "\"{}\" is not a number! err = {}, Please re-input again.",
                    input_str, e
                );
                guess = 0;
                continue
            }
        }

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

    }

    // let guess: u32 = input_str.parse().expect("Not a number!");
    println!("you input number is {} , bye.", guess);
}

三、通用性变成概念

3.1 变量和可变性

变量和常量 Demo

fn main() {
    println!("Hello, world!");

    let x = 5; // let mut x = 5;
    println!("The value of x is: {}", x);
    x = 6; //  是不可变变量 这里会报错,cannot assign twice to immutable variable
    println!("The value of x is: {}", x);


    // 常量, 命名规范 大写字母加下划线
    const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

    // 遮蔽/隐藏(Shadowing)
    let x = x + 1;
    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {}", x);
    }
    println!("The value of x is: {}", x);

    let spaces = "   "; // 字符串切片
    let spaces = spaces.len(); // usize 类型 arch
    println!("The value of spaces is: {}", spaces);

}

3.2 数据类型

Rust 的每个值都有确切的数据类型(data type),Rust 是一种静态类型(statically typed)的语言。

let guess: u32 = "42".parse().expect("Not a number!");
let guess:_ = "42".parse().expect("Not a number!"); // 不知道是什么类型,会报错

标量类型:一个标量类型代表一个单个的值

Rust 有四个主要的标量类型:整数类型浮点类型布尔类型字符类型

无符号类型u开头,有符号类型i开头,i8i16i32i64i128isize

数字字面量 示例
十进制 98_222
十六进制 0xff
八进制 0o77
二进制 0b1111_0000
字节 (仅限于 u8) b'A'
fn main() {
    let x = 2.0; // f64
    let y: f32 = 3.0; // f32

    // 数字运算
    // addition
    let sum = 5 + 10;
    // subtraction
    let difference = 95.5 - 4.3;
    // multiplication
    let product = 4 * 30;
    // division
    let quotient = 56.7 / 32.2;
    let floored = 2 / 3; // Results in 0
    // remainder
    let remainder = 43 % 5;

    // 布尔类型
    let t = true;
    let f: bool = false; // with explicit type annotation

    // 字符类型
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '😻';
}

复合类型(compound type)可以将多个值组合成一个类型。Rust 有两种基本的复合类型:元组(tuple)和数组(array)。

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1); // 元组
    let five_hundred = x.0; // 点标记法访问
    let six_point_four = x.1;
    let one = x.2;
    
    // 数组长度不变,vector 长度可变
    let a = [1, 2, 3, 4, 5]; // 数组
    let first = a[0];
   let second = a[1];
   
   a[99] // index out of bound
   // RUST_BACKTRACE = 1
}

3.3 函数

parameters表示形参,arguments表示实参。

fn another_function() { // snake case 命名规范
    println!("Another function.");
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {}{}", value, unit_label);
}

fn five() -> i32 {
    5 // 表示返回 5 (可以不需要 return)
}

fn plus_one(x: i32) -> i32 {
    x + 1
    // x + 1; // 这个表示返回一个空的元组 "()"
}

3.4 控制流

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
    
   let condition = true;
  let number = if condition { 5 } else { 6 };

    println!("The value of number is: {}", number);

    // rust 是强类型语言,必须在编译时候确变量类型
  let number = if condition { 5 } else { "six" }; // 会报错,类型不一致
  
     let mut count = 0;
    'counting_up: loop {
        println!("count = {}", count);
        let mut remaining = 10;

        loop {
            println!("remaining = {}", remaining);
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }


    let mut counter = 0;

    let result = loop { // 从循环返回
        counter += 1;

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


    // while
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number -= 1;
    }


    // for
    let a = [10, 20, 30, 40, 50];

    for element in a { 
        println!("the value is: {}", element);
    }
    
     for number in (1..4).rev() {
        println!("{}!", number);
    }

}

四、认识所有权

所有权(系统)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收器(garbage collector)即可保证内存安全。因此,理解 Rust 中所有权的运作方式非常重要。在本章中,我们将讨论所有权以及相关功能:借用、slice 以及 Rust 如何在内存中存放数据。

4.1 什么是所有权

  • Rust的核心特性就是所有权
  • 所有程序在运行时都必须管理他们使用的计算机内存的方式
    • 有些语言有垃圾收集机制,在程序运行时,它们会不断寻找不再使用的内存
    • 在其他语言中,程序员必须显示的分配和释放内存
  • Rust 采用了第三种方式:
    • 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检测的规则

Stack Vs Heap

  • Stack 按值的接受顺序来存储,按相反的顺序将他们移除(后进先出,LIFO
    • 所有存在在Stack上的数据或运行时大小可能发生变化的数据必须存放在heap上。
    • 指针是已知固定的大小,可以把指针存在Stack上。
  • Heap内存组织性差一些
    • 当你把数据放在heap时,你会请求一定数据量的空间
    • 操作系统在heap里找到一块足够大的空间,标记为在用,并返回一个指针,也就是这个空间的地址。
    • 这个过程叫做在heap上进行分配,有时候仅仅成为“分配”

所有权规则

首先,让我们看一下所有权的规则。当我们通过举例说明时,请谨记这些规则:

  • Rust 中的每一个值都有一个被称为其 所有者owner)的变量。
  • 值在任一时刻有且只有一个所有者。
  • 当所有者(变量)离开作用域,这个值将被丢弃。

示例

fn main() {    
    let mut s = String::from("hello");
    s.push_str(", world!"); // push_str() 在字符串后追加字面值
    println!("{}", s); // 将打印 `hello, world!`


    let x = 5;
    let y = x; // 编译的时候就确定了大小,在栈上分配的,会自动复制,所以不存在所有权转移的问题。
    println!("x = {}, y = {}", x, y);


    // 数据类型
    let s1 = String::from("hello");
    let s2 = s1; // !!!! 【s1 value moved here】
    println!("The value of s1 is: {}", s1); // 会报错 value borrowed here after move
    println!("The value of s2 is: {}", s2);

    let s1 = String::from("hello");
    let s2 = s1.clone(); // 深拷贝

    println!("s1 = {}, s2 = {}", s1, s2);
    
    let s1 = "abc"; // 字符串字面量
    let s2 = s1 + "cdf"; // 报错 cannot add `&str` to `&str`
    let s2 = s1.to_owned() + "cdf"; // String + &str
    println!("s1 = {} , s2 = {}", s1, s2); // 报错 cannot be used to concatenate two `&str` strings

}

#[cfg(not(no_global_oom_handling))]
#[stable(feature = "rust1", since = "1.0.0")]
impl Add<&str> for String {
    type Output = String;

    #[inline]
    fn add(mut self, other: &str) -> String {
        self.push_str(other);
        self
    }
}

image.png

Rust 有一个叫做 Copy trait 的特殊标注,可以用在类似整型这样的存储在栈上的类型上。如果一个类型实现了 Copy trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。Rust 不允许自身或其任何部分实现了 Drop trait 的类型使用 Copy trait。如果我们对其值离开作用域时需要特殊处理的类型使用 Copy 标注,将会出现一个编译时错误。要学习如何为你的类型添加 Copy 标注以实现该 trait

任何一组简单标量值的组合都可以实现 Copy,任何不需要分配内存或某种形式资源的类型都可以实现 Copy 。如下是一些 Copy 的类型:

  • 所有整数类型,比如 u32
  • 布尔类型,bool,它的值是 truefalse
  • 所有浮点数类型,比如 f64
  • 字符类型,char
  • 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。

所有权与函数

fn main() {
  let s = String::from("hello");  // s 进入作用域
  takes_ownership(s);             // s 的值移动到函数里 ...
                                  // ... 所以到这里不再有效

  let x = 5;                      // x 进入作用域
  makes_copy(x);                  // x 应该移动函数里,
                                  // 但 i32 是 Copy 的,所以在后面可继续使用 x
} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
  // 所以不会有特殊操作

fn takes_ownership(some_string: String) { // some_string 进入作用域
  println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
  println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作

返回值与作用域

fn main() {
  let s1 = gives_ownership();         // gives_ownership 将返回值
                                      // 移给 s1
  let s2 = String::from("hello");     // s2 进入作用域
  let s3 = takes_and_gives_back(s2);  // s2 被移动到
                                      // takes_and_gives_back 中,
                                      // 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
  // 所以什么也不会发生。s1 移出作用域并被丢弃

fn gives_ownership() -> String {           // gives_ownership 将返回值移动给
                                           // 调用它的函数
  let some_string = String::from("yours"); // some_string 进入作用域
  some_string                              // 返回 some_string 并移出给调用的函数
}

// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
  a_string  // 返回 a_string 并移出给调用的函数
}

4.2 引用与借用

我们把引用作为函数的参数,这个行为叫做借用

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
    s.push_str(", world"); // 报错 `s` is a `&` reference, so the data it refers to cannot be borrowed as mutable
    s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
  // 所以什么也不会发生

可变引用

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

    change(&mut s);
    let mut s = String::from("hello");
    let r1 = &mut s;
    let r2 = &mut s; // 报错,cannot borrow `s` as mutable more than once at a time,可以防止 Data Race

    println!("{}, {}", r1, r2);
    
    let mut s = String::from("hello");

     // 不可以同时把变量借用给 “不可变引用”和“可变引用”
    let r1 = &s; // 没问题 
    let r2 = &s; // 没问题
    let r3 = &mut s; // 大问题

    println!("{}, {}, and {}", r1, r2, r3);


}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

悬垂引用(Dangling References)

在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针(dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s // 报错 missing lifetime specifier
}

4.3 切片 Slice 类型

另一个没有所有权的数据类型是 sliceslice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s); // word 的值为 5
    s.clear(); // 这清空了字符串,使其等于 ""

    // word 在此处的值仍然是 5,
    // 但是没有更多的字符串让我们可以有效地应用数值 5。word 的值现在完全无效!
}


fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

   let s = String::from("hello world");
    let hello = &s[0..5];
    let world = &s[6..11];
    
    let s = String::from("hello");
    let len = s.len();        
    let slice = &s[0..len];
    let slice = &s[..];

返回第一个单词的 Demo

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s); // immutable borrow occurs here
    // s.clear(); // error! cannot borrow `s` as mutable because it is also borrowed as immutable

    println!("the first word is: {}", word);
}


fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

五、Struct

5.1 定义和举例说明结构体

#[derive(Debug)]
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

fn main() {
    let user1 = build_user(String::from("another@example.com"), String::from("someusername123"));

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
    
     println!("rect1 is {:?}", user2);

}

5.2 方法语法

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

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

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    let rect2 = Rectangle {
        width: 10,
        height: 40,
    };
    let rect3 = Rectangle {
        width: 60,
        height: 45,
    };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

六、枚举和模式匹配

6.1 定义枚举、Option

enum IpAddrKind {
    V4,
    V6,
}

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

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

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

Option 枚举和其相对于空值的优势

编程语言的设计经常要考虑包含哪些功能,但考虑排除哪些功能也很重要。Rust 并没有很多其他语言中有的空值功能。空值(Null)是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。

Tony Hoarenull 的发明者,在他 2009 年的演讲 Null References: The Billion Dollar Mistake 中曾经说到:

我称之为我十亿美元的错误。当时,我在为一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过我未能抵抗住引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数十亿美元的苦痛和伤害。

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

问题不在于概念而在于具体的实现。为此,Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 Option<T>,而且它定义于标准库中

enum Option<T> {
    Some(T),
    None,
}


let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;

let x: i8 = 5;
let y: Option<i8> = Some(5);
// no implementation for `i8 + std::option::Option<i8>`
let sum = x + y; // 报错,the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not satisfied
    

Option 比 Null 好在哪?

  • Option<T>T 是不同类型,不可以把 Option<T> 直接当成 T
  • 若想使用 Option<T> 中的 T,必须把他转化T

6.2 match/if 控制流运算符

fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
            // _  => None, “_” 表示default
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}


let mut count = 0;
if let Coin::Quarter(state) = coin {
    println!("State quarter from {:?}!", state);
} else {
    count += 1;
}

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

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

  • 包(Packages): Cargo 的一个功能,它允许你构建、测试和分享crate。(最顶层)
  • Crates :一个模块的树形结构,它形成了库或二进制项目。
  • 模块(Modules)和 use: 允许你控制作用域和路径的私有性。
  • 路径(path):一个命名例如结构体、函数或模块等项的方式

7.1 Packages 和 crate

Crate Root :1. 是源码文件。 2. Rust 编译器从这里开始,组成你的 Crate 的根 Module

一个Package

  • 包含一个Cargo.toml, 它描述了如何构建这些 Crates
  • 只能包含0-1library crate
  • 可以包含任意数量的 binary crate
  • 但是必须至少有一个cratelibrary或者crate

cargo.toml Demo

[package]
name = "async_io"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "main"
path = "src/main.rs"

[[bin]]
name = "echo-server"
path = "src/echo_server.rs"

[[bin]]
name = "echo-server-copy"
path = "src/echo_server_copy.rs"

[dependencies]
tokio = { version = "1", features = ["full"] }
mini-redis = "0.4"


cargo run --bin main

Cargo的惯例

src/main.rs:

  • binary crateCrate root
  • crate 名与 package 名相同

src/lib.rs:

  • package 包含一个 library crate
  • library cratecrate root
  • crate 名与 package 名相同
  • 一个Package可以同时包含src/main.rssrc/lib.rs
    • 一个 binary crate,一个library crate
    • 名称与package名相同
  • 一个 Package 可以有多个binary crate
    • 文件放在 src/bin
    • 每个文件是单独的binary crate

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

定义Module来控制作用域和私有性

  • Module
    • 在一个crate内,将代码进行分组
    • 增加可读性,易于复用
    • 控制项目(item)的私有性。publicprivate
  • 建立 Module
    • mod 关键字
    • 可嵌套
    • 可以包含其他项(structenum、常量、trait、函数等)

demo

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}
        fn server_order() {}
        fn take_payment() {}
    }
}

7.3 路径用于引用模块树中的项

路径有两种形式:

  • 绝对路径(absolute path)从 crate 根部开始,以 crate 名或者字面量 crate 开头。
  • 相对路径(relative path)从当前模块开始,以 selfsuper 或当前模块的标识符开头。

文件名: src/lib.rs

mod front_of_house {
    pub mod hosting { // 这里必须定义为 pub,不然下面调用的时候会报错
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}

使用 super 起始的相对路径

fn serve_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order();
    }

    fn cook_order() {}
}

    

7.4 使用 use 关键字将名称引入作用域

Demo

// src/main.rs
    
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use front_of_house::hosting;
// use crate::front_of_house::hosting;


pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}


use crate::front_of_house::hosting;
use crate::front_of_house::hosting::add_to_waitlist;
use std::collections::HashMap;
use std::io::Result as IoResult;
pub use crate::front_of_house::hosting; // 使用 pub use 重导出名称
use std::{cmp::Ordering, io};
use std::io;
use std::io::Write;
use std::io::{self, Write}; // 这一行便将 std::io 和 std::io::Write 同时引入作用域。
use std::collections::*; // 这个 use 语句将 std::collections 中定义的所有公有项引入当前作用域。使用 glob 运算符时请多加小心!Glob 会使得我们难以推导作用域中有什么名称和它们是在何处定义的。

7.5 将模块分割进不同文件

文件名: src/main.rs

fn main() {
    println!("Hello, world!");
    eat_at_restaurant();
}


mod front_of_house; // 对应 src/front_of_house.rs

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

文件名: src/front_of_house.rs

// pub mod hosting {
//     pub fn add_to_waitlist() {}
// }


pub mod hosting; // 对应 src/front_of_house/hosting.rs
// mod hosting;

文件名: src/front_of_house/hosting.rs

pub fn add_to_waitlist() {
    println!("add_to_waitlist")
}

拆分方式一:拆分成文件

- Cargo.toml
- src/
    - add.rs
    - main.rs

// src/add.rs
pub mod add_one {
    pub fn add_one (base: u32) -> u32 {
        base + 1
    }
}

// src/main.rs
mod add;

fn main() {
    print!("{}", add::add_one::add_one(0));
}

拆分方式二:拆分成文件夹

- Cargo.toml
- src
    - add/
        - add_one.rs
        - mod.rs
    - main.rs


// src/add/add_one.rs
pub fn add_one (base: u32) -> u32 {
  base + 1
}

// src/add/mod.rs
pub mod add_one;

// src/main.rs
mod add;

fn main() {
    print!("{}", add::add_one::add_one(0));
}

拆分方式三:拆分成文件加文件夹

- Cargo.toml
- src
    - add/
        - add_one.rs
    - add.rs
    - main.rs


// src/add/add_one.rs
pub fn add_one (base: u32) -> u32 {
  base + 1
}

// src/add.rs
pub mod add_one;


// src/main.rs
mod add;

fn main() {
    print!("{}", add::add_one::add_one(0));
}

八 常见集合

Rust 标准库中包含一系列被称为 集合(collections)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和成本,而根据当前情况选择合适的集合,这是一项应当逐渐掌握的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合:

  • vector 允许我们一个挨着一个地储存一系列数量可变的值
  • 字符串(string)是字符的集合。我们之前见过 String 类型,不过在本章我们将深入了解。
  • 哈希 maphash map)允许我们将值与一个特定的键(key)相关联。这是一个叫做 map 的更通用的数据结构的特定实现。

8.1 vector

vector 基本操作:

let v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];

let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);


let third: &i32 = &v[2];
println!("The third element is {}", third);

match v.get(2) {
    Some(third) => println!("The third element is {}", third),
    None => println!("There is no third element."),
}


let v = vec![1, 2, 3, 4, 5]; 

let does_not_exist = &v[100];
let does_not_exist = v.get(100);

let v = vec![100, 32, 57];
for i in &v {
    println!("{}", i);
}

// 我们也可以遍历可变 vector 的每一个元素的可变引用以便能改变他们。示例 8-9 中的 for 循环会给每一个元素加 50:
let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;
}

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];

在拥有 vector 中项的引用的同时向其增加一个元素

// 防止底层的 slice 扩容以后 ,first 指向的老的内存位置可能会被释放或者重新分配。
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0]; // immutable borrow occurs here
v.push(6); // cannot borrow `v` as mutable because it is also borrowed as immutable
println!("The first element is: {}", first);

不能这么做的原因是由于 vector 的工作方式:在 vector 的结尾增加新元素时,在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。

8.2 字符串

  • Rust 的核心语言层面,只有一个字符串类型:字符串切片 str 或者 &str
  • 字符串切片:对存储在其他地方、UTF-8编码的字符串的引用
    • 字符串字面值:存储在二进制文件中,也是字符串切片

String类型:

  • 来自标准库而不是核心语言
  • 可增长、可修改、可拥有
  • UTF-8编码
  • 不支持索引语法访问。// str[0]
    • 索引操作应该消耗一个常量的时间(O(1))
    • String 无法保证:需要遍历所有内容,来确定有多少个合法的字符。

字符串字面量 Go vs Rust

let data = "initial contents";
let s = data.to_string();
// 该方法也可直接用于字符串字面量:
let s = "initial contents".to_string();

func main() {
    a := "hello world."
    //a := string([]byte("1234567890"))

    println("a = ", a, " &a = ", &a)
    bs := StringToBytes(a)

    println(string(bs))

    bs[0] = 'c' // panic
    println(string(bs))
}

func StringToBytes(s string) []byte {
    return *(*[]byte)(unsafe.Pointer(
        &struct {
            string
            Cap int
        }{s, len(s)},
    ))
}

索引字符串 GO vs Rust

这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以理解字符串:字节、和字形簇(最接近人们眼中 字母 的概念)。

字符串: "नमस्ते"

  • 字节 str.bytes() // [224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
  • 标量值 str.chars() //['न', 'म', 'स', '्', 'त', 'े']
  • 字形簇 // ["न", "म", "स्", "ते"]

origin_img_v2_f862efd1-d827-4812-a3f8-9b3e4cd40chu.jpg

你好两个汉字对应的Unicode编码如下,一共占用6个字节。

11100100 10111101 10100000 // 你
11100101 10100101 10111101 // 好

先看下Golang的字符串

str := "你好"
for idx, v := range str {
    //idx = 0 , value = 你
    //idx = 3 , value = 好
    fmt.Printf("idx = %d , value = %c\n", idx, v)
}

runeData := []rune(str) // rune int32
for i, rData := range runeData {
    //idx = 0 , rData = 20320, utf8.RuneLen(rData) = 3, binary code = 100111101100000 , str = 你
    //idx = 1 , rData = 22909, utf8.RuneLen(rData) = 3, binary code = 101100101111101 , str = 好
    fmt.Printf("idx = %d , rData = %d, utf8.RuneLen(rData) = %d, binary code = %b , str = %s \n",
        i, rData, utf8.RuneLen(rData), rData, string(rData))
}

char1 := str[:3]
char2 := str[:2]
fmt.Printf("char1 = %s, chat2 = %s \n", char1, char2) //char1 = 你, chat2 = �

在编译器源码里面for-range 处理方式decoderune

let mut hello = String::from("你");
hello.push_str("好");
println!("len = {}", hello.len()); // len = 6
for c in hello.chars() {
    // 你
    // 好
    println!("{}", c);
}

for c in hello.bytes(){
    // 228
    // 189
    // 160
    // 229
    // 165
    // 189
    println!("{}", c);
}


// 需要按字符边界切割,不然就会恐慌 
let h1 = &hello[0..2]; // 'byte index 2 is not a char boundary; it is inside '你' (bytes 0..3) of `你好`'
let h2 = &hello[..3]; 
println!("h2 = {}", h2) // h2 = 你

其他用法

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = s1 + "-" + &s2 + "-" + &s3;

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{}-{}-{}", s1, s2, s3);

8.3 哈希 map 储存键值对

  • 默认情况下,HashMap使用加密功能强大的Hash函数,可以抵抗拒绝服务(DoS)。
    • 不是可用的最快的Hash算法
    • 但具有更好安全性
  • 可以指定不同的hasher来切换到另一个函数
    • hasher 是实现BuildHasher trail的类型

Demo

//示例 8-20:新建一个哈希 map 并插入一些键值对
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
    
// 示例 8-21:用队伍列表和分数列表创建哈希 map
use std::collections::HashMap;
let teams  = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
// 两个数组zip成一个map
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();

// 示例 8-22:展示一旦键值对被插入后就为哈希 map 所拥有
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// 这里 field_name 和 field_value 不再有效,

// 示例 8-23:访问哈希 map 中储存的蓝队分数
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name);

for (key, value) in &scores {
    println!("{}: {}", key, value);
}

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);

// 根据旧值更新一个值, 如果是第一次看到某个单词,就插入值 0。
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
    let count = map.entry(word).or_insert(0);
    *count += 1;
}

九、错误处理

9.1 panic! 与不可恢复的错误

  • Rust的可靠性:错误处理
    • 大部分情况下:在编译时提示错误,并处理
  • 错误的分类:
    • 可以恢复,例如文件未找到,可以再试尝试。
    • 不可恢复的,bug,例如访问索引超出返回。
  • Rust 没有类似异常的机制
    • 可以恢复的错误:Result<T,E>
    • 不可恢复:panic!

当出现 panic 时,程序默认会开始 展开(unwinding),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 终止(abort),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,panic 时通过在 Cargo.toml 的 [profile] 部分增加 panic = ‘abort’,可以由展开切换为终止。例如,如果你想要在release模式中 panic 时直接终止:

[profile.release]
panic = 'abort'

设置环境变量RUST_BACKTRACE=full 可以打印回溯信息

9.2 Result 与可恢复的错误

enum Result<T, E> {
    Ok(T),
    Err(E),
}

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {:?}", e),
            },
            other_error => panic!("Problem opening the file: {:?}", other_error),
        },
    };
}


fn main() {
    let f = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {:?}", error);
            })
        } else {
            panic!("Problem opening the file: {:?}", error);
        }
    });
}

失败时 panic 的简写:unwrap 和 expect

let f = File::open("hello.txt").unwrap(); // 失败的时候,panic
let f = File::open("hello.txt").expect("Failed to open hello.txt");// 失败的时候,panic("Failed to open hello.txt")

传播错误(返回错误)

use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    } // 这里面没有“;” 表示当时是会返回值。
}

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

use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?; // “?”表示,如果出错,就直接返回Err,
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

// 简写一
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)

// 简写二
use std::io;
use std::fs;    
fs::read_to_string("hello.txt")

? 运算符可被用于返回 Result 的函数

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let f = File::open("hello.txt")?;

    Ok(())
}

9.3 使用 panic! 还是不用 panic!

  • 示例、代码原型和测试都非常适合 panic

  • 当我们比编译器知道更多的情况

      let home: IpAddr = "127.0.0.1".parse().unwrap();
    
  • 错误处理指导原则

    • 有害状态是指一些意外的事情,而不是预期可能偶尔发生的事情,比如用户输入错误格式的数据。
    • 在此之后的代码需要摆脱这种有害状态,而不是在每一步都检查这个问题。
    • 在使用的类型中,没有一个好的方式来编码这些信息。

十、泛型、trait 和生命周期

10.1 泛型数据类型

函数定义中的泛型

fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest { // binary operation `>` cannot be applied to type `T`
            largest = item; 
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

结构体定义中的泛型

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
    let wont_work = Point { x: 5, y: 4.0 }; // 报错 expected integer, found
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

enum Option<T> {
    Some(T),
    None,
}

枚举定义中的泛型

  • structenum实现方法的时候,可在定义中使用泛型
  • 注意:
    • T放在impl关键字后,表示在类型T上实现方法
      • 例如:impl<T> Point<T>
    • 只针对具体类型实现方法(其余类型没实现方法):
      • 例如:impl Point<f32>
  • struct 里的泛型类型参数可以和方法的泛型类型参数不同

Demo

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c'};

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

10.2 trait:定义共享的行为

  • Trait 告诉 Rust 编译:某种类型具有哪些并且而已与其他类型共享的功能
  • Trait:抽象的定义共享行为
  • Trait bounds(约束):泛型类型参数指定为实现了特点行为的类型。
  • Trait与其他语言的接口(interface)类似,但是有些区别

Demo

pub trait Summary {
    fn summarize(&self) -> String;
    // 也可以有默认实现
    fn summarize(&self) -> String {
    String::from("(Read more...)")
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

// Trait Bound 语法
pub fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}
pub fn notify(item1: impl Summary, item2: impl Summary) {
pub fn notify<T: Summary>(item1: T, item2: T) {
// 通过 + 指定多个 trait bound
pub fn notify(item: impl Summary + Display) {
// 通过 where 简化 trait bound
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug

// 返回实现了 trait 的类型
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}

// 这段代码的返回值类型指定为返回 impl Summary,但是返回了 NewsArticle 或 Tweet 就行不通:
fn returns_summarizable(switch: bool) -> impl Summary {// 报错
    if switch {
        NewsArticle {
            headline: String::from("Penguins win the Stanley Cup Championship!"),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from("The Pittsburgh Penguins once again are the best
            hockey team in the NHL."),
        }
    } else {
        Tweet {
            username: String::from("horse_ebooks"),
            content: String::from("of course, as you probably already know, people"),
            reply: false,
            retweet: false,
        }
    }
}


// 示例 10-15:一个可以用于任何实现了 PartialOrd 和 Copy trait 的泛型的 largest 函数
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0].Clone();

    for item in list.iter() {
        if item > &largest {
            largest = item.clone();
        }
    }

    largest
}

fn largest2<T: PartialOrd + Clone>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest3<T: PartialOrd + Clone>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for &item in list.iter() {
        if item > &largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

// 使用 trait bound 有条件地实现方法
struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self {
            x,
            y,
        }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

let p = Pair {
    x: 1,
    y: 2,
};
p.cmp_display();

let p1 = Pair {
    x: vec![1, 2, 3],
    y: vec![1, 2, 3],
};
p1.cmp_display(); // 报错

3.to_string() //
//     impl Summary for Tweet {
// impl<T: fmt::Display + ?Sized> ToString for T {
pub trait ToString {
    fn to_string(&self) -> String;
}

10.3 生命周期与引用有效性

  • Rust的每个引用都有自己的生命周期。
  • 生命周期:引用保持有效的作用域。
  • 大多数情况:生命周期是隐式的、可被推断的。
  • 当引用的生命周期可能以不同的方式相关互相关联时:手动标注生命周期。

借用检测器 Borrow Checker

  • Rust 编译器的借用检查器:比较作用域来判断所有借用是否合法。

Demo

{
    let r;
    {
        let x = 5;
        r = &x; // 报错, does not live long enough
    }

    println!("r: {}", r);
}

fn longest(x: &str, y: &str) -> &str { //  报错,expected lifetime parameter
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

生命周期标注语法

&i32        // 引用
&'a i32     // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str()); // `string2` does not live long enough
    }
    println!("The longest string is {}", result);
}


fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str() //  `result` does not live long enough
}

结构体定义中的生命周期标注

struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.')
        .next()
        .expect("Could not find a '.'");
    let i = ImportantExcerpt { part: first_sentence };
}

生命周期省略(Lifetime Elision)

  • 每个引用都有生命周期
  • 需要为使用生命周期的函数或者 Struct 指定生命周期参数

生命周期省略规则

  • Rust引用分析中所编入的模式成为生命周期省略规则(lifetime elision rules)

    • 这些规则无需开发者来遵循
    • 他们是一些特殊的情况,由编译器来考虑
    • 如果你的代码符合这些情况,那么就无需显示标注生命周期
  • 生命周期规则不会提供完整的推动

    • 如果应用规则后,引用的生命周期仍然模糊不清 -> 编译错误
    • 解决方法:添加生命周期标注,表明引用间的互相关系

函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes),而返回值的生命周期被称为 输出生命周期(output lifetimes)。

生命周期的省略的三个规则

  • 建议其使用3个规则在没有显示标注生命周期的情况下,来确定引用的生命周期
    • 规则1应用于输入生命周期
    • 规则23应用于输出生命周期
    • 如果编译器应用完3个规则之后,仍然无法确定生命周期的应用 -> 报错
    • 这些规则适用于 fn定义和impl
  • 规则一:每个引用类型的参数都有自己的生命周期
  • 规则二:每个只有1个输入生命周期参数,那么该生命周期被付给所有的输出生命周期参数。
  • 规则三:如果有多个输入生命周期参数,但其中一个是&self或者是&mut self(是方法),那么self的生命周期会付给所有输出生命周期参数。

静态生命周期

let s: &'static str = "I have a static lifetime.";

结合泛型类型参数、trait bounds 和生命周期

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
    where T: Display
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

十二、编写自动化测试

12.1 如何编写测试

解剖测试函数

  • 测试函数需要使用test属性(attribute)进行标注
    • Attribute就是一段Rust代码的元数据
    • 在函数上加#[test] ,就可以把函数变成测试函数。
  • 使用 cargo test 命令运行所有测试函数
    • Rust 会构建一个Test Runner可执行文件
    • 它会运行标注了test的函数,并报告其运行是否成功
  • 当使用 cargo 创建library项目的时候,会生成一个test module,里面有一个test函数
    • 你可以添加任意数量的test module或者函数

demo

#[cfg(test)] // 表示是测试模块
mod tests {
    #[test] // 表示是测试函数
    #[should_panic(expected = "Guess value must be less than or equal to 100")]
    fn it_works() -> Result<(), String> {
        assert_eq!(2 + 2, 4);
        assert!(true);
        assert!(
            result.contains("Carol"),
            "Greeting did not contain name, value was `{}`", result
        );

    }
}

将 Result<T, E> 用于测试

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() -> Result<(), String> { // 返回 Result
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
    }
}

11.2 控制测试如何运行

// 这里将测试线程设置为 1,告诉程序不要使用任何并行机制
$ cargo test -- --test-threads=1
// 如果你希望也能看到通过的测试中打印的值,截获输出的行为可以通过 --nocapture 参数来禁用:
$ cargo test -- --nocapture
$ cargo test -- --show-output
$ cargo test one_hundred
$ cargo test add // test tests::add_two_and_two, test tests::add_three_and_two

#[test]
#[ignore] // 标注以后,cargo test 不会运行
fn expensive_test() {
    // 需要运行一个小时的代码
}

$ cargo test -- --ignored // 可以单独运行被标记为 ignored

11.3 测试的组织结构

#[cfg(test)]标注

  • tests模块上的#[cfg(test)]标注:
    • 只有运行cargo test 才会编译和运行代码
    • 运行 cargo build则不会
  • 集成测试在不同的目录,它不需要 #[cfg(test)]标注
  • cfg:configration(配置)
    • 告诉Rust下面的条目只有在指定的配置选项下才会被包含
    • 配置选项test:由Rust提供,用来编译和运行测试。
      • 只有 cargo test 才会编译代码,包括模块中的helper函数和#[test]标注的函数

单元测试

#[cfg(test)] // 表示只会在运行 cargo test 才会编译执行下面函数
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

集成测试

  • 创建集成测试:tests目录

  • tests目录下的每个测试文件都是一个单独的crate

    • 需要将被测试库导入
  • 无需标注#[cfg(test)],tests目录被特殊对待

    cargo test 函数名
    cargo test –test 文件名

文件名: tests/integration_test.rs

use adder;

#[test]
fn it_adds_two() {
    assert_eq!(4, adder::add_two(2));
}

十二、一个 I/O 项目:构建一个命令行程序

src/main.rs

use std::env;
use std::process;

use minigrep::Config;

fn main() {
    let config = Config::new(env::args()).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        process::exit(1);
    });

    if let Err(err) = minigrep::run(config) {
        eprintln!("Application error: {}", err);
        process::exit(1);
    };
}

src/lib.rs

use std::error::Error;
use std::{env, fs};

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.filename)?;

    let results: Vec<&str> = if config.case_sensitive {
        search(&config.query, &contents)
    } else {
        search_case_insensitive(&config.query, &contents)
    };

    for line in results {
        println!("{}", line)
    }

    Ok(())
}

fn search<'a>(key: &str, contents: &'a str) -> Vec<&'a str> {
    contents.lines().filter(|line| line.contains(key)).collect()
}

fn search_case_insensitive<'a>(key: &str, contents: &'a str) -> Vec<&'a str> {
    let key = key.to_lowercase();
    contents
        .lines()
        .filter(|line| line.to_lowercase().contains(&key))
        .collect()
}

pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool,
}

impl Config {
    pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };

        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a filename"),
        };

        let case_sensitive = std::env::var("CASE_INSENSITIVE").is_err();
        Ok(Config {
            query,
            filename,
            case_sensitive: case_sensitive,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
        assert_eq!(vec!["safe, fast, productive."], search(query, contents))
    }

    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        )
    }
}

十三、Rust 中的函数式语言功能:迭代器与闭包

13.1 闭包:可以捕获其环境的匿名函数

Demo

fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x|             { x + 1 };
let add_one_v4 = |x|               x + 1  ;

let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5); // 报错

let expensive_closure = |num| {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};

使用带有泛型和 Fn trait 的闭包

struct Cacher<T>
    where T: Fn(u32) -> u32
{
    calculation: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
    where T: Fn(u32) -> u32
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            },
        }
    }
}

let mut c = Cacher::new(|a| a);

let v1 = c.value(1);
let v2 = c.value(2);

assert_eq!(v2, 2);

闭包会捕获其环境

创建闭包时,通过闭包对环境值的使用,Rust推断出具体使用哪个trait

  • 所有闭包都实现了FnOnce
  • 没有移动捕获变量的实现了FnMut,(那些并没有移动被捕获变量的所有权到闭包内的闭包也实现了 FnMut
  • 无需可变访问捕获变量的闭包实现了Fn(不需要对被捕获的变量进行可变访问的闭包则也实现了 Fn

闭包从所在环境捕获值的方式:与函数获得参数三种方式一样:

  1. 取得所有权:FnOnce,消费从周围作用域捕获的变量,闭包周围的作用域被称为其 环境,environment。为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。其名称的 Once 部分代表了闭包不能多次获取相同变量的所有权的事实,所以它只能被调用一次。(所有闭包都实现了FnOnce
  2. 可变借用:FnMut,获取可变的借用值所以可以改变其环境。(没有移动捕获变量的实现了FnMut
  3. 不可变借用:Fn,从其环境获取不可变的借用值 。 (无需可变访问捕获变量的闭包实现了Fn

Demo

let x = 4;
fn equal_to_x(z: i32) -> bool { z == x } // can't capture dynamic environment in a fn item; use the || { ...} closure form instead
// Fn trait
// equal_to_x 闭包不可变的借用了 x(所以 equal_to_x 具有 Fn trait)
let equal_to_x = |z| z == x; 
let y = 4;
assert!(equal_to_x(y));

    
// x 被移动进了闭包,因为闭包使用 move 关键字定义。接着闭包获取了 x 的所有权,同时 main 就不再允许在 println! 语句中使用 x 了。去掉 println! 即可修复问题。
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
println!("can't use x here: {:?}", x); // 报错。value borrowed here after move
let y = vec![1, 2, 3];
println!("equal_to_x(y) = {}",equal_to_x(y));

13.2 使用迭代器处理元素序列

几个迭代方法:

  • iter:在不可变引用上创建迭代器
  • into_iter:创建的迭代器会获得所有权
  • iter_mut:迭代可变的引用

Demo

let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
    println!("Got: {}", val);
}

let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);

let total: i32 = v1_iter.sum();

 // 产生其他迭代器的方法
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);

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

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;

        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

let mut counter = Counter::new();

assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), Some(4));
assert_eq!(counter.next(), Some(5));
assert_eq!(counter.next(), None);


fn using_other_iterator_trait_methods() {
    let sum: u32 = Counter::new()
        .zip(Counter::new().skip(1))
        .map(|(a, b)| a * b)
        .filter(|x| x % 3 == 0)
        .sum();
    assert_eq!(18, sum);
}

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

test bench_search_for  ... bench:  19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench:  19,234,900 ns/iter (+/- 657,200)

结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,我们的目的并不是为了证明他们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。

对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 零成本抽象(zero-cost abstractions)之一,它意味着抽象并不会引入运行时开销,它与本贾尼·斯特劳斯特卢普(C++ 的设计和实现者)在 “Foundations of C++”(2012) 中所定义的 零开销(zero-overhead)如出一辙:

In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.
Bjarne Stroustrup "Foundations of C++"

从整体来说,C++ 的实现遵循了零开销原则:你不需要的,无需为他们买单。更有甚者的是:你需要的时候,也不可能找到其他更好的代码了。
本贾尼·斯特劳斯特卢普 "Foundations of C++"

十四、Cargo 和 Crates.io

采用发布配置自定义构建

$ cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
$ cargo build --release
    Finished release [optimized] target(s) in 0.0 secs


[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3


// add_one 函数的文档注释:

/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}


cargo doc
cargo doc --open

// my_crate crate 整体的文档的注释
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.



// 重新导出

use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}


pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;


use art::PrimaryColor;
use art::mix;

fn main() {
    // --snip--
}

// 创建 Crates.io 账号
$ cargo login abcdefghijklmnopqrstuvwxyz012345

[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

cargo publish

// 为了撤回一个 crate,运行 cargo yank 并指定希望撤回的版本:

$ cargo yank --vers 1.0.1
$ cargo yank --vers 1.0.1 --undo


// 工作空间

[workspace]

members = [
    "adder",
    "add-one",
]

cargo new add-one --lib


$ cargo install ripgrep

// Cargo 的设计使得开发者可以通过新的子命令来对 Cargo 进行扩展,而无需修改 Cargo 本身。如果 $PATH 中有类似 cargo-something 的二进制文件,就可以通过 cargo something 来像 Cargo 子命令一样运行它。像这样的自定义命令也可以运行 cargo --list 来展示出来。能够通过 cargo install 向 Cargo 安装扩展并可以如内建 Cargo 工具那样运行他们是 Cargo 设计上的一个非常方便的优点!

十五、智能指针

  • 指针:一个变量在内存中包含的是一个地址(指向其他数据)
  • Rust 中最常见的指针就是“引用”
  • “引用”:
    • 使用&
    • 借用它指向的值
    • 没有其余开销
    • 最常见的指针类型

智能指针:

  • 行为和指针相似。
  • 有额外的元数据和功能。

引用计数(reference counting)智能指针类型

  • 通过记录所有者的数量,使一份数据被多个所有者同事持有
  • 并且在没有任何使用者时,自动清理数据。

引用和智能指针的不同:引用只是借用数据,智能指针是拥有数据。

  • StringVec<T>
  • 都拥有一片内存区域,且允许用户对其操作

智能指针的实现

  • 智能指针通常使用struct实现,并且实现了:
    • DerefDrop这两个trait
  • Deref trait:允许只能指针struct的实例像引用一样使用
  • Drop trait:允许你自定义当智能指针实例走出作用域时的代码

智能指针

  • Box<T>,用于在堆上分配值
  • Rc<T>,一个引用计数类型,其数据可以有多个所有者
  • Ref<T>RefMut<T>,通过 RefCell<T> 访问( RefCell<T>是一个在运行时而不是在编译时执行借用规则的类型)

15.1 使用 Box 指向堆上的数据

最简单直接的智能指针是 box,其类型是 Box<T>。除了数据被储存在堆上而不是栈上之外,box没有性能损失。不过也没有很多额外的功能。它们多用于如下场景:

  • 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
  • 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
  • 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候

Box<T>是一个指针,Rust知道它需要多少空间,因为:指针的大小是固定的。

Box<T>

  • 只提提供了“间接”存储和heap内存分配的功能。
  • 没有其他额外功能
  • 没有性能开销
  • 适用于需要“间接”存储的场景,例如Cons List
  • 实现了Deref traitDrop trait

Demo

使用 Box<T> 在堆上储存数据
fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}

Box 允许创建递归类型
use crate::List::{Cons, Nil};

enum List { // 嵌套
    Cons(i32, List),
    Nil,
}

fn main() {
    let list = Cons(1, Cons(2, Cons(3, Nil))); // 报错,说递归类型有无限大小。
    println!(list)
}


enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let list = Cons(1,
        Box::new(Cons(2,
            Box::new(Cons(3,
                Box::new(Nil))))));
}

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

  • 实现Deref Trait 使我们可以自定义解引用运算法*的行为。
  • 通过实现Deref,智能指针可以像常规引用一样来处理。

Demo

fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}


struct MyBox<T>(T);// 泛型的元组结构体

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}


use std::ops::Deref;


impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

// *(y.deref())

let m = MyBox::new(String::from("Rust"));
//  &m &MyBox<String>
// deref &Striing
// deref &str

hello(&m);
hello(&(*m)[..]);

fn hello(name: &str) {
    println!("Hello, {}!", name);
}

解引用强制转换如何与可变性交互

Rust 在发现类型和 trait 实现满足三种情况时会进行解引用强制转换:

  • T: Deref<Target=U> 时从 &T&U
  • T: DerefMut<Target=U> 时从 &mut T&mut U
  • T: Deref<Target=U> 时从 &mut T&U

函数和方法的隐私解引用转化(Deref Coercion)

  • 隐式解引用转化(Deref Coercion):
    • Deref Coercion 可以吧T的引用转化为T经过Deref操作后生成的引用
  • 当把某类型的引用传递给函数或方法时,但它的类型与定义的参数类型不匹配:
    • Deref Coercion就自动发生
    • 编译器会对deref进行一系列调用,来把它转为所需的参数类型
      • 在编译时候完成,没有额外性能开销

15.3. 使用 Drop Trait 运行清理代码

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer { data: String::from("my stuff") };
    let d = CustomSmartPointer { data: String::from("other stuff") };
    println!("CustomSmartPointers created.");
   
   // 输出 
   CustomSmartPointers created.
    Dropping CustomSmartPointer with data `other stuff`!
    Dropping CustomSmartPointer with data `my stuff`!

}

// 通过 std::mem::drop 提早丢弃值

fn main() {
let c = CustomSmartPointer { data: String::from("some data") };
println!("CustomSmartPointer created.");
drop(c);
println!("CustomSmartPointer dropped before the end of main.");

CustomSmartPointer created.
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main.

15.4 Rc 引用计数智能指针

有时候,一个值会有多个所有者(这种场景Box<T>不支持),比如双向链表。为了支持多重所有权:Rc<T>:

  • reference counting 引用计数
  • 追踪所有值的引用
  • 0 个引用的时候,该值会被清理掉
  • 需要在heap上分配数据,这些数据被程序的多个部分读写(只读),但在编译时无法确定哪个部分最后使用完这些数据。
  • Rc<T> 只能用于单线程场景
  • Rc::clone(&a) 函数:增加引用计数
  • Rc::strong(&a)、Rc::weak(&a) 获得引用计数
  • Rc<T>,通过不可变引用,你可以在程序不同部分之间共享只读数据

Demo

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}

let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
    let c = Cons(4, Rc::clone(&a));
    println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));

// 输出
count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2

15.5 RefCell 和内部可变性模式

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

借用规则:在任何给定的时间你,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用。

RefCell<T>Box<T> 区别

  • Box<T>编译阶段强制代码遵守借用规则,否则就编译报错。 编译阶段的特点:尽早暴露问题、没有任何运行时开销、对大多数场景是最佳选择、是Rust默认行为
  • RefCell<T>只会在运行时检测借用规则,否则就panic。运行阶段特点:问题可能发生在生成环境、因借用计数产生许些性能损失、实现某些特定的内存安全场景(不变环境中修改自身数据)

使用RefCell<T>在运行时记录借用信息

  • borrow方法
    • 返回智能指针Ref<T>,它实现了 Deref
  • borrow_mut 方法
    • 返回智能指针RefCell<T>,它实现了Deref

Demo

fn main() {
    let x = 5;
    let y = &mut x; // 报错
}


#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

    let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}

15.6 引用循环与内存泄漏

循环引用,导致的内存泄露

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));

    println!("a initial rc count = {}", Rc::strong_count(&a));
    println!("a next item = {:?}", a.tail());

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

    println!("a rc count after b creation = {}", Rc::strong_count(&a));
    println!("b initial rc count = {}", Rc::strong_count(&b));
    println!("b next item = {:?}", b.tail());

    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b);
    }

    println!("b rc count after changing a = {}", Rc::strong_count(&b));
    println!("a rc count after changing a = {}", Rc::strong_count(&a));

    // Uncomment the next line to see that we have a cycle;
    // it will overflow the stack
    // println!("a next item = {:?}", a.tail());
}

使用weak

use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}


fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

可视化 strong_count 和 weak_count 的改变

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );

    {
        let branch = Rc::new(Node {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });

        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

        println!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch),
        );

        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf),
        );
    }

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
}

leaf strong = 1, weak = 0
branch strong = 1, weak = 1
leaf strong = 2, weak = 0
leaf parent = None
leaf strong = 1, weak = 0

十六、无畏并发

16.1. 使用线程同一时间运行代码

使用 spawn 创建新线程

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().unwrap();
}

线程与 move 闭包

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn( move || {
        println!("Here's a vector: {:?}", v);
    });

    drop(v); // oh no!
    handle.join().unwrap();
}

16.2. 使用消息传递在线程间通信

通道与所有权转移

use std::thread;
use std::sync::mpsc;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap(); // 这里会发生所有权变更
        // println!("val is {}", val); // value borrowed here after move
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

发送多个值并观察接收者的等待

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];

        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });

    for received in rx {
        println!("Got: {}", received);
    }
}

通过克隆发送者来创建多个生产者

let (tx, rx) = mpsc::channel();

let tx1 = tx.clone();
thread::spawn(move || {
    let vals = vec![
        String::from("hi"),
        String::from("from"),
        String::from("the"),
        String::from("thread"),
    ];

    for val in vals {
        tx1.send(val).unwrap();
        thread::sleep(Duration::from_secs(1));
    }
});

thread::spawn(move || {
    let vals = vec![
        String::from("more"),
        String::from("messages"),
        String::from("for"),
        String::from("you"),
    ];

    for val in vals {
        tx.send(val).unwrap();
        thread::sleep(Duration::from_secs(1));
    }
});

for received in rx {
    println!("Got: {}", received);
}

16.3. 共享状态并发 Lock

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }

    println!("m = {:?}", m);
}

在线程间共享 Mutex

use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();

            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

16.4. 使用Sync 与 Send Trait 的可扩展并发

然而有两个并发概念是内嵌于语言中的:std::marker 中的 SyncSend trait

通过 Send 允许在线程间转移所有权

  • 实现Send trait 的类型可在线程间转移所有权
  • Rust中几乎所有的类型都实现了Send
    • Rc<T> 没有实现 Send,它只用于单线程情景
  • 任何完全由 Send 类型组成的类型也标记为Send
  • 除了原始指针之外,几乎所有的基础类型都是Send

Sync 允许多线程访问

  • 实现Sync的类型可以安全的被多个线程引用
  • 也就是说:如果TSync,那么&T就是Send
    • 引用可以被安全的送往另外一个线程
  • 基础类型都是Sync
  • 完全由Sync类型组成的类型也是Sync
    • Rc<T>不是Sync
    • RefCell<T>Cell<T> 家族也不是Sync
    • Mutex<T>Sync

手动实现 Send 和 Sync 是不安全的

通常并不需要手动实现 SendSync trait,因为由 SendSync 的类型组成的类型,自动就是 SendSync 的。因为他们是标记 trait,甚至都不需要实现任何方法。他们只是用来加强并发相关的不可变性的。

十七、 Rust 的面向对象编程特性

Trait 对象执行的是动态派发

  • trait约束作用于泛型时,Rust编译器会执行单态化:
    • 编译器会为我们用来替换泛型类型参数的每一个具体类型生成对应的函数和方法的非泛型实现。
  • 通过单态化生成的代码会执行静态派发(static dispatch),在编译过程中确定调用的具体方法
  • 动态派发(dynamic dispatch):
    • 无法在编译过程中确定你调用的究竟是哪一种方法
    • 编译器会产生额外的代码以便在运行时找出希望调用的方法
  • 使用trait对象,会执行动态派发:
    • 产生运行时开销
    • 阻止编译器内联方法代码,使得部分优化操作无法进行

Demo

pub trait Draw {
    fn draw(&self);
}

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>, // Box<dyn Draw> 实现动态派发
    // pub components: Vec<T>, // 用泛型的话,Vec类型只能是一种

}


impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}



use gui::{Screen, Button};

fn main() {
    let screen = Screen {
        components: vec![
            Box::new(SelectBox {
                width: 75,
                height: 10,
                options: vec![
                    String::from("Yes"),
                    String::from("Maybe"),
                    String::from("No")
                ],
            }),
            Box::new(Button {
                width: 50,
                height: 10,
                label: String::from("OK"),
            }),
        ],
    };

    screen.run();
}

Trait 对象必须保障对象安全

  • 只能把满足对象安全(object-safe)的trait转化为trait对象
  • Rust采用一系列规则来判断某个对象是否安全,只需记住两条:
    • 方法的返回类型不是Self
    • 方法中不包含任何泛型类型参数

Demo

pub trait Clone {
    fn clone(&self) -> Self;
}

pub struct Screen {
    pub components: Vec<Box<dyn Clone>>, //  the trait `std::clone::Clone` cannot be made into an object
}

完整 Demo

fn main() {
    let mut post = Post::new();

    post.add_text("I ate a salad for lunch today");

    let post = post.request_review();

    let post = post.approve();

    assert_eq!("I ate a salad for lunch today", post.content());
}

pub struct Post {
    content: String,
}

pub struct DraftPost {
    content: String,
}

impl Post {
    // 此时返回DraftPost类型,也就无法调用content,没有Post对象
    pub fn new() -> DraftPost {
        DraftPost {
            content: String::new(),
        }
    }

    pub fn content(&self) -> &str {
        &self.content
    }
}

impl DraftPost {
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
    // 执行后原来的DraftPost对象就释放了,因为入参是self
    pub fn request_review(self) -> PendingReviewPost {
        PendingReviewPost {
            content: self.content,
        }
    }
}

pub struct PendingReviewPost {
    content: String,
}

// 同样没有content方法
impl PendingReviewPost {
    pub fn approve(self) -> Post {
        Post {
            content: self.content,
        }
    }
}

十八、模式匹配

模式有两种形式:refutable(可反驳的)和 irrefutable(不可反驳的)。能匹配任何传递的可能值的模式被称为是 不可反驳的(irrefutable)。一个例子就是 let x = 5; 语句中的 x,因为 x 可以匹配任何值所以不可能会失败。对某些可能的值进行匹配会失败的模式被称为是 可反驳的(refutable)。一个这样的例子便是 if let Some(x) = a_value 表达式中的 Some(x);如果变量 a_value 中的值是 None 而不是 Some,那么 Some(x) 模式不能匹配。

函数参数、 let 语句和 for 循环只能接受不可反驳的模式

Demo

let mut stack = Vec::new();

stack.push(1);
stack.push(2);
stack.push(3);

while let Some(top) = stack.pop() {
    println!("{}", top);
}

let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
    println!("{} is at index {}", value, index);
}

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

if let Some(x) = some_option_value {
    println!("{}", x);
}

// 匹配命名变量
fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(y) => println!("Matched, y = {:?}", y),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {:?}", x, y);
}

// 多个模式
let x = 1;

match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}

let x = 5;

match x {
    1..=5 => println!("one through five"),
    _ => println!("something else"),
}

let x = 'c';

match x {
    'a'..='j' => println!("early ASCII letter"),
    'k'..='z' => println!("late ASCII letter"),
    _ => println!("something else"),
}


// 解构结构体
fn main() {
    let p = Point { x: 0, y: 7 };

    match p {
        Point { x, y: 0 } => println!("On the x axis at {}", x),
        Point { x: 0, y } => println!("On the y axis at {}", y),
        Point { x, y } => println!("On neither axis: ({}, {})", x, y),
    }
}

// 解构枚举
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.")
        }
        Message::Move { x, y } => {
            println!(
                "Move in the x direction {} and in the y direction {}",
                x,
                y
            );
        }
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!(
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
    }
}

// 用 .. 忽略剩余值
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {}, {}", first, last);
        },
    }
}

// 匹配守卫提供的额外条件
let num = Some(4);

match num {
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}

let x = 4;
let y = false;

match x {
    4 | 5 | 6 if y => println!("yes"),
    _ => println!("no"),
}

// @ 绑定
enum Message {
    Hello { id: i32 },
}

let msg = Message::Hello { id: 5 };

match msg {
    Message::Hello { id: id_variable @ 3..=7 } => {
        println!("Found an id in range: {}", id_variable)
    },
    Message::Hello { id: 10..=12 } => {
        println!("Found an id in another range")
    },
    Message::Hello { id } => {
        println!("Found some other id: {}", id)
    },
}

十九、高级特征

19.1 unsafe Rust

目前为止讨论过的代码都有 Rust 在编译时会强制执行的内存安全保证。然而,Rust 还隐藏有第二种语言,它不会强制执行这类内存安全保证:这被称为 不安全 Rustunsafe Rust)。它与常规 Rust 代码无异,但是会提供额外的超能力。

unsafe Rust存在的原因:

  • 静态分析是保守的。
    • 使用unsafe Rust:我知道自己在做什么,并承担相应风险
  • 计算机硬件本身就是不安全的,Rust需要能够进行底层系统编程

unsafe Rust的超能力:

  • 使用unsafe 关键字来切换到unsafe Rust,开启一个快,里面放着unsafe代码
  • unsafe Rust里可以执行的四个动作(unsafe超能力):
    • 解引用原始指针
    • 调用unsafe函数或方法
    • 访问或修改可变的静态变量
    • 实现unsafe trait
  • 注意:
    • unsafe 并没有关闭借用检测或者停用其它安全检查
    • 任何内存安全相关的错误必须停留在unsafe快里
    • 尽可能的隔离unsafe代码,最好封装在安全的抽象里,提供安全的API

解引用原始指针

  • 原始指针
    • 可变的:*mut T
    • 不可变的:*const T。意味着指针在解引用后,不能直接对其进行赋值
    • 注意:这里的*不是解引用符号,它是类型名的一部分。
  • 与引用不同,原始指针:
    • 允许通过同事具有不可变和可变的指针或多个指向同一位置的可变指针来忽略借用规则。
    • 无法保证能指向合理的内存
    • 允许为null
    • 不实现任何自动清理
  • 放弃保证安全,换取更好的性能/与其他语言或者硬件接口的能力
  • 为什么要用原始指针?
    • C语音进行接口
    • 构建借用检查器无法理解的安全抽象

Demo

let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
let address = 0x012345usize;
let r = address as *const i32;

unsafe {
    println!("r1 is: {}", *r1);
    println!("r2 is: {}", *r2);
    println!("r is: {}", *r); // panic
} 

// 示例 19-6 展示了如何使用 unsafe 块,裸指针和一些不安全函数调用来实现 split_at_mut:
use std::slice;

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();

    assert!(mid <= len);


  // (&mut slice[..mid], &mut slice[mid..]) 
  // cannot borrow `*slice` as mutable more than once at a time

    unsafe {
        (slice::from_raw_parts_mut(ptr, mid),
         slice::from_raw_parts_mut(ptr.add(mid), len - mid))
    }
}

使用 extern 函数调用外部代码

  • extern关键字:简化创建和使用外部函数接口(FFI)的过程
  • 外部函数接口(FFIForeign Function Interface):它运行一种编程语言定义函数,并让其它编程语言能调用这些函数
  • 应用程序二进制接口(ABIApplication Binary Interface)定义函数在汇编层面的调用方式
  • CABI是最常见的ABI,它遵循C语音的ABI

Demo

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

// 从其它语言调用 Rust 函数
#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}

访问或修改可变静态变量

静态变量

  • 命名规范: SNAKE_CASE
  • 必须标注类型
  • 静态变量只能存储'static生命周期的引用,无需显示标注
  • 访问不可变静态变量是安全的

常量和不可变静态变量区别:

  • 静态变量:有固定的内存地址,使用它的值总会访问同样的数据
  • 常量:允许使用它们的时候对数据进行复制
  • 静态常量:可以是可变的,访问和修改静态可变变量是不安全unsafe的。

Demo

// 读取或修改一个可变静态变量是不安全的
static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}

实现不安全 trait

unsafe 的另一个操作用例是实现不安全 trait。当 trait 中至少有一个方法中包含编译器无法验证的不变式(invariant)时 trait 是不安全的。可以在 trait 之前增加 unsafe 关键字将 trait 声明为 unsafe,同时 trait 的实现也必须标记为 unsafe.

unsafe trait Foo {
    // methods go here
}

unsafe impl Foo for i32 {
    // method implementations go here
}

19.2. 高级 trait

关联类型在 trait 定义中指定占位符类型

通过关联类型,则无需标注类型,因为不能多次实现这个 trait。对于示例使用关联类型的定义,我们只能选择一次 Item 会是什么类型,因为只能有一个 impl Iterator for Counter。当调用 Counternext 时不必每次指定我们需要 u32 值的迭代器。

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {}    
}

默认泛型类型参数和运算符重载

use std::ops::Add;

#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
               Point { x: 3, y: 3 });
}

这里默认泛型类型位于 Add trait 中。这里是其定义:

trait Add<RHS=Self> {
    type Output;

    fn add(self, rhs: RHS) -> Self::Output;
}

这看来应该很熟悉,这是一个带有一个方法和一个关联类型的 trait。比较陌生的部分是尖括号中的 RHS=Self:这个语法叫做默认类型参数(default type parameters)。RHS 是一个泛型类型参数(“right hand side” 的缩写),它用于定义 add 方法中的 rhs 参数。如果实现 Add trait 时不指定 RHS 的具体类型,RHS 的类型将是默认的 Self 类型,也就是在其上实现 Add 的类型。

// 在 Millimeters 上实现 Add,以便能够将 Millimeters 与 Meters 相加
// 我们指定 impl Add<Meters> 来设定 RHS 类型参数的值而不是使用默认的 Self。

use std::ops::Add;

struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

完全限定语法与消歧义:调用相同名称的方法

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

fn main() {
    let person = Human;
    Pilot::fly(&person);
    Wizard::fly(&person);
    person.fly(); // waving arms furiously
}


trait Animal {
    fn baby_name() -> String;
}

struct Dog;

impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}

impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}

fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

父 trait 用于在另一个 trait 中使用某 trait 的功能

outline_print 的实现中,因为希望能够使用 Display trait 的功能,则需要说明 OutlinePrint 只能用于同时也实现了 Display 并提供了 OutlinePrint 需要的功能的类型。

use std::fmt;

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

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

use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
    println!("w = {}", w);
}

19.3. 高级类型

类型别名用来创建类型同义词

type Kilometers = i32;

let x: i32 = 5;
let y: Kilometers = 5;

println!("x + y = {}", x + y);

type Thunk = Box<dyn Fn() + Send + 'static>;

let f: Thunk = Box::new(|| println!("hi"));

fn takes_long_type(f: Thunk) {
    // --snip--
}

fn returns_long_type() -> Thunk {
    // --snip--
}

fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
    // --snip--
}

fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
    // --snip--

type Result<T> = std::result::Result<T, std::io::Error>;

从不返回的 never type

fn bar() -> ! {//报错 , 返回的是“()”不是“!”
   
}

let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue, // 这个返回的就是 "!"
};

impl<T> Option<T> {
    pub fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic!("called `Option::unwrap()` on a `None` value"),// 这个返回的就是 "!"
        }
    }
}

print!("forever ");

loop {
    print!("and ever ");
}// 这个返回的就是 "!"

动态大小类型和 Sized trait

因为 Rust 需要知道例如应该为特定类型的值分配多少空间这样的信息其类型系统的一个特定的角落可能令人迷惑:这就是 动态大小类型(dynamically sized types)的概念。这有时被称为 DSTunsized types,这些类型允许我们处理只有在运行时才知道大小的类型。

为了处理 DSTRust 有一个特定的 trait 来确定一个类型的大小是否在编译时可知:这就是 Sized trait。这个 trait 自动为编译器在编译时就知道其大小的类型实现。另外,Rust 隐式的为每一个泛型函数增加了 Sized bound。也就是说,对于如下泛型函数定义:

fn generic<T>(t: T) {
    // --snip--
}

实际上被当作如下处理:

fn generic<T: Sized>(t: T) {
    // --snip--
}

泛型函数默认只能用于在编译时已知大小的类型。然而可以使用如下特殊语法来放宽这个限制:

fn generic<T: ?Sized>(t: &T) {
    // --snip--
}

?Sized trait boundSized 相对;也就是说,它可以读作 T 可能是也可能不是 Sized 的。这个语法只能用于 Sized ,而不能用于其他 trait

19.4. 高级函数与闭包

函数指针

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {}", answer);
}

let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
    .iter()
    .map(ToString::to_string) //     .map(|i| i.to_string())
    .collect();

返回闭包

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

19.5. 宏

使用 macro_rules! 的声明宏用于通用元编程

let v: Vec<u32> = vec![1, 2, 3];

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

// 等同下面代码
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec

后面使用装饰器模块给函数添加代码的实例就不添加了。

二十、最后的项目: 构建多线程 Web 服务器