rust book 学习笔记

19-07-12 -> 19-08-15

Hell world

开始 & 安装

curl https://sh.rustup.rs -sSf | sh

网络真的是僵硬,下载下一年, 我用远端服务器就很快下了 真是艹了,我在远端 都跟着教程看了两章+尝试demo了,本地的build下载还没下好

国内的话 配置~/.cargo/config如下会稍微快一些,吐槽

1
2
3
4
5
6
7
[registry]
index = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index/"
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.sjtug]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index/"

更新 上面不能用的话看这里 https://lug.ustc.edu.cn/wiki/mirrors/help/rust-crates

IDE/vim配置

我是用的vim + spf13-vim上加自己配置的 ~/.vimrc.local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function CompileByFileType()
if &filetype == "cpp"
"!clang++ -o "%<" "%" -std=gnu++17 -O2 -g -Wall -Wcomma
!clang++ -o "%<" "%" -std=gnu++17 -g -Wall -Wcomma
"!g++ -o "%<" "%" -std=gnu++14 -O2 -g -Wall
elseif &filetype == "c"
!gcc -o "%<" "%" -O2 -g
elseif &filetype == "python"
!python3 "%"
elseif &filetype == "go"
!go run "%"
+ elseif &filetype == "rust"
+ !cargo run
else
echo "UNKNOWN FILETYPE : &filetype"
endif
endfunction

nnoremap <F9> :call CompileByFileType() <CR>

其它的话 据说Clion + rust插件 不错

入门命令

rustup docs --book

66666 题外话,我觉得现代的游戏 除了游戏本身还需要 背景,目标玩家定位,目标游戏类型定位,以及自身完备性和营销。自身完备性比如观战,历史记录等,去年出的 刀牌 从6万峰值到现在上线就是世界前100,我认为其中缺少的就是完备性。而rust 带一个 这个文档66666

有了教程,那我yyp一点 教程里的总结吧

  • 单个rs:rustc main.rs
  • build project using cargo build or cargo check.
  • build + run a project cargo run.
  • Instead of saving the result of the build in the same directory as our code, Cargo stores it in the target/debug directory.
  • cargo build –release

依赖相关

如果玩过npm可以感觉到和package.json 和对应的lock相似

首先Cargo.toml 中加入需要依赖的库和版本描述,如rand = "0.3.14",会匹配能匹配的某个版本

然后cargo build

Cargo.lock会保存首次build的各个库的版本

如果希望升级cargo update

基本语法+写一个猜测数游戏

猜数

依赖

Cargo.toml添加

1
2
[dependencies]
rand = "0.3.14"

main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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);

// println!("The secret number is: {}", secret_number);

loop {

println!("Please input your guess.");

let mut guess = String::new();

io::stdin().read_line(&mut guess)
.expect("Failed to read line");

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;
}
}

}
}

immutable和mutable

default variables are immutable : 默认的变量是不变的 =。=????

const的区别 const 的值只能由const表达式提供,所以可以看作 immutable是那种 从宏观上看是会变的,例如函数的入参,但对于函数内部可能是不变的?

1
2
3
4
5
let x = 5;

let mut x = 5;

const x : u32 = 100_000;

shadow掉。。。。。。。。。

1
2
3
let x = 5;
let x = x + 1; // let 不能省略
let x = x * 2;

match

1
2
3
4
5
match 表达式/值 {
1 => 表达式/{表达式,...},
2 => ...,
...
}

明确的类型

let guess = "42".parse().expect("...")

没有指定guess的类型,而parse是有不同类型的方法,正确的办法let guess: u32

基础类型: 浮点数 整数 布尔 字符(Rust’s char type is four bytes in size and represents a Unicode Scalar Value,比ASCIIchar定义多 )

整数

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

进制直接描述法

Number literals Example
十进制 98_222
十六进制 0xff
八进制 0o77
二进制 0b1111_0000
Byte (u8 only) b’A’

在Debug模式下,overflow会panic,在release模式下, 会把overflow wrap掉

浮点数

默认f64,可选 f32

tuple & array

Tuple

1
2
3
let tup: (i32, f64, u8) = (500.6.4,1);
let (x,y,z) = tup;
let firstvalue = tup.0; // 有点意思啊,c++的话 我印象中是 get<index>(某个tuple)

Array 定长同类型

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

数组越界会报错 :-)

函数

let 语句没有返回 所以不能写let x= (let y =6)这种代码

括号是一个表达式可以写成

1
2
3
4
5
6
7
8
9
10
fn main() {
let x = 5;

let y = {
let x = 3;
x + 1
};

println!("The value of y is: {}", y);// 输出4
}

函数定义法fn 函数名(参数1:类型,参数2:类型) -> 返回类型 {...}

if

1
2
3
4
5
6
7
if 表达式 {

} else if 表达式 {

} else {

}

和c++不同 ,rust的类型要求,不能非零,一定要布尔

有趣的是 花括号时表达式 所以三元运算 可以写成x = if true { 2 } else { 3 }

但是 返回类型如果有不同类型 let number = if condition { 5 } else { "six" } 会报错

循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
loop {
// code...
}

let returnvaluefromloop = loop {
// ...
break the value you want return
// ...
};

while condition {
// code...
}

let arr = [11,22];

for element in arr.iter() {
println!("the value is: {}", element);
}

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

Ownership & slice

这使rust不需要gc 让内存安全得以保证,这一章讲ownership工作

对于内存使用,某些语言用gc,某些语言需要用户自己明确的管理,rust使用memory is managed through a system of ownership with a set of rules that the compiler checks at compile time.所以对运行时速度不会有影响

这也是让rust独特的关键之一

介绍了一下 数据结构意义上的 栈和堆

Ownership rules

  1. 每一个value 都有一个对应的变量是它的owner
  2. 同一个时间点 只会有一个owner
  3. owner出了对应的scope,值会被弃用

变量的有效有两个关键的 时间点,入和出

  • When s comes into scope, it is valid.
  • It remains valid until it goes out of scope.

字面量string和 String::from的不同是,一个硬编码,一个运行时,如果按c++反编译回来理解,的话,有的char数组直接放在栈上,有的是一个公用地址放的字符,而String就是用多少是啥样了

rust通过 takes a different path: the memory is automatically returned once the variable that owns it goes out of scope

类似于 C++中的Resource Acquisition Is Initalization (RAII)

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

string

然后String赋值 可以看做指针赋值大概

string

1
2
3
4
let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);

以上代码会报错

string move

正确的

1
2
3
4
let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2);

然后 原始类型都是默认copyint

除此以Tuples, if they only contain types that are also Copy. For example, (i32, i32) is Copy, but (i32, String) is not.

然后一个反 其它语言编程常识的!

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let s = String::from("hello"); // s comes into scope

takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here

takes_ownership(s); // 然而 在调用函数时 已经被drop了,现在 的s已经被move掉了 不可用
}

fn takes_ownership(some_string: String) { // some_string comes into scope
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
// memory is freed.

rust的报错真好看

一个 非原始类型只能用一次?解决方案

1
2
3
fn 函数名(a_str: String) -> String{
a_str
}

多返回参数fn 函数名(参数) -> (String, usize) { ... (xxx,yyy)}

为了不用每一个 函数都这样写 来维持控制权, rust可以用引用来解决

reference

1
2
3
4
5
6
7
8
9
10
11
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.len()
}

原理就是 函数并没有 获得 该Stringownership,而是只有其引用的,

所以如果我们 对&ss.push_str(", world");则会报错,不允许修改 默认 引用是 immutable

如果要修改fn 函数名(参数名: &mut String) { 参数名.push_str("sdafsdfasd")}

限制

对于同一个变量

一个scope里至多只能有一个 mutable 的引用

在有immutable引用的scope里,也不能有mutable的引用

多个immutable的引用时可以的,根据drop意义,以下是可行的

1
2
3
4
5
6
7
8
9
10
11
12
13
let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem

// 但是r3不能移动到这个位置,因为有 immutable时候不能有mutable 的限制

println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point

// 之所以可以,是因为drop的原因,这里看作是所有immutable都已经drop掉了 ,所以他们的scope没有重叠
let r3 = &mut s; // no problem
println!("{}", r3);

总结描述就是任意一行代码上的scope对任意一个变量 要求immutable*|mutable?

rust 编译时会检测 dangling references [如函数内创建的临时值,传出引用,会导致外部并不可用

slice

没有ownership的类型,slice让你 指向一个连续序列的某一段

1
2
3
4
5
6
7
let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];
let sprefix = &s[..5];
let ssuffix = &s[6..];
let ss = &s[..];

slice

注意在utf-8下 取的slice坐标应该是合法的分割坐标

String slice类型是&str,回顾之前字符串字面量

let s= "hell world";, 这里的s的类型就是&str

让函数接受参数是&str 能让代码更加可用

其它 slice,如数值数组的slice,写法和上面类似 类型是&[i32]

Struct

定义

1
2
3
4
5
struct struct名{
字段名1:字段类型1,
字段名2:字段类型2,
...
}

初始化

1
2
3
4
let 变量名 = struct名{
字段名1: 值1,
字段名2: 值2,
}

注意的是,rust只允许整体上mutable or immutable,不允许 按字段来划分

在参数和字段保持一致时可以略写

1
2
3
4
5
6
7
8
fn build_user(email: String, username: String) -> User {
User {
email: email, // 这里可以略写作 email,
username: username, // 这里可以略写作 username,
active: true,
sign_in_count: 1,
}
}

通过其它的struct的部分字段一致造新的struct

1
2
3
4
5
6
7
8
9
10
11
12
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};
// 可以写作
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};

tuple structs! 没有字段名的struct

1
2
3
4
5
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Unit-Like:

注意到上面的类型是String,是因为我们希望结构体 有对应String的Ownership

如果用 slice,则会因为rust的生命周期管理而报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}

fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}

输出结构体 // rust 的报错真的有意思 还有建议使用

1
2
3
4
5
6
7
8
9
10
11
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

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

println!("rect1 is {:?}", rect1); // 或者 {:#?} 可以 格式化样式输出
}

struct方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn area(&self,other:&Rectangle) -> u32 { // 编译器知道self类型,我们只希望 操作 不希望take ownership,否则 &mut self
// ...
self.width * self.height
}
//}
// 可以拆分到多个impl 合法的语法 但不建议
//impl
fn square(size:u32) -> Rectangle{ //
Rectangle { width:size,height:size}
}
}

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

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

let square1 = Rectangle::square(3);
}

相对于 C++/C-> == (* ).

rust有自动 referencing和dereferencing ,都可以简写为.

Enums and Option and Pattern Matching

定义枚举类型和成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
enum 枚举类型{
枚举成员1,
枚举成员2,
}

let v1 = 枚举类型::枚举成员1

枚举 的每个成员可以不同类型

enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}

let home = IpAddr::V4(127, 0, 0, 1);

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

// 表转库的使用方式
struct Ipv4Addr {
// --snip--
}

struct Ipv6Addr {
// --snip--
}

enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}

enum 也可以 impl方法函数

rust中不存在的概念下的枚举量 Option:

1
2
3
4
5
6
7
8
enum Option<T> {
Some(T),
None,
}
let some_number = Some(5);
let some_string = Some("a string");

let absent_number: Option<i32> = None;

Option 的意味,这 编写者,对于Option中的值并不确定 如 Option

而直接的i8意味着 一定有有效值,所以i8Option<i8>类型相加会报错,在使用Option中的值时, 需要把Option<T>中的T提取出来

Match 看上去像其它语言的switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
// 支持None的+1

fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}

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

It’s a bit tricky at first, but once you get used to it, you’ll wish you had it in all languages. It’s consistently a user favorite. 自吹自擂

然而 如果你上面省去None,编译会报错,这就和switch有了差异,相当于强制 处理所有

这里有_可以匹配所有,不希望处理的可以在match最后_ => ()

简洁控制流if let

1
2
3
4
5
6
7
8
9
10
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
// 可以改为

if let Some(3) = some_u8_value {
println!("three");
}

Managing Growing Projects with Packages, Crates, and Modules

  • Packages: A Cargo feature that lets you build, test, and share crates
  • Crates: A tree of modules that produces a library or executable
  • Modules and use: Let you control the organization, scope, and privacy of paths
  • Paths: A way of naming an item, such as a struct, function, or module

Packages & Crates

crate = binaray | library

package = (library crate)?+(binaray crate)*

回顾cargo new my-project创建出来的。

会发现Cargo.toml并不会包含src/main.rs

是因为默认 src/main.rs会是binary的入口,会编译产生和project同名的二进制文件,同样src/lib.rs会是crate的library的入口

cargo会交给rustc 来build library或binary

A package can have multiple binary crates by placing files in the src/bin directory: each file will be a separate binary crate.

如果像第二章里面引入rand,它会提供一个trait叫Rng

而如果我们自己也写了一个struct Rng,那么如果要用rand的,使用rand::Rng

Defining Modules to Control Scope and Privacy

  • use keyword that brings a path into scope
  • pub keyword to make items public.
  • as

文档这里用写一个餐厅举例

cargo new --lib restaurant

src/lib.rs :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mod front_of_house { // 用mod 定义一个module
mod hosting { // module 可以嵌套
fn add_to_waitlist() {}

fn seat_at_table() {}
}

mod serving {
fn take_order() {}

fn serve_order() {}

fn take_payment() {}
}
}

记得把#[cfg(test)]去掉

这样分割后,别人阅读代码可以按照group分割找,相对于完全看一遍代码更友好

Path

  • 绝对路径create开头的
  • 相对路径 self,super 或 当前module中的一个identifier

例如对上面

1
2
3
4
5
6
7
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();

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

注意以上代码并不能通过编译,因为会有hosting is private

Modules aren’t only useful for organizing your code, they also define Rust’s privacy boundary

module的所有成员(functions, methods, structs, enums, modules, and constants) 默认全是private

父级别不能使用子级别的内部private方法,但是子级别可以用它祖先的方法…废话

如果希望能被外部调用 上面的mod应该 改为

1
2
3
4
5
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

因为相函数和front_of_house的位置是 同层级关系,所以最外的mod不需要pub

super

1
2
3
4
5
6
7
8
9
10
fn serve_order() {}

mod back_of_house {
fn fix_incorrect_order() {
cook_order(); // 同级别
super::serve_order(); // 父级别
}

fn cook_order() {}
}

如果要让一个mod中的 任何一个public,需要在每个需要的地方加上pub

use

类似C++ using namespace ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

use crate::front_of_house::hosting; // bring hosting module into the scope
// 相对路径写法 需要加上self::
// use self::front_of_house::hosting;

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

然后上面这样是idiom的,虽然也可以use crate::front_of_house::hosting::add_to_waitlist然后 之后就省去hosting::,但根据大家书写习惯,建议这样,接受这个建议

不过无论怎么设计,随着3方库的越来越多,总会有重名=。=解决方案可以用as

1
2
3
4
5
6
7
8
9
10
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
// --snip--
}

fn function2() -> IoResult<()> {
// --snip--
}

pub use

pub use crate::front_of_house::hosting;

这样对于外部代码可以用 hosting::add_to_waitlist来调用

package 引入

回想之前rand的 在Cargo.toml中添加和代码中引入

use std::{cmp::Ordering, io}; 内嵌式引入

同时引入std::iostd::io::Write: use std::io::{self, Write};

use std::collections::*;

This use statement brings all public items defined in std::collections into the current scope. Be careful when using the glob operator! Glob can make it harder to tell what names are in scope and where a name used in your program was defined.

模块分化

当项目大了之后模块也会需要分化

src/lib.rs

1
2
3
4
5
6
7
8
9
mod front_of_house;// 分号结束

pub use crate::front_of_house::hosting;

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

对于子模块

写法1:

src/front_of_house.rs

1
2
3
pub mod hosting {
pub fn add_to_waitlist() {}
}

写法2:

src/front_of_house.rs

1
pub mod hosting;

src/front_of_house/hosting.rs

1
pub fn add_to_waitlist() {}

Common Collections

C++ STL即视感?

  • A vector 允许你储存 变化数量的 值
  • A string is a collection of characters. 之前提到过 这章会更深入的讲解
  • A hash map allows you to associate a value with a particular key. 是 map的一个具体的实现版本.

Vectors

let v: Vec<i32> = Vec::new();

vector不知道你要存什么,他使用 generics(泛型)实现的Vec<T> // 第10章会详细讲泛型

直接用值 let v = vec![1, 2, 3];

操作

1
2
3
4
5
6
7
8
9
10
11
12
13
let mut v = Vec::new();

v.push(5); // push.....

// read element

let third: &i32 = &v[2]; // 需要多一个&符号
println!("The third element is {}", third);

match v.get(2) { // 返回的是 Option<&T>
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}

When the vector gets dropped, all of its contents are also dropped

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

let first = &v[0]; // immutable borrow occurs here

v.push(6); // mutable borrow occurs here

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

上面会挂掉,

  1. 是因为 之前讲过的 rust关于 ownership的管理方法,
  2. 是虽然看上去读第一个元素和尾部加元素毫不影响, 但实际上可能rust的内部实现在 push时会 申请新的地址,并整个拷贝,这样的情况下 first会指向一个被释放了的内存地址。所以其实它们可能有关[其实根本还是 ownership

遍历

1
2
3
4
5
6
7
8
9
10
// 只读
let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
// 可写
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}

通过Enum支持 存储多种类型

1
2
3
4
5
6
7
8
9
10
11
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}

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

怎么 这样看上去 就像 自己实现了一个struct,带一个变量标识类型,和 一个void *指针,然后说vector可以装这些 不同 类型 XD

但下面说 Rust needs to know what types will be in the vector at compile time so it knows exactly how much memory on the heap will be needed to store each element.需要在编译时 知道 每个元素 需要的内存大小(max?)

Storing UTF-8 Encoded Text with Strings

如果你从其它语言来,那么你可能因为string而自闭XD

string的实现是 a collection of bytes

core language: string slice : str ,常见的用法&str

standard library: 字符串( growable, mutable, owned, UTF-8 encoded string type) : String

String 创建

下面两个函数做同样的事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 字面量 + to_string()
let s = "initial contents".to_string();

// String::from(字面量)
let s = String::from("initial contents");

let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");

操作

1
2
3
4
5
6
7
8
9
10
11
12
// push
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2); // push_str 不会拿ownership
println!("s2 is {}", s2);

// concat
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1 被 moved,之后就不能用了, 但是s2是引用的使用方法,所以之后还是可用

// + 使用的方法是形如 fn add(self, s: &str) -> String , add 真实的是使用 泛型实现

&s2的类型是&String ,被rust通过deref coercion转换为 &s2[..],也就是类型&str

1
2
3
4
5
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{}-{}-{}", s1, s2, s3);// 易读 而且 不会对参数 take ownership

蛋是,想直接访问s1[0]并不可行std::string::String cannot be indexed by {integer}

String is a wrapper over a Vec<u8>

例子

1
2
let len = String::from("Hola").len(); // 4
let len = String::from("Здравствуйте").len(); // 24

如上两个 因为有UTF-8的编码,所以 简单的 index去描述 会难以理解

因为有3种看待String的方式

  • as bytes,
  • scalar values,
  • and grapheme clusters (the closest thing to what we would call letters).

对于 “नमस्ते”

  1. 我们以u8为单位看[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]

  2. 以Unicode scalar value 看['न', 'म', 'स', '्', 'त', 'े']

  3. grapheme clusters["न", "म", "स्", "ते"]

可行的String操作

1

1
2
3
4
5
6
7
let hello = "Здравствуйте";

let s = &hello[0..4]; // 类型&str ,值为 Зд

for b in "नमस्ते".bytes() {
println!("{}", b);
}

如果是一个非法的分割字符,则会 panic

2

1
2
3
for c in "नमस्ते".chars() {
println!("{}", c);
}

Storing Keys with Associated Values in Hash Maps

HashMap<K, V>

1
2
3
4
5
6
use std::collections::HashMap;

let mut scores = HashMap::new();

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

所有的key需要同样的类型

1
2
3
4
5

["Blue","Yellow"]
[10,50]
变为
{"Blue":10,"Yellow":50}
1
2
3
4
5
6
use std::collections::HashMap;

let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];

let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();

注意的是,insert的操作会拿取ownership

读取

1
2
let team_name = String::from("Blue");
let score = scores.get(&team_name);

get返回的是Option<&V>类型

1
2
3
4
for (key, value) in &scores {
println!("{}: {}", key, value);
}
println!("{:?}", scores);

调用insert可以更新 原来的值

scores.entry(String::from("Blue")).or_insert(50);

entry来看是否存在,如果存在则无后续操作,如果不存在则插入值

基于原来的值更新

1
2
let count = map.entry(word).or_insert(0);
*count += 1;

By default, HashMap uses a “cryptographically strong”1 hashing function that can provide resistance to Denial of Service (DoS) attacks !!!!!

Error Handling

错误分为recoverableunrecoverable errors

Rust 没有exceptions, 它只有type Result<T, E>

不可恢复的错误with panic!

当调用panic!时 macro executes, your program will print a failure message, unwind and clean up the stack, and then quit

panic发生时,默认行为 the program starts unwinding, which means Rust walks back up the stack and cleans up the data from each function it encounters

你也可以通过在Cargo.toml的 [profile]部分 增加panic='abort'来 达到一旦panic直接abort的效果,如

1
2
[profile.release]
panic = 'abort'

在终端里export RUST_BACKTRACE=1或者 RUST_BACKTRACE=1 cargo run可以让panic时输出栈信息

Recoverable Errors with Result

Result的样子:

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

例如 打开文件 的返回就是Result<std::fs::File,std::io::Error>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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),
},
};
}

注意到的是Result被默认 引入,所以不需要在Ok和Err前加上Result::

我们这里 打开文件,如果成功直接 Ok(file)=>file ,如果发生错误,则根据错误的类型判断,来进行不同操作

以上出现了很多match,也可以写成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use std::fs::File;
use std::io::ErrorKind;

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);
}
});
}

还有更简洁的unwrap(正确返回 错误则panic) : let f = File::open("hello.txt").unwrap();

If the Result value is the Ok variant, unwrap will return the value inside the Ok. If the Result is the Err variant, unwrap will call the panic! macro for us. Here is an example of unwrap in action

还有一个expect 和unwrap一样,但是可以多传参想展示的错误信息let f = File::open("hello.txt").expect("Failed to open hello.txt");

你可以在 match中不处理错误 手动的return 给Err,让上层函数来处理, [就是Exception那个

对于传递错误的可以 简写为 ?

1
2
3
4
5
6
7
8
9
10
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")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}

在main里使用?

1
2
3
4
5
6
7
8
use std::error::Error;
use std::fs::File;

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

Ok(())
}

To panic! or Not to panic!

本节讨论 panic的时机

在代码原型上和测试代码中,使用panic,如用unwrap,可以 既让你的代码保持干净,也能让你明确知道哪里还没有做处理

当你有比编译器更多的信息时,如你通过一些检查 明确这里不可能发生panic时,直接unwrap(),比如 ip访问127.0.0.1

当你的代码 进入一个bad state时 建议panic,这里bad state指的 assumption, guarantee, contract, or invariant has been broken, such as when invalid values, contradictory values, or missing values are passed to your code—plus one or more of the following:

  • The bad state is not something that’s expected to happen occasionally.
  • Your code after this point needs to rely on not being in this bad state.
  • There’s not a good way to encode this information in the types you use.

感觉这个就有点像 使用assert

除此以外更建议使用 类型 来做保证,利用rust编译时检查来保护

另一个办法是自定义一个类型

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

impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}

Guess {
value
}
}

pub fn value(&self) -> i32 {
self.value
}
}

总之 处理不了则 panic!,可以处理 或 有可能处理 等等的其它情况 Result

Generic Types, Traits, and Lifetimes

上面章节 我们已经用过的有 Option<T>,Vec<T>,HashMap<K,V>,Result<T,E>

道理我都懂,为什么tutorial要这样举例 没把tutorial的例子移动过来 要看的话 https://doc.rust-lang.org/book/ch10-00-generics.html

Generic Data Types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn largest<T>(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);
}

注意上面代码会报错,

1
2
3
4
5
6
7
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src/main.rs:5:12
|
5 | if item > largest {
| ^^^^^^^^^^^^^^
|
= note: an implementation of `std::cmp::PartialOrd` might be missing for `T`

相对于C++的template来说,如果是这种int char类型的话 并不会报错,对于自己定义的struct的话,自己实现operator >也就能用

关于如何解决 后续讨论

在struct中使用

1
2
3
4
5
6
7
8
9
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 };
}

回顾

1
2
3
4
5
6
7
8
9
enum Option<T> {
Some(T),
None,
}

enum Result<T, E> {
Ok(T),
Err(E),
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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());
}

Point<T>上实现一个x()方法能够返回x字段的引用

对于一个具体的类型的函数定义和方法实现

1
2
3
4
5
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}

性能

You might be wondering whether there is a runtime cost when you’re using generic type parameters. The good news is that Rust implements generics in such a way that your code doesn’t run any slower using generic types than it would with concrete types.

Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.

编译时,所以运行时没有使用generics的代价

Traits: Defining Shared Behavior

Note: Traits are similar to a feature often called interfaces in other languages, although with some differences.

src/lib.rs

1
2
3
pub trait Summary {
fn summarize(&self) -> String;// 这里使用 分号 具体要实现的每个类型去实现:
}

在struct上分别 实现 trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}

impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}

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是pub,这样其它需要实现其内部方法的 通过use 文件名::Summary来引入

But we can’t implement external traits on external types. For example, we can’t implement the Display trait on Vec<T> within our aggregator crate, because Display and Vec<T> are defined in the standard library and aren’t local to our aggregator crate. This restriction is part of a property of programs called coherence, and more specifically the orphan rule, so named because the parent type is not present. This rule ensures that other people’s code can’t break your code and vice versa. Without the rule, two crates could implement the same trait for the same type, and Rust wouldn’t know which implementation to use.

在上面 trait定义中 可以提供默认实现,然后在使用这个默认实现时写impl Summary for NewsArticle {}

默认的 trait的方法可以调用同一个trait里的其他方法,即使其它的方法没有被实现

假设有A,B,C 3个方法,A提供了默认实现,调用了B和C,那么意味着我们对于一个具体的struct 实现了B 和C 那么就可以自然的调用A 方法了

把trait作为参数

1
2
3
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}

上面impl Trait语法实际是语法糖,trait bound,大概长这样

1
2
3
pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}

pub fn notify(item1: impl Summary, item2: impl Summary) { 这样 并没有限制 item1 和item2的类型,它们可以时不同类型

如果我们写成pub fn notify<T: Summary>(item1: T, item2: T) {则可以限制他们的类型为同一类型

关于限制,同时限制满足多个trait

pub fn notify(item: impl Summary + Display) { 这样 需要同时实现Summary和Display才能

pub fn notify<T: Summary + Display>(item: T) { 也可以这样使用

where美化trait限制的书写

原本

fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {

改写为

1
2
3
4
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{

在返回值的地方也可以使用impl Trait

1
2
3
4
5
6
7
8
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,
}
}

but the code calling this function doesn’t know that.

这里举例说 例如在Iterator之类的使用情景下,让你不需要关注具体,只需要关注功能实现

但是,实际只能返回单一类型..(由于它的实现原理)

假设A 和 B 都实现了Summary, 但是 你不可以使用if somestate { A {} }else { B{} }来返回


我们回到Generic Data Types讲的那个比大小的代码

大于号会用到Rust中 的 std::cmp::PartialOrd

fn largest<T: PartialOrd>(list: &[T]) -> T { 并不能简单的解决问题

为了让我们的largest 只支持 我们实现过PartialOrd 和Copy (char和i32默认实现了)的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn largest<T: PartialOrd + Copy>(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);
}

如果我们不希望限制 largest函数 限制 需要实现Copy,我们可以用Clone代替Copy,意味着我们需要 使用更多的heap allocations,对于大数据来说,会让速度下降很多

Another way we could implement largest is for the function to return a reference to a T value in the slice. If we change the return type to &T instead of T, thereby changing the body of the function to return a reference, we wouldn’t need the Clone or Copy trait bounds and we could avoid heap allocations. Try implementing these alternate solutions on your own!

答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn largest<T: PartialOrd>(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 能够 有条件的实现 指定范畴内的 方法

例如标准库在任何type上实现了 ToString Trait

the standard library implements the ToString trait on any type that implements the Display trait. The impl block in the standard library looks similar to this code:

1
2
3
impl<T: Display> ToString for T {
// --snip--
}

Validating References with Lifetimes

Rust编译器有一个borrow checker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 错误的
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
// 正确的 data has a longer lifetime than the reference
{
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// --+ |
} // ----------+

有问题的代码

1
2
3
4
5
6
7
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}

对于编译时来说,你不知道x和y的生命周期,当然编译器更不知道x和y的生命周期

生命周期注解

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

单个注解无意义

For example, let’s say we have a function with the parameter first that is a reference to an i32 with lifetime ‘a. The function also has another parameter named second that is another reference to an i32 that also has the lifetime ‘a. The lifetime annotations indicate that the references first and second must both live as long as that generic lifetime.

上面的代码修复为

1
2
3
4
5
6
7
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}

要注意的是 这并不会改变任何的存活时间????,这只会保证 lifetime的合法性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 正确
fn main() {
let string1 = String::from("long string is long");

{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
}
// 错误 并不能给string2续命
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());
}
println!("The longest string is {}", result);
}

Thinking in Terms of Lifetimes 从生命周期的角度思考

注意的是,你只用标注有关联的,如下 当y和 结果无关联时,不写注解也能通过编译

1
2
3
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}

从理论上讲对引用来说 至少有一个入参和它的生命周期相关,如果全无关,全是函数内部产生的,那么显然在函数结束的时候,会被drop掉,那么返回的引用也就无效了

在 struct中也可以使用 生命周期注解

在历史版本的代码中Rust 需要很多的 生命周期注解,后来Rust team 发现 有许多是可推断的,也就是可省略的lifetime elision rules

在未来,可能需要的 生命周期注解会更少

总的来说 ,平时编码你不需要过多关注,如果有编译器无法推断的情况,它会抛错,请你提供注解

  1. 函数或方法 参数的Lifetimes称作 input lifetimes,
  2. 返回值上的lifetimes 称作 output lifetimes.

编译器用3条规则来推测

  1. 每一个 input 变量 对应单独一个 lifetimes,也就是有几个参数就有几个不同的Lifetimes
  2. 如果只有一个input Lifetimes 参数,那么 它的lifetimes会被应用到所有 out lifetimes参数
  3. 如果是个方法,也就是参数中有 &self&mut self 那么self的lifetime会被 应用到所有output lifetime

Lifetime Annotations in Method Definitions

1
2
3
4
5
6
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}

返回值根据规则3 和self一样

The Static Lifetime

通过如下的'static注解

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

这样的注解意味着 生命周期 占整个程序,建议可用的地方有 错误提示信息的字串

Generic Type Parameters, Trait Bounds, and Lifetimes Together

all in one

1
2
3
4
5
6
7
8
9
10
11
12
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
}
}

Writing Automated Tests

How to Write Tests

基本3个步骤

  1. 设置依赖的数据和状态
  2. 运行你想要测试的代码
  3. 验证结果 和期望的一致

在函数前加上#[test],当你运行cargo test时,cargo会调用他们

1
2
3
cargo new adder --lib
cd adder
vim src/lib.rs

运行 cargo test

下面我们把src/lib.rs换成

1
2
3
4
5
6
7
8
9
10
11
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

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

新增文件src/test.rs 感觉 tutorial 这里写错了(吗?),这里写的还是src/lib.rs虽然把下面一段代码放在上面代码后面也可以运行

**补,注:**看完这一章后 我去掉了test.rs把 测试代码放在实现代码同一份代码的下部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle { width: 8, height: 7 };
let smaller = Rectangle { width: 5, height: 1 };

assert!(larger.can_hold(&smaller));
}

#[test]
fn smaller_cannot_hold_larger() {
let larger = Rectangle { width: 8, height: 7 };
let smaller = Rectangle { width: 5, height: 1 };

assert!(!smaller.can_hold(&larger));
}
}

src下的样子:

1
2
3
src/
├── lib.rs
└── test.rs

运行cargo test,忽略那些黄色的warning提示,能成功通过assert

测试常用的宏:

  • assert!
  • assert_eq!
  • assert_ne!

相对于 手动assert + == 运算来说 ,后面两种方法 会自动带出 输入的值

根据后面两个宏的实现原理,你传入的struct需要同时实现PartialEqDebug两个trait

通常会在你的struct或enum前加上 #[derive(PartialEq, Debug)]

可以自定义额外的输出消息

assert!(表达式,额外的输出消息)

#[should_panic]来 检测panic是否发生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pub struct Guess {
value: i32,
}

impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}

Guess {
value
}
}
}

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

#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}

更精确的 panic,增加expected

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// --snip--

impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!("Guess value must be greater than or equal to 1, got {}.",
value);
} else if value > 100 {
panic!("Guess value must be less than or equal to 100, got {}.",
value);
}

Guess {
value
}
}
}

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

#[test]
#[should_panic(expected = "Guess value must be less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}

在测试代码中使用Result<T,E>

1
2
3
4
5
6
7
8
9
10
11
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
}
}

You can’t use the #[should_panic] annotation on tests that use Result<T, E>

Controlling How Tests Are Run

默认的 test是并行运行的!

如果你的不同测试会读写同一个文件,那么可能在测试中有冲突,cargo test -- --test-threads=1可以让只有一个测试线程

函数的输出,当测试成功时,你不会在terminal看到 被测试程序中的输出,当测试失败时,你看到所有被测试程序过程中的输出

cargo test -- --nocapture 可以让 程序的过车给你输出始终能看到

运行一定条件函数名测试cargo test <希望被函数名包含的字符串>,同时在输出中,你会看到XXX filtered out

cargo test add只会运行 函数名中有add的测试方法

对于一些测试可能很耗时或其它原因你希望默认忽略,那么加上#[ignore]#[test]的下一行

1
2
3
4
5
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}

这样默认cargo test它不会被运行

我们可以通过cargo test -- --ignored来调用这些被标注#[ignore]的函数

Test Organization

把测试分为两个类型:单元测试和交互测试

Unit Tests

You’ll put unit tests in the src directory in each file with the code that they’re testing. The convention is to create a module named tests in each file to contain the test functions and to annotate the module with cfg(test).

测试module

#[cfg(test)]注解告诉 只有运行cargo test时才会编译和运行

也就是cargo build并不会理有这样注解的

测试私有的函数!other languages make it difficult or impossible to test private functions

1
2
3
4
5
6
7
8
9
10
11
12
13
fn internal_adder(a: i32, b: i32) -> i32 {
a + b
}

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

#[test]
fn internal() {
assert_eq!(4, internal_adder(2, 2));
}
}

Integration Tests

区别于单元测试,交互测试,会如同正常的代码调用去模拟

在我们项目的顶层 创建一个tests文件夹 和src同级别,cargo会在这个文件夹中 找交互测试的文件

1
2
3
4
5
6
use adder; // 我们在单元测试中不会需要这个, 而

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

我们也不需要任何#[cfg(test)],因为整个文件夹 都会被视作cargo test使用的

文件src/lib.rs

1
2
3
4
5
6
7
pub fn add_two(a: i32) -> i32 {
internal_adder(a, 2)
}

fn internal_adder(a: i32, b: i32) -> i32 {
a + b
}

运行cargo test全ok即可

指定测试文件名cargo test --test integration_test

如果你在tests下建立了一个 common.rs,即使你没有在里面写测试函数,你运行cargo test时,在输出中依然能看到它

我们 把src/common.rs改成 src/common/mod.rs

这样的命名文件方式 告诉Rust 不要把common module 视作 交互测试文件,其它文件可以调用它

tests/integration_test.rs

1
2
3
4
5
6
7
8
9
use adder;

mod common;

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

tests/common/mod.rs

1
2
3
pub fn setup() {

}

如上的分解文件能实现

  1. 不同的测试文件都可以调用common/mod.rs里的方法
  2. cargo test不会把 common/mod.rs直接视作 交互测试文件

Integration Tests for Binary Crates

如果,我是说如果,我们的工程是 binary crate,也就是没有src/lib.rs,只有src/main.rs

那么我们不能 通过 上面tests文件夹 的 交互测试方法,来 bring src/main.rs 中定义的函数

原因是:Only library crates expose functions that other crates can use; binary crates are meant to be run on their own.

从 分化的哲学上说,如果复杂的功能(被分化到src/lib.rs中的)测试都没问题,那么少量的src/main.rs正常工作不需要测试?

不太认可这种(少量代码 就不需要测试)的观点,但反过来看,这种设计能带来的结果会促使代码的分化,留更少的逻辑在main中,XD

An I/O Project: Building a Command Line Program

英语小课堂

have sth. under your belt,

你已经有经验的某事/ 已经掌握

  1. In one’s scope of experience.

Once you get a few more major league games under your belt, you’ll feel more comfortable.

吸收或消化

  1. Ingested or consumed.

He should be less cranky after he gets some food under his belt.


本章 打算搞个grep(globally search a regular expression and print) , 经常在linux开发的应该是再熟不过的命令了

Rust 社区成员 Andrew Gallant已经完成了一个 完整版的ripgrep

本章只会实现一些简单的功能,因为本章的目的还是

  1. 回顾你所学的,
  2. 熟悉一些新的库函数

本章包含内容

  • 组织代码(chapter 7)
  • 使用Vec和String (chapter 8)
  • 处理错误(chapter 9)
  • Using traits and lifetimes where appropriate(chapter 10)
  • 编写测试(chapter 11)

接受命令行参数

1
cargo new minigrep && cd minigrep

有写过py脚本 或者shell脚本的 应该经常用相应语言写过解析了

使用std::env::args来读取参数

如果你需要你的程序接受无效的Unicode,请尝试std::env::args_os

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
use std::env;

fn main() {
let args: Vec<String> = env::args().collect();

let query = &args[1];
let filename = &args[2];

println!("Searching for {}", query);
println!("In file {}", filename);
}

测试 cargo run par1 par2

Reading a File

先建一个文件 让我们读

poem.txt

1
2
3
4
5
6
7
8
9
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use std::env;
use std::fs;

fn main() {
let args: Vec<String> = env::args().collect();

let query = &args[1];
let filename = &args[2];

println!("Searching for {}", query);
println!("In file {}", filename);

let contents = fs::read_to_string(filename)
.expect("Something went wrong reading the file");

println!("With text:\n{}", contents);
}

测试cargo run the poem.txt

Refactoring to Improve Modularity and Error Handling

拆分重构

main中应该 只有

  • Calling the command line parsing logic with the argument values
  • Setting up any other configuration
  • Calling a run function in lib.rs
  • Handling the error if run returns an error

官方tutorial的重构 步骤比我这里记录的更细致!包括先换成tuple,再换成struct,再设计new等等

文件目录src结构

1
2
3
src/
├── lib.rs
└── main.rs

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use std::error::Error;
use std::fs;

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

impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone(); // There’s a tendency among many Rustaceans to avoid using clone to fix ownership problems because of its runtime cost. In Chapter 13, you’ll learn how to use more efficient methods in this type of situation. But for now, it’s okay to copy a few strings to continue making progress because you’ll make these copies only once and your filename and query string are very small. It’s better to have a working program that’s a bit inefficient than to try to hyperoptimize code on your first pass. As you become more experienced with Rust, it’ll be easier to start with the most efficient solution, but for now, it’s perfectly acceptable to call clone.

Ok(Config { query, filename })
}
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> { // Box<dyn Error> means the function will return a type that implements the Error trait, but we don’t have to specify what particular type the return value will be
let contents = fs::read_to_string(config.filename)?;

println!("With text:\n{}", contents);

Ok(())
}

src/main.rs

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

use minigrep::Config;

fn main() {
let args: Vec<String> = env::args().collect();

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

println!("Searching for {}", config.query);
println!("In file {}", config.filename);

if let Err(e) = minigrep::run(config) {
println!("Application error: {}", e);

process::exit(1);
}
}

Developing the Library’s Functionality with Test-Driven Development

TDD !!!! 以前写python时尝试过,始终没有一个很好的实践

  1. Write a test that fails and run it to make sure it fails for the reason you expect.
  2. Write or modify just enough code to make the new test pass.
  3. Refactor the code you just added or changed and make sure the tests continue to pass.
  4. Repeat from step 1!

This process is just one of many ways to write software, but TDD can help drive code design as well.

看上去这种过程比我之前的会好很多

我之前TDD的过程,有以下问题

  1. 在尝试在一开始,就想把所有 必要不必要,主流程,辅流程考虑完,实际根本无法完成
  2. 尝试在一开始就写足量的测试,但实际上,很难一开始就足量
  3. 进行过程中,一边改代码,一边改测试,没有编码和写测试的分化逐渐模糊
  4. 没有循环的环,想是 想,1写完 测试,2写完代码

相比之下,上面的过程,意味着不要一步到位,每次测试编写只关注,“当前版本”期望解决的问题,然后,明确分化了 测试编写和代码编写。最后有迭代环。

先编写测试代码, 在src/lib.rs文件后加上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";

assert_eq!(
vec!["safe, fast, productive."],
search(query, contents)
);
}
}

最终 成果

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
use std::error::Error;
use std::fs;

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

impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone(); // There’s a tendency among many Rustaceans to avoid using clone to fix ownership problems because of its runtime cost. In Chapter 13, you’ll learn how to use more efficient methods in this type of situation. But for now, it’s okay to copy a few strings to continue making progress because you’ll make these copies only once and your filename and query string are very small. It’s better to have a working program that’s a bit inefficient than to try to hyperoptimize code on your first pass. As you become more experienced with Rust, it’ll be easier to start with the most efficient solution, but for now, it’s perfectly acceptable to call clone.

Ok(Config { query, filename })
}
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> { // Box<dyn Error> means the function will return a type that implements the Error trait, but we don’t have to specify what particular type the return value will be
let contents = fs::read_to_string(config.filename)?;

for line in search(&config.query, &contents) {
println!("{}", line);
}

Ok(())
}

// 如果没有加 'a (lifetime specifier) :this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `query` or `contents`

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}

// 以下是上面的测试代码
// ....

Working with Environment Variables

增加功能,用户可以配置环境变量,设置 大小写不敏感

在刚刚的测试代码中增加 新的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//... 和另一个 #[test]同层级
#[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)
);
}
//...

大小写敏感搜索实现代码

1
2
3
4
5
6
7
8
9
10
11
12
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();

for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}

results
}

算法实现了,下面要做的是 用户可以 通过 环境变量来决定是否是否使用大小写敏感

通过env::vars 来读取环境变量

最终实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use std::error::Error;
use std::fs;
use std::env;

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

impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();

let case_insensitive = env::var("CASE_INSENSITIVE").is_err(); // 读取环境变量

Ok(Config { query, filename, case_insensitive })
}
}

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

let results = if config.case_insensitive {
search(&config.query, &contents)
} else{
search_case_insensitive(&config.query,&contents)
};

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

Ok(())
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();

for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}

results
}

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

#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";

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)
);
}
}

分别运行以下命令,观察输出的差异

cargo run to poem.txt

CASE_INSENSITIVE=1 cargo run to poem.txt

Writing Error Messages to Standard Error Instead of Standard Output

有经常用linux命令的 都知道 输出有 标准输出和错误输出

把 错误输出的println! 换成 eprintln! 即可

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

use minigrep::Config;

fn main() {
let args: Vec<String> = env::args().collect();

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

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

好了 下面你可以用 重定向输出发现,我们 把 错误信息输出到stderr了

回顾TDD

我们上面的步骤为

  1. 首先编写最外的框架,分离main和lib
  2. 编写初代的测试代码,针对我们的关键运算函数
  3. 实现函数 通过测试
  4. 完成其它功能,比如调用这个运算函数,增加输入输出提示
  5. 完成,为新的功能编写测试代码,跳到第3步

Functional Language Features: Iterators and Closures

  • Closures, a function-like construct you can store in a variable
  • Iterators, a way of processing a series of elements
  • How to use these two features to improve the I/O project in Chapter 12
  • The performance of these two features (Spoiler alert: they’re faster than you might think!)

Closures: Anonymous Functions that Can Capture Their Environment

闭包

1
2
3
4
5
6
7
8
9
10
11
12
fn 函数名字(...){
let 值 = 某个耗时运算

if ... {
使用 值
使用 值
} else if(...) {
使用 值
} else {
不使用 值
}
}

我们希望只有需要运算结果,才会执行. 这有一个闭包的应用场景。

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

定义一个闭包,把它存在expensive_closure变量中

我们用的单个参数|num|如果要多个,用逗号隔开,|param1,param2|

注意,let statement意味着expensive_closure 包含一个匿名函数的定义,不是包含一个匿名函数调用的返回值

改成如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fn generate_workout(intensity: u32, random_number: u32) {
// 闭包
// 通常 因为有上下文 和 可推断 不需要注解,但是 如果使用时,用不同类型调用则会报错
let expensive_closure = |num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};

if intensity < 25 {
println!(
"Today, do {} pushups!",
expensive_closure(intensity) // 闭包调用
);
println!(
"Next, do {} situps!",
expensive_closure(intensity)
);
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!(
"Today, run for {} minutes!",
expensive_closure(intensity)
);
}
}
}

其它书写方法

1
2
3
4
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 ;

Storing Closures Using Generic Parameters and the Fn Traits

memoization or lazy evaluation.

有三种闭包实现,要么是Fn, FnMut, or FnOnce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
},
}
}
}

上面可改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  // 初始化值
let mut expensive_result = Cacher::new(|num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
});
// 获取值
expensive_result.value(intensity)
``

在目前实现上,只有首次调用value的intensity会有效,其它的暂时没有区别

第二个问题是 目前只支持u32,尝试让它支持更多类型

> Capturing the Environment with Closures

闭包能读取它所在环境中的变量

```rust
fn main() {
let x = 4;

let equal_to_x = |z| z == x;// taking ownership, borrowing mutably, and borrowing immutably
// 注意 不能换成 fn equal_to_x(z: i32) -> bool { z == x } 这样函数写法而不是闭包写法 无法读取外部x

let y = 4;

assert!(equal_to_x(y));
}
  • FnOnce consumes the variables it captures from its enclosing scope, known as the closure’s environment. To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. The Once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.
  • FnMut 可以改变环境里的变量,
  • Fn borrows values from the environment immutably.
1
2
3
4
5
6
7
8
9
10
11
fn main() {
let x = vec![1, 2, 3];

let equal_to_x = move |z| z == x;
// 对于非基础类型来说 -------- value moved (into closure) here
println!("can't use x here: {:?}", x);

let y = vec![1, 2, 3];

assert!(equal_to_x(y));
}

因为我们加了move关键字,当闭包被定义时, 值就被移动到闭包内

Processing a Series of Items with Iterators

iterators are lazy 创建时,不会消耗iterator

1
2
3
4
5
6
7
8
9
let v1 = vec![1, 2, 3];

// 创建
let v1_iter = v1.iter();

// 遍历
for val in v1_iter { // We didn’t need to make v1_iter mutable when we used a for loop because the loop took ownership of v1_iter and made it mutable behind the scenes.
println!("Got: {}", val);
}

实现自己的可iterable:只要实现 Iterator的trait

1
2
3
4
5
6
7
8
pub trait Iterator {
type Item;
// 新的语法: type Item and Self::Item, which are defining an associated type with this trait.

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

// methods with default implementations elided
}

通过next遍历

1
2
3
4
5
6
7
8
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);

如果我们想 创建一个会拿ownership的 并能返回owned values的,调用into_iter 取代 iter. 类似如果我们想遍历,mutable references,我们调用iter_mut来替代iter,这谁设计的名字?怎么一个加前缀一个后缀

Methods that call next are called consuming adaptors,例如 sum 方法

1
2
3
4
5
6
7
8
9
10
11
#[test]
fn iterator_sum() {
let v1 = vec![1, 2, 3];

let v1_iter = v1.iter();

let total: i32 = v1_iter.sum();

// 这里意味着 v1_iter 不再可用,因为sum 拿取了ownership
assert_eq!(total, 6);
}

Other methods defined on the Iterator trait, known as iterator adaptors

比如 v1.iter().map(...)map,iterator adaptors are lazy, and we need to consume the iterator here.

直接v1.iter().map(|x| x+1)会报错,因为没有消耗iterator

解决方案是collect一个会消耗iterator的方法,它收集返回值到一个新的 collection data type

1
2
3
4
5
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]);

filter方法 是 iterator adaptor, 如果函数返回真 则是要的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}

fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter() // 拿取 ownership
.filter(|s| s.size == shoe_size)
.collect()
}

#[test]
fn filters_by_size() {
let shoes = vec![
Shoe { size: 10, style: String::from("sneaker") },
Shoe { size: 13, style: String::from("sandal") },
Shoe { size: 10, style: String::from("boot") },
];

let in_my_size = shoes_in_my_size(shoes, 10);

assert_eq!(
in_my_size,
vec![
Shoe { size: 10, style: String::from("sneaker") },
Shoe { size: 10, style: String::from("boot") },
]
);
}

自定义一个 iterable

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
}
}
}

#[test]
fn calling_next_directly() {
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);
}

#[test]
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);
}

zip 只处理4个pair,因为任何pair中有None的话,zip会返回None

上面都会调用next()

Improving Our I/O Project

看上面IO那一章的Config::new函数

我们使用clone,因为我们没有args的 ownership

修改上面那章的main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,9 +4,7 @@ use std::process;
use minigrep::Config;

fn main() {
- let args: Vec<String> = env::args().collect();
-
- let config = Config::new(&args).unwrap_or_else(|err| {
+ let config = Config::new(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
process::exit(1);
});

这里的env::args我们传递的是iteratorownership

对应 把之前的代码改简洁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,12 +9,18 @@ pub struct Config {
}

impl Config {
- pub fn new(args: &[String]) -> Result<Config, &'static str> {
- if args.len() < 3 {
- return Err("not enough arguments");
- }
- let query = args[1].clone();
- let filename = args[2].clone();
+ pub fn new(mut args: std::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 file name"),
+ };

let case_insensitive = env::var("CASE_INSENSITIVE").is_err();

@@ -40,13 +46,9 @@ pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
- let mut results = Vec::new();
- for line in contents.lines() {
- if line.contains(query) {
- results.push(line);
- }
- }
- results
+ contents.lines()
+ .filter(|line| line.contains(query))
+ .collect()
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {

新的search代码 把老生长谈的方法变成函数

Comparing Performance: Loops vs. Iterators

竟然还有性能比较,大概

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

这就意味着 iterators, although a high-level abstraction, get compiled down to roughly the same code as if you’d written the lower-level code yourself. 牛逼了

Iterators are one of Rust’s zero-cost abstractions, by which we mean using the abstraction imposes no additional runtime 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.

下面是一个音频处理代码的部分

1
2
3
4
5
6
7
8
9
10
11
12
let buffer: &mut [i32];
let coefficients: [i64; 12];
let qlp_shift: i16;

for i in 12..buffer.len() {
let prediction = coefficients.iter()
.zip(&buffer[i - 12..i])
.map(|(&c, &s)| c * s as i64)
.sum::<i64>() >> qlp_shift;
let delta = buffer[i];
buffer[i] = prediction as i32 + delta;
}

为了计算prediction的值,遍历coefficients的12个值,和buffer的每个值构成(一个coefficients,一个buffer)然后两两相乘,求和,最后位移一下。

Rust编译器会把它们编译成 和手写实现 相类似的 汇编代码,这里甚至把循环12次进行循环展开

More About Cargo and Crates.io

  • Customize your build through release profiles
  • Publish libraries on crates.io
  • Organize large projects with workspaces
  • Install binaries from crates.io
  • Extend Cargo using custom commands

更多文档 请看 这里

Customizing Builds with Release Profiles

release profiles是预定义,可定制

Cargo有两个主要的配置

dev:cargo build

release:cargo build --release

如果Cargo.toml没有任何[profile.*] 则采用默认配置

如果在Cargo.toml中配置,则是写了多少就覆盖多少默认的

1
2
3
4
5
[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

Publishing a Crate to Crates.io

crates.io 可以用github账号登陆

这是一个可以上传也可以下载别人共享的

  • 准确的文档描述你的packages

Rust 如果用///来写注释,可以自动生成HTML文档 例如

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
/// Adds one to the number given.
///
/// # Examples
///
/// <三个` hexo next 的解析不支持...会和上面的配对>
/// let arg = 5;
/// let answer = <这里写你的包名>::add_one(arg);
///
/// assert_eq!(6, answer);
/// <三个` hexo next 的解析不支持...会和上面的配对>
pub fn add_one(x: i32) -> i32 {
x + 1
}

你可以在本地cargo doc --open来查看

所以这是markdown语法!?

标题 有

# Examples

# Panics:提示用户什么时候可能触发

# Errors:例如返回的Result,那么有错误类型的描述

# safety: 解释为什么这个方法是unsafe的 //19章节

running cargo test will run the code examples in your documentation as tests!!!!!!!!!!!!!!!!!??????????????

试了一下有点意思啊,这么溜吗,这解决了,有可能只改了代码和测试,但忘记改文档的问题

We typically use these doc comments inside the crate root file, 用//! 做根描述

你在实现模块的层级架构和用户使用期望的层级架构是有差异的,你编写可能更考虑分化,可迭代性等等多层级,而用户可能只希望好用。

如果你直接cargo doc 对于你层级内的东西,用户需要反复点击才能找到

要解决这个问题,你可以在 src/lib.rs上 加

1
2
3
4
pub use 层级::层级::要pub的;
pub use 层级::层级::要pub的;
pub use 层级::层级::要pub的;
...

账号

github登陆

https://crates.io/me创建token,然后根据它的提示,就可以在命令行登陆了,注意保密!

发布准备

Cargo.toml配置名称等,注意crates.io上是先到先得 //个人感觉这样设计并不够好,还是用户/仓库名的那种体验更好,复议

license 也可以用OR 连接,一个完整的大概像这样

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

注意!!!一旦发布不能被删除,是永久的,只能有版本更新: 使用cargo publish来发布

关于版本号 https://semver.org/

Given a version number MAJOR.MINOR.PATCH, increment the:

  • MAJOR version when you make incompatible API changes,
  • MINOR version when you add functionality in a backwards-compatible manner, and
  • PATCH version when you make backwards-compatible bug fixes.

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

撤回版本cargo yank --vers 1.0.1:意义 并没有删除,但是新的没有指定该版本的不会依赖该版本,但别人已经指定lock该版本的,依然可以下载。

cargo yank --vers 1.0.1 --undo 撤销 撤回

Cargo Workspaces

cargo提供一个Workspaces,来管理多个相互关联的开发包

workspace = package*

1
mkdir workspacedemo && cd workspacedemo

建立文件Cargo.toml并写入

1
2
3
4
5
6
[workspace]

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

执行命令

1
2
cargo new adder
cargo new add-one --lib

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
.
├── adder
│   ├── Cargo.toml
│   └── src
│   └── main.rs
├── add-one
│   ├── Cargo.toml
│   └── src
│   └── lib.rs
├── Cargo.lock
├── Cargo.toml
└── target

文件add-one/src/lib.rs

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

文件adder/Cargo.toml[dependencies]字段

1
2
3
[dependencies]

add-one = { path = "../add-one" }

adder/src/main.rs

1
2
3
4
5
6
use add_one;

fn main() {
let num = 10;
println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
}

运行

cargo build

cargo run -p adder

你会发现,只有workspace根地方才有lock,保证了哥哥package之间用的相同的依赖版本

对于外部包来说,例如rand

我们在add-one/Cargo.toml中加上

1
2
3
[dependencies]

rand = "0.3.14"

但是在adder中并不能使用,如果要使用,需要在adderCargo.toml中也加上 rand的依赖

单元测试和之前一样,区别是,我们可以 用cargo test -p add-one这样来指定具体的packages

workspace发布,你需要进入每个package目录单独cargo publish发布,没有--all-p参数

Installing Binaries from Crates.io with cargo install

你只能install 有 二进制targets的 也就是src/main.rs

一般来说README文件中会说清 是lib还是bin

所有cargo install的二进制默认放在$HOME/.cargo/bin

1
2
3
> ls ~/.cargo/bin/
cargo cargo-fmt clippy-driver rustc rustfmt rustlings rustup
cargo-clippy cargo-miri rls rustdoc rust-gdb rust-lldb

我们用ripgrep举例

首先cargo install ripgrep

然后就可以rg --help使用了

Extending Cargo with Custom Commands

如果,你的$PATH中存在某个cargo-something那么你可以用cargo something 命令

你甚至可以用cargo ---list查看

Smart Pointers

好熟悉的名字,玩过C++的都知道 c++一组 smart pointer,以前用的时候觉得还是很香的

https://docs.microsoft.com/en-us/cpp/cpp/smart-pointers-modern-cpp?view=vs-2019

https://en.cppreference.com/book/intro/smart_pointers


references are pointers that only borrow data;

作为对比大多数情况下, smart pointers own the data they point to.

我们已经讲过的StringVec<T>就是 smart pointers,因为它们有用一些内存,并且允许你 操作它,

除此外它们也有metadata,以及一些额外的功能,如String 会保证始终是一个有效的 UTF-8;

职能指针通常用结构体实现,和普通 struct相比,它们实现了DerefDrop traits

Deref trait 允许一个 smart pointer 结构体 像一个 reference一样使用

Drop trait 允许你自己设计当一个该smart pointers 的 instance 除了scope时的行为

本章 只会 讲解 部分 常用的 标准库里的 智能指针:

  • Box<T>: 在 堆上 allocating values
  • Rc<T>, 一个引用计数,允许 多个ownership
  • Ref<T> and RefMut<T>, accessed through RefCell<T>, a type that enforces the borrowing rules 在运行时 而不是 编译时

此外,本章还会有 interior mutability

也会讨论reference cycles 是如何 内存泄漏 和 如何防止

Using Box to Point to Data on the Heap

Box<T>

最直接的 smart pointer,让你可以在堆上保存值,而不是栈上

较常使用场景:

  • 编译时 不知道大小,比如递归类型
  • 想transfer ownership大量的数据,但要确保数据不会被拷贝,比如为了性能
  • 当你 own a value 且你只关心这个value的type实现了一个特定的trait 而不在意 是一个特定的 type, 看作trait object

香不香

1
2
3
4
fn main() {
let b = Box::new(5); // 虽然一般不会这样写没啥意义,但这样可以示例
println!("b = {}", b);
}

A cons list is a data structure that comes from the Lisp programming language and its dialects

cons = ‘construct’

to cons x onto y 意味着形成? (x,y)

以下代码无 ,但是 尝试一个递归的定义效

1
2
3
4
enum List {
Cons(i32, List),
Nil,
}

使用List 类型来存储1,2,3

src/main.rs

1
2
3
4
5
use crate::List::{Cons, Nil};

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

因为我们还没有解决上面递归定义的问题,会报错recursive type has infinite size

…c玩得熟的话,这大概就是struct里放同一个structstruct里放 同一个struct指针的区别

Cons list Cons

Box<T> needs: a pointer’s size, 不会因为指向不同的数据改变所需要的空间大小

以下是可以运行的

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
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))))));
}

cons box list

Treating Smart Pointers Like Regular References with the Deref Trait

实现了Dereftrait 可以让你自定义 dereference operator的行为

然后你就可以像使用引用一样使用 相应结构体

先看deref coercion feature

注意以下示例 只关注了 Dereftrait的实现,

src/main.rs

1
2
3
4
5
6
7
struct MyBox<T>(T);

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

我们期望下面代码能运行成功

1
2
3
4
5
6
7
fn main() {
let x = 5;
let y = MyBox::new(x);

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

实现

1
2
3
4
5
6
7
8
9
use std::ops::Deref;

impl<T> Deref for MyBox<T> {
type Target = T; // 这里为了让`Deref`能使用 MyBox<T> -> T

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

Rust 会把上面*y实际看作*(y.deref())

Implicit Deref Coercions with Functions and Methods

像上面的 我们可以这样使用

1
2
3
4
5
6
7
8
9
fn hello(name: &str) {
println!("Hello, {}!", name);
}

fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m); // 通过Deref, &MyBox<String> -> &String -> &str
// 如果没有 Rust的这个帮助,我们要`&(*m)[..]`这样写
}

有三种情况会转化

  • From &T to &U when T: Deref<Target=U>
  • From &mut T to &mut U when T: DerefMut<Target=U>
  • From &mut T to &U when T: Deref<Target=U>

类似如果你希望 *操作 是 immutable references,那么用DerefMut替代Deref

第三种 是一个trick,吧 mutable 转换成 immutable, (但反过来不行)

Running Code on Cleanup with the Drop Trait

Drop trait : lets you customize what happens when a value is about to go out of scope.

例如,Box<T>自定义的Drop行为是 deallcate box 指向的堆上的space

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.");
}

会输出

1
2
3
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

Variables are dropped in the reverse order of their creation.

如果我们希望 早一些 释放,我们并不能直接调用.drop()explicit destructor calls not allowed,因为这样Rust依然会 自动调用drop,可能double free

我们只能调用std::mem::drop来早些释放,如(锁的控制)

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

这说明了 内存你依然管不了?只能说 影响影响 释放时机

Rc, the Reference Counted Smart Pointer

计数智能指针

比如图,一个点可能被很多指向边拥有

注意! 在单线程中使用Rc<T>如果要多线程的 请看下一章节

rc

首先 Box+Cons会挂掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum List {
Cons(i32, Box<List>),
Nil,
}

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

fn main() {
let a = Cons(5,
Box::new(Cons(10,
Box::new(Nil))));
let b = Cons(3, Box::new(a)); // 这里move了a 而不是Copy
let c = Cons(4, Box::new(a)); // 这里便不可用了
}

使用 Rc

1
2
3
4
5
6
7
8
9
10
11
12
13
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)); // 计数+1 而不是深拷贝 a.clone()
let c = Cons(4, Rc::clone(&a)); // 计数+1
}

通过Rc::strong_count查看Rc计数的变化

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a)); // 1
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a)); // 2
{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a)); // 3
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a)); // 2
}

现在的遗憾就是,还没能 让多个都是mutable,但有时,多个拥有者都能mutable也是很有用的。将在下一节提到

RefCell and the Interior Mutability Pattern

RefCell<T>interior Mutability Pattern

Interior mutability的实际模式,是为了让你能改变引用不可变的内部数据

用的是unsafe代码来 bend 一些Rust 规则,达到 外部 不可变,内部可变

represents single ownership over the data it holds

回顾第4章 的ownership

  • At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.
  • References must always be valid.

然后,我们上面的 Box 会在 编译时 检查borrowing规则, // 因为这种 会有更小的运行时消耗,因此 Rust默认大多是这样

然后,我们上面的 RefCell 会在 运行时 检查borrowing规则 // 例如停机问题咯,并不是所有都能,

  • Rc<T> enables multiple owners of the same data; Box<T> and RefCell<T> have single owners.
  • Box<T> allows immutable or mutable borrows checked at compile time; Rc<T> allows only immutable borrows checked at compile time; RefCell<T> allows immutable or mutable borrows checked at runtime.
  • Because RefCell<T> allows mutable borrows checked at runtime, you can mutate the value inside the RefCell<T> even when the RefCell<T> is immutable.

Interior Mutability的一个使用场景: Mock Objects

test double

如下cargo test会报错

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
pub trait Messenger {
fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where T: Messenger {
pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
LimitTracker {
messenger,
value: 0,
max,
}
}

pub fn set_value(&mut self, value: usize) {
self.value = value;

let percentage_of_max = self.value as f64 / self.max as f64;

if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger.send("Warning: You've used up over 75% of your quota!");
}
}
}

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

struct MockMessenger {
sent_messages: Vec<String>,
}

impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger { sent_messages: vec![] }
}
}

impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages.push(String::from(message));
}
}

#[test]
fn it_sends_an_over_75_percent_warning_message() {
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

limit_tracker.set_value(80);

assert_eq!(mock_messenger.sent_messages.len(), 1);
}
}

一个方案是 加一堆mut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,16 +1,16 @@
pub trait Messenger {
- fn send(&self, msg: &str);
+ fn send(&mut self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
- messenger: &'a T,
+ messenger: &'a mut T,
value: usize,
max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where T: Messenger {
- pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
+ pub fn new(messenger: &mut T, max: usize) -> LimitTracker<T> {
LimitTracker {
messenger,
value: 0,
@@ -48,15 +48,15 @@ mod tests {
}

impl Messenger for MockMessenger {
- fn send(&self, message: &str) {
+ fn send(&mut self, message: &str) {
self.sent_messages.push(String::from(message));
}
}

#[test]
fn it_sends_an_over_75_percent_warning_message() {
- let mock_messenger = MockMessenger::new();
- let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
+ let mut mock_messenger = MockMessenger::new();
+ let mut limit_tracker = LimitTracker::new(&mut mock_messenger, 100);

limit_tracker.set_value(80);

而我们实际 只是希望self中的某一个子部分是可变的,我们用RefCell<T>来搞,改动如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -36,20 +36,21 @@ impl<'a, T> LimitTracker<'a, T>
#[cfg(test)]
mod tests {
use super::*;
+ use std::cell::RefCell;

struct MockMessenger {
- sent_messages: Vec<String>,
+ sent_messages: RefCell<Vec<String>>,
}

impl MockMessenger {
fn new() -> MockMessenger {
- MockMessenger { sent_messages: vec![] }
+ MockMessenger { sent_messages: RefCell::new(vec![]) }
}
}

impl Messenger for MockMessenger {
fn send(&self, message: &str) {
- self.sent_messages.push(String::from(message));
+ self.sent_messages.borrow_mut().push(String::from(message));
}
}

@@ -60,6 +61,6 @@ mod tests {

limit_tracker.set_value(80);

- assert_eq!(mock_messenger.sent_messages.len(), 1);
+ assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}

相对于 我们 之前的 &&mut,RefCell<T>的对应的方法是

borrowborrow_mut 调用时 会引起 计数变化

分别返回Ref<T>RefMut<T>类型

同理 基本的borrow标准还是不能违反, 如下面这样就会挂掉

1
2
3
4
5
6
7
8
9
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
let mut one_borrow = self.sent_messages.borrow_mut();
let mut two_borrow = self.sent_messages.borrow_mut();

one_borrow.push(String::from(message));
two_borrow.push(String::from(message));
}
}

但不太棒的是 这个错误因为是RefCell给出的,它是运行时,所以你可能不一定检测到发生了这个问题,

一个推荐的方法是 Rc<T> + RefCell<T>同时使用

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#[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);
}

输出

1
2
3
a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))

标准库里还有Cell<T>,Mutex<T>

Reference Cycles Can Leak Memory

循环 引用,比如图

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
use crate::List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}

impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}

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());
}

a和b的strong_count计数都是2

cycle

Creating a reference cycle would be a logic bug???之前有实践过用index来代替指针,实现可以数据库保存读取,但是 我在实现如一些图论算法的时候,感觉用指针来写会体验更好?虽然感觉这种手工index能对 内存泄露解决,但是感觉是把本来指针就能提供的功能弃用了

应该automated tests, code reviews, and other software development practices to minimize.尽量减少循环引用

Another solution for avoiding reference cycles is reorganizing your data structures so that some references express ownership and some references don’t. As a result, you can have cycles made up of some ownership relationships and some non-ownership relationships, and only the ownership relationships affect whether or not a value can be dropped. In Listing 15-25, we always want Cons variants to own their list, so reorganizing the data structure isn’t possible. Let’s look at an example using graphs made up of parent nodes and child nodes to see when non-ownership relationships are an appropriate way to prevent reference cycles.

Rc<T>改成Weak<T>

之前是调用Rc::clone,会增加strong_count

现在Rc::downgrade会创建一个Weak<T>会使得weak_count的计数加1,并不表示一个ownership relationship

只有在strong_count变为0时就会clean up了,不会在意weak_count

所以 你在操作一个weak_count指向的时候,应该检查它指向的是否还存在,通过upgrade在一个Weak<T>实例上调用,返回Option<T>,如果没有被Drop它会返回一个Some,否则是个None

样例代码 建立一个树

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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());
}

想法类似的话,感觉文件系统里的 软链接和硬链接 很相似

Fearless Concurrency

让并行程序安全和高效是Rust的另一个主要goals

rust通过手段让很多错误变成 编译时错误而不是运行时错误

We’ve nicknamed this aspect of Rust fearless concurrency

  • How to create threads to run multiple pieces of code at the same time 如何写并行
  • Message-passing concurrency, where channels send messages between threads 消息传递
  • Shared-state concurrency, where multiple threads have access to some piece of data 共享数据
  • The Sync and Send traits, which extend Rust’s concurrency guarantees to user-defined types as well as types provided by the standard library 两个 trait 保证 用户定义的types

Using Threads to Run Code Simultaneously

有些语言是 调用OS的thread ,1 operating system thread : 1 language thread.

有的语言是 M:N 的线程模型

Rust needs to have nearly no runtime and cannot compromise on being able to call into C to maintain performance.

有的语言会用运行代价来换取更多feature,但Rust定位是low-level的 只提供1:1的线程实现

我们调用thread::spawn并且传递一个 闭包

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::thread;
use std::time::Duration;

fn main() {
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));
}
}

thread::sleep允许 线程 让出一小段时间不执行,其它线程可能紧接着这个时候开始执行,也可能不

Waiting for All Threads to Finish Using join Handles

c++也是有thread::join,重名大法好,不用记多个

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 closure 经常伴随 thread::spawn 因为它允许 在不同的线程之间使用数据

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
use std::thread;

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

let handle = thread::spawn(move || { // 被move到线程里了
println!("Here's a vector: {:?}", v);
});

// 主线程 不再有v
handle.join().unwrap();
}

Using Message Passing to Transfer Data Between Threads

奶了一口 Golang

Here’s the idea in a slogan from the Go language documentation: “Do not communicate by sharing memory; instead, share memory by communicating.”

mpsc stands for multiple producer, single consumer

mpsc::channel返回tuple = (sending end, receiving end)

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
});

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

接受端 方法有recvtry_recv

recv会阻塞 返回Result<T,E> 当这个channel发送端关闭,recv会收到一个 error

recv_block不会阻塞 会根据是否有 返回Result<T,E>

ownership 在channel间 交接:-) ,send 会 获取参数所有权,并移动这个值 归 接受者所有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::thread;
use std::sync::mpsc;
use std::time::Duration;

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);
}
}

多个发送者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let tx1 = mpsc::Sender::clone(&tx); // <--------------
thread::spawn(move || {
let vals = vec![
String::from("tx1:hi"),
String::from("tx1:from"),
String::from("tx1:the"),
String::from("tx1:thread"),
];

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

Shared-State Concurrency

Consider this part of the slogan from the Go language documentation again: “communicate by sharing memory.”

Using Mutexes(mutual exclusion) to Allow Access to Data from One Thread at a Time

锁嘛=.=

  • You must attempt to acquire the lock before using the data.
  • When you’re done with the data that the mutex guards, you must unlock the data so other threads can acquire the lock.

However, thanks to Rust’s type system and ownership rules, you can’t get locking and unlocking wrong.

The API of Mutex<T>

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::sync::Mutex;

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

{
let mut num = m.lock().unwrap(); // 申请锁 , 返回的是mutable reference to the data value
*num = 6;
// 智能指针自动释放 lock也会自动释放
}

println!("m = {:?}", m); // 释放以后可读
}

.lock()返回一个 MutexGuard的智能指针,wrapped in a LockResult

在线程间 使用 mutex Multiple Ownership with Multiple Threads

Rc<T> is not safe to share across threads,改变reference 计数,它不是一个线程安全的

Atomic Reference Counting with Arc<T>

因为 原子实现有性能代价,所以不是所有库和方法都是原子的

Arc<T>Rc<T>有相同的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
// immutable 但我们 可以获得一个它内部的 mutable
let counter = Arc::new(Mutex::new(0)); // 相对于Rc 这是原子的,如果用Rc 编译器会提示你不安全
let mut handles = vec![];

for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || { // move 语义 只移动了 Arc 的
let mut num = counter.lock().unwrap();

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

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

println!("Result: {}", *counter.lock().unwrap()); // 主线程 还能访问counter 因为没有被 move
}

RefCell<T>/Rc<T> and Mutex<T>/Arc<T>的相似之处

Mutex<T> provides interior mutability, as the Cell family does

之前 我们用 Rc+RefCell来 完成 interior mutablility,现在可以用Mutex+Arc来实现多线程的

但比如死锁 并没有办法完美检测, 不过Rust 用了其它语言用的减轻 deadlock 的策略 来 进行有效的信息提醒

Extensible Concurrency with the Sync and Send Traits

语言中 内嵌了两个 并行概念: the std::marker traits Sync and Send

Send允许 thread之间 传递Ownership

Send marker trait,基本上每个Rust都实现了Send,但有一些例外,包括Rc<T>

会报错the trait Send is not implemented for Rc<Mutex<i32>>

Any type composed entirely of Send types is automatically marked as Send as well. !!!!

这就有意思了,自动 mark为Send

Sync marker trait,意味着这个类型 被多线程引用是安全的

any type T is Sync if &T (a reference to T) is Send,意味这它的引用被发送到另一个 线程是安全的

类似上面 也有基础类型是Sync,以及由Sync组成的依然是Sync

the family of related Cell<T> types are not Sync

Implementing Send and Sync Manually Is Unsafe

不要自己实现,应该遵循 基础类型是,然后 符合类型如果所有子类型是,那么即是的 规则

Object Oriented Programming Features of Rust

该来的 总是会来了 OOP还是来了

Characteristics of Object-Oriented Languages

OOP languages share certain common characteristics, namely objects, encapsulation, and inheritance.

Encapsulation that Hides Implementation Details

之前章节就讲过,rust通过pub来对是否可被外部访问进行控制,并且是对任意的子field可以进行控制

If encapsulation is a required aspect for a language to be considered object oriented, then Rust meets that requirement

Inheritance as a Type System and as Code Sharing

If a language must have inheritance to be an object-oriented language, then Rust is not one

没有办法定义一个 能够继承父struct 字段和方法实现的 struct

如果你希望实现继承,那么你可以根据你的需求来进行选择,要使用 inheritance 有两个主要的原因

  1. 重用代码: 你可以用default trait method implementations instead,比如某trait有默认实现的方法
  2. 子类可以用父类trait 多态:

多态

  • To many people, polymorphism is synonymous with inheritance. But it’s actually a more general concept that refers to code that can work with data of multiple types. For inheritance, those types are generally subclasses.

  • Rust instead uses generics to abstract over different possible types and trait bounds to impose constraints on what those types must provide. This is sometimes called bounded parametric polymorphism.

最近 inheritance 开始 在很多语言的设计方案中,不再受到欢迎,因为它有共享的多余真实需要的代码的风险,甚至让子类中一些方法是无效的

Rust采用了 trait objects instead of inheritance

Using Trait Objects That Allow for Values of Different Types

定义 通用行为的trait

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pub trait Draw {
fn draw(&self);
}
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}

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

和 用泛型 相比, 下面一次只能 替代一个具体类型,而trait对象 允许 运行时 多种类型

1
2
3
4
5
6
7
8
9
10
11
12
pub struct Screen<T: Draw> {
pub components: Vec<T>,
}

impl<T> Screen<T>
where T: Draw {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}

Button/SelectBox的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
pub struct Button {
pub width: u32,
pub height: u32,
pub label: String,
}

impl Draw for Button {
fn draw(&self) {
// code to actually draw a button
}
}

use gui::Draw;

struct SelectBox {
width: u32,
height: u32,
options: Vec<String>,
}

impl Draw for SelectBox {
fn draw(&self) {
// code to actually draw a select box
}
}

// ----

use gui::{Screen, Button};

fn main() {
let screen = Screen {
components: vec![
Box::new(SelectBox { // 在这个位置检查 类型是否符合Trait
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();
}

最终 我们的两个文件

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pub trait Draw {
fn draw(&self);
}
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}

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

pub struct Button {
pub width: u32,
pub height: u32,
pub label: String,
}

impl Draw for Button {
fn draw(&self) {
// code to actually draw a button
}
}

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use gui::{Draw, Screen, Button};

struct SelectBox {
width: u32,
height: u32,
options: Vec<String>,
}

impl Draw for SelectBox {
fn draw(&self) {
// code to actually draw a select box
}
}

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();
}

cargo run运行不报错

下面 我们把lib.rs中的 实现换成 trait bound ,会报错

expected struct SelectBox, found struct gui::Button``

说明了 trait bound 只能一种类型,而 trait objects只会

trait动态分发,运行时确定要调用 什么方法,因为编译器并不知道 具体哪个 类型的实例会调用

at runtime, Rust uses the pointers inside the trait object to know which method to call

代价 是 运行时分析,以及 不能编译时优化,得到的是flexibility

A trait is object safe if all the methods defined in the trait have the following properties:

  • The return type isn’t Self.
  • There are no generic type parameters

但你不能像上面dyn Draw那样来dyn Clone, 因为标准库中的Clone 不是object safe

the trait std::clone::Clone cannot be made into an object

Implementing an Object-Oriented Design Pattern

??????….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
fn content<'a>(&self, post: &'a Post) -> &'a str {
""
}
}

// 然后对Draft分别

struct XXX {}

impl State for XXX {
fn /*或者其它方法*/ approve(self: Box<Self>) -> Box<dyn State> {
self
}
}

完整代码

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
pub struct Post {
content: String,
}

pub struct DraftPost {
content: String,
}

impl 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);
}

pub fn request_review(self) -> PendingReviewPost {
PendingReviewPost {
content: self.content,
}
}
}
pub struct PendingReviewPost {
content: String,
}

impl PendingReviewPost {
pub fn approve(self) -> Post {
Post {
content: self.content,
}
}
}

trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
fn content<'a>(&self, post: &'a Post) -> &'a str {
""
}
}

struct Draft {}

impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}

struct PendingReview {}

impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published {})
}
}

struct Published {}

impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}

fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
use blog::Post;

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());
}

Patterns and Matching

A pattern consists of some combination of the following:

  • Literals
  • Destructured arrays, enums, structs, or tuples
  • Variables
  • Wildcards
  • Placeholders

All the Places Patterns Can Be Used

1
2
3
4
5
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}

特殊的pattern _可以匹配任何的,但它不会binds to a variable

if let一般只关心一个具体情况的,不能&&条件组合,需要条件组合,则把 组合的条件放到括号内

while let可以用来循环

1
2
3
4
5
6
7
8
9
let mut stack = Vec::new();

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

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

// …

Refutability: Whether a Pattern Might Fail to Match

分化为 refutable(只能接受一部分可能的值) 和 irrefutable(可以接受任何可能的传递值).

let x = 5; irrefutable

Some(x) = a_value 可能因为 传入是None而fail

在需要irrefutable的地方 用

1
2
3
4
let some_option_value: Option<i32> = None;
if let Some(x) = some_option_value {
println!("{}", x);
}

如果本来就是irrefutable,Rust会error

1
2
3
4
5
error[E0162]: irrefutable if-let pattern
--> <anon>:2:8
|
2 | if let x = 5 {
| ^ irrefutable pattern

Pattern Syntax

所有 syntax和 使用场景建议

字面量:

1
2
3
4
5
6
7
8
let x = 1;

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

命名变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main() {
let x = Some(5);
let y = 10;

match x {
Some(50) => println!("Got 50"),
// 这个y不是上面的y=10,而是引入的新变量y
Some(y) => println!("Matched, y = {:?}", y),
// 如果None则上面的不会匹配
_ => println!("Default case, x = {:?}", x),
}

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

匹配多个 pattern / 匹配值范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 匹配多个
let x = 1;

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

// 匹配范围
let x = 5;

match x {
// 1 | 2 | 3 | 4 | 5
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"),
}

结构体解构

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

fn main() {
let p = Point { x: 0, y: 7 };

// a , b 被分配
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Point {
x: i32,
y: i32,
}

fn main() {
let p = Point { x: 0, y: 7 };

match p {
// x 是 匹配出的临时, y:0是限制
Point { x, y: 0 } => println!("On the x axis at {}", x),
// x:0 是限制 y是匹配出的临时变量
Point { x: 0, y } => println!("On the y axis at {}", y),
// 都是临时变量
Point { x, y } => println!("On neither axis: ({}, {})", x, y),
}
}

枚举类型 解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
)
}
}
}

nested Structs and Enums 解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}

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

match msg {
// 内嵌结构体和枚举类型 解构
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!(
"Change the color to red {}, green {}, and blue {}",
r,
g,
b
)
},
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!(
"Change the color to hue {}, saturation {}, and value {}",
h,
s,
v
)
}
_ => ()
}
}

合一 :let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });

_忽略,例如 fn foo( _ : i32,y : i32)

匹配时 忽略部分值

1
2
3
4
5
6
7
8
9
10
11
12
13
let mut setting_value = Some(5);
let new_setting_value = Some(10);

match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}

println!("setting is {:?}", setting_value);

_前缀 常见for里

注意,_整个都是忽略掉的,所以不会有move,而_作为前缀,只是忽略掉使用,依然有move。如下列代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 运行报错
let s = Some(String::from("Hello!"));

if let Some(_s) = s { // 被move
println!("found a string");
}

println!("{:?}", s);
// 正常运行
let s = Some(String::from("Hello!"));

if let Some(_) = s { // 没有move
println!("found a string");
}

println!("{:?}", s);

忽略多个..

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

let origin = Point { x: 0, y: 0, z: 0 };

match origin {
Point { x, .. } => println!("x is {}", x),
}

注意的是..的使用需要无 歧义

match guard:

1
2
3
4
5
6
7
8
let num = Some(4);

match num {
// if x < 5 是 match guard ...emmm 相当于 && 条件?
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}

Bindings: @

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)
},
}

Advanced Features

下一章是个具体项目,这章讲也许有一天 你会遇到的rust内容

Unsafe Rust

以前为了 效率 有在golang里用 unsafe来写,但是 你这样做,需要的前提是你确定知道你在干什么

也就是 这部分的使用,rust不会再 保护,一旦你出错了,那rust也无法提醒你

  • Dereference a raw pointer
  • Call an unsafe function or method
  • Access or modify a mutable static variable
  • Implement an unsafe trait

注意的是borrow checker 和其它的Rust's safety checks 依然工作,你能用的unsafe只有上面4种

Keep unsafe blocks small

以及 建议上是把 unsafe的代码,封装成safe的安全抽象,例如一些标准库的内部实现是有unsafe

Dereferencing a Raw Pointer

raw pointers in Rust

*const T*mut T : 不可变raw pointers 和 可变raw pointers

raw pointers上下文中 immutable 意味着 不能直接被 assigned to after being dereferenced.

与 引用 和 smart pointers的不同

  • 可以突破之前4章的 同时多个immutable和mutable指针的限制 Are allowed to ignore the borrowing rules by having both immutable and mutable pointers or multiple mutable pointers to the same location
  • 不保证指向有效内存, Aren’t guaranteed to point to valid memory
  • 可以null, Are allowed to be null
  • 没有任何 自动清理 Don’t implement any automatic cleanup

指针类型强转

1
2
3
4
5
6
7
8
9
10
let mut num = 5;
// 可以写在 safe中
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

unsafe {
// 需要unsafe包裹
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}

直接上地址

1
2
let address = 0x012345usize;
let r = address as *const i32;

Calling an Unsafe Function or Method

1
2
3
4
5
6
7
unsafe fn dangerous() {
// 这里写不安全函数 不需要增加 unsafe块
}

unsafe {
dangerous();
}

把不安全的 操作,封装成 安全的方法

1
2
3
4
5
6
7
8
let mut v = vec![1, 2, 3, 4, 5, 6];

let r = &mut v[..];

let (a, b) = r.split_at_mut(3);

assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);

如何实现split_at_mut

Rust’s borrow checker can’t understand that we’re borrowing different parts of the slice; it only knows that we’re borrowing from the same slice twice.

1
2
3
4
5
6
7
8
9
10
11
12
13
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(); // 返回一个 *mut i32 类型

assert!(mid <= len);

unsafe {
(slice::from_raw_parts_mut(ptr, mid), // 这也是不安全的
slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
}
}

Using extern Functions to Call External Code

调用C代码

1
2
3
4
5
6
7
8
9
extern "C" { // 遵循 "C"的 application binary interface,ABI
fn abs(input: i32) -> i32;
}

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

创建给其它语言用的方法

1
2
3
4
5
6
// 通过 no_mangle 告诉编译器不要mangle 以方便其它语言 安相同方法名调用
#[no_mangle]
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}
// This usage of extern does not require unsafe.

Accessing or Modifying a Mutable Static Variable

如果两个 线程 同时访问一个mutable 全局变量,可能引发data race

1
2
3
4
5
6
// 全局变量
static HELLO_WORLD: &str = "Hello, world!";

fn main() {
println!("name is: {}", HELLO_WORLD);
}

区别是,静态variables 运行时 有固定的内存地址,可以unsafe的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static mut COUNTER: u32 = 0;

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

fn main() {
add_to_count(3); // 单线程,多线程调用可能引发data races

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

Implementing an Unsafe Trait

1
2
3
4
5
6
7
unsafe trait Foo {
// methods go here
}

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

何时使用

当 你有需要时,且你能保证unsafe的代码是safe的时

Advanced Traits

Specifying Placeholder Types in Trait Definitions with Associated Types

1
2
3
4
5
pub trait Iterator {
type Item; // 占位

fn next(&mut self) -> Option<Self::Item>; // 知道返回的是一个Item
}

为什么不写成

1
2
3
pub trait Iterator<T> {
fn next(&mut self) -> Option<T>;
}

With associated types, we don’t need to annotate types because we can’t implement a trait on a type multiple times.

Default Generic Type Parameters and Operator Overloading

Point 重载 Add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 });
}

默认参数

  • 扩展类型而不破坏现有代码。
  • 在大部分用户都不需要的特定情况进行自定义。
1
2
3
4
5
trait Add<RHS=Self> { // 这里RHS=self 是 default type parameters ,默认的参数,不指定则是它本身
type Output;

fn add(self, rhs: RHS) -> Self::Output; // rhs 是 right hand side的缩写,
}

指定 RHS

1
2
3
4
5
6
7
8
9
10
11
12
use std::ops::Add;

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

impl Add<Meters> for Millimeters { // 指定 和 本身Millimeters不同的Meters
type Output = Millimeters;

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

Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name

如何指定 同名字方法中的一个具体方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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;
person.fly(); // 如何指定 默认会调用输出 `*waving arms furiously*`

// 按trait方法
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();
}

fully qualified syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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() {
// 我们希望 puppy
println!("A baby dog is called a {}", Dog::baby_name()); // Spot
println!("A baby dog is called a {}", Animal::baby_name()); // 没有self 无法推断是哪一个 报错
println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); // puppy
}

Implementing the OutlinePrint trait that requires the functionality from Display

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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));
}
}

struct Point {
x: i32,
y: i32,
}

impl OutlinePrint for Point {}

// 会报错 没有实现 std::fmt::Display

修复

1
2
3
4
5
6
7
use std::fmt;

impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}

Using the Newtype Pattern to Implement External Traits on External Types

newtype pattern来摆脱orphan rule that states we’re allowed to implement a trait on a type as long as either the trait or the type are local to our crate的限制

newtype源于 Haskell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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(", ")) // 通过 self.0访问 内部的 Vec<T>
}
}

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

如果你希望Wrapper有所有inner的method,那么在Wrapper上实现Dereftrait

Advanced Types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Kilometers = i32; // 类型别名

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

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

// 用途2简化书写
type Thunk = Box<dyn Fn() + Send + 'static>; // 类型别名简化书写 不过实际编码中不建议使用Thunk这样的模糊意义明明

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

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

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

例如std::io

1
2
3
4
5
6
7
8
9
10
use std::io::Error;
use std::fmt;

pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
fn flush(&mut self) -> Result<(), Error>;

fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}

=

1
type Result<T> = Result<T, std::io::Error>;
1
2
3
4
5
6
7
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;

fn write_all(&mut self, buf: &[u8]) -> Result<()>;
fn write_fmt(&mut self, fmt: Arguments) -> Result<()>;
}

The Never Type that Never Returns : !

1
2
3
fn bar() -> ! {
// --snip--
}

continue has a ! value.

同样panic!视作返回值也是!

dynamically sized types.

which does not work:

1
2
let s1: str = "Hello there!";
let s2: str = "How's it going?";

所以我们一般用的是&str而不是str,有时也有Box<str>,RC<str>

对于trait object

&dyn Trait or Box<dyn Trait>,Rc<dyn Trait>

相对来说//之前讲过

generic<T>这种 需要在编译时知道大小,也就是以下代码

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

会被看做

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

来处理

有特殊语法放宽限制

1
2
3
fn generic<T: ?Sized>(t: &T) { //T may or may not be Sized ,This syntax is only available for Sized, not any other traits.
// --snip--
}

Advanced Functions and Closures

之前闭包Fn

函数指针fn:函数指针实现了所有三个闭包 trait(FnFnMutFnOnce

所以可以在要传递闭包时传递函数指针

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

可以写成

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

一些结构体 默认有它的初始化函数,也就有默认实现闭包

1
2
3
4
5
6
7
8
9
enum Status {
Value(u32), // 默认实现了构造函数初始化语法,对应 闭包
Stop,
}

let list_of_statuses: Vec<Status> =
(0u32..20)
.map(Status::Value)
.collect();

如何返回闭包

1
2
3
fn returns_closure() -> Fn(i32) -> i32 { // 会报错 没有一个可返回的具体类型
|x| x + 1
}

会报错

1
2
3
4
5
6
7
8
9
10
11
error[E0277]: the trait bound `std::ops::Fn(i32) -> i32 + 'static:
std::marker::Sized` is not satisfied
-->
|
1 | fn returns_closure() -> Fn(i32) -> i32 {
| ^^^^^^^^^^^^^^ `std::ops::Fn(i32) -> i32 + 'static`
does not have a constant size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for
`std::ops::Fn(i32) -> i32 + 'static`
= note: the return type of a function must have a statical

可行的方法

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

Macros

我们使用过println!

declarative macros with macro_rules!

procedural macros:

  • Custom #[derive] macros that specify code added with the derive attribute used on structs and enums
  • Attribute-like macros that define custom attributes usable on any item
  • Function-like macros that look like function calls but operate on the tokens specified as their argument

宏可以在编译器翻译代码前展开,Due to this indirection, macro definitions are generally more difficult to read, understand, and maintain than function definitions.

简化vec!的定义 // The actual definition of the vec! macro in the standard library includes code to preallocate the correct amount of memory up front. That code is an optimization that we don’t include here to make the example simpler.

// 看不懂语法 这些 $ *是个啥 =.= 语法https://doc.rust-lang.org/reference/macros.html

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => { // vec![1,2,3]
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x); // 依次等于1,2,3
)*
temp_vec
}
};
}

#[macro_export] 表明,当这个crate被带入scope,macro变得可用

宏模式所匹配的是 Rust 代码结构而不是值

Procedural Macros for Generating Code from Attributes

自定义你的some_attribute

1
2
3
4
5
use proc_macro;

#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}

例如 我们自定义一个derive Macro, 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── hello_macro
│   ├── Cargo.toml
│   ├── hello_macro_derive
│   │   ├── Cargo.toml
│   │   └── src
│   │   └── lib.rs
│   └── src
│   └── lib.rs
└── pancakes
├── Cargo.toml
└── src
└── main.rs

pancakes/Cargo.toml

1
2
3
[dependencies]
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }

pancakes/src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes;

/* 我们希望#[derive(HelloMacro)]宏实现的
impl HelloMacro for Pancakes {
fn hello_macro() {
println!("Hello, Macro! My name is Pancakes!");
}
}
*/

fn main() {
Pancakes::hello_macro();
}

hello_macro/src/lib.rs

1
2
3
pub trait HelloMacro {
fn hello_macro();
}

hello_macro/hello_macro_derive/Cargo.toml

1
2
3
4
5
6
[lib]
proc-macro = true

[dependencies]
syn = "0.14.4"
quote = "0.6.3"

hello_macro/hello_macro_derive/src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
extern crate proc_macro;

use crate::proc_macro::TokenStream;
use quote::quote; // quote crate turns syn data structures back into Rust code.
use syn; // syn crate 解析Rust 代码 为一个我们可以操作的数据结构

// 只要有#[derive(HelloMacro)]注解 就会调用下面的proc_macro_derive(HelloMacro)
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();

// Build the trait implementation
impl_hello_macro(&ast)
}

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
};
gen.into()
}

总的来说流程:

  1. rust编译器检测到derive(XXX)的注解
  2. rust编译器调用对应 proc_macro_derive(XXX)的代码,传入原本的代码
  3. 通过syn工具把 字符串代码变成 可以操作的数据结构
  4. 在数据结构上提取我们需要的 字段,实现我们希望实现的方法,如上面的impl HelloMacro for #name
  5. 通过quote!写回宏产生的Rust代码
  6. gen.into()

我们最需要关注的就是 我们需要ast的哪些信息,以及我们要生成什么代码,而 提取解析和重新插入代码都一句嗯有现成的库了

最后使用一下

cd pancakes && cargo run

正常输出说明我们的宏有作用了

Attribute-like macros

和上面类似

如我们希望实现宏

1
2
#[route(GET, "/")]
fn index() {

那么需要

1
2
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {

和上面比较是,上面是通过derive + proc_macro_derive来工作

现在是自定义XXX + proc_macro_attribute+pub fn 自定义XXX来工作

另一个区别是,现在的实现函数接受两个TokenStream

attr指 如上面GET,"/"这一部分 , 而itemfn index() {等之后的部分

Function-like macros

感觉掌握derive macro的实现以后,后面都很类似了

举例sql!

期望使用let sql = sql!(SELECT * FROM posts WHERE id=1);

对应实现

1
2
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {

Final Project: Building a Multithreaded Web Server

终于 终于 final project了,,,,,,

  • Learn a bit about TCP and HTTP.
  • Listen for TCP connections on a socket.
  • Parse a small number of HTTP requests.
  • Create a proper HTTP response.
  • Improve the throughput of our server with a thread pool.

本章仅仅是教学目的,如果真的要搞web server 以及thread pool,请左转https://crates.io/

关于 http的知识 就略去了,因为有学过,

完整代码

1
2
3
4
5
6
7
hello
├── 404.html
├── Cargo.toml
├── hello.html
└── src
   ├── lib.rs
   └── main.rs

src/main.rs 具体业务 和 pool调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
use std::net::TcpListener;
use std::io::prelude::*;
use std::net::TcpStream;
use std::fs;
use std::thread;
use std::time::Duration;

use hello::ThreadPool;

fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); // 监听端口
let pool = ThreadPool::new(4).unwrap();


for stream in listener.incoming() { // try .take(2) 接受传入的流/网络访问
let stream = stream.unwrap();

pool.execute(|| {
handle_connection(stream); // 处理流内容
});
}
}

fn handle_connection(mut stream: TcpStream) { // 需要mut 关键字
let mut buffer = [0; 512];
stream.read(&mut buffer).unwrap();

let get = b"GET / HTTP/1.1\r\n"; // 正常请求
let sleep = b"GET /sleep HTTP/1.1\r\n";

let (status_line, filename) = if buffer.starts_with(get) {
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
} else if buffer.starts_with(sleep) {
thread::sleep(Duration::from_secs(5)); // 模拟慢请求
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
} else {
("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
};

let contents = fs::read_to_string(filename).unwrap(); // 读取文件

let response = format!("{}{}", status_line, contents); // 返回给浏览器

stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}

src/lib.rs

作为教学目的实现的ThreadPool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 下面代码的结构
ThreadPool
Vec< Worker>
id : worker 在Vec的index,用来标识?
thread : 每个worker一个线程
接受到channel的值后 match 结构体是Job还是Terminate
Job执行方法 封了一个call_box,实际就是调用传递的闭包方法?
通过lock来
Sender

new():初始化 Workers 和 Sender
execute():让Sender通过channel发送人物给worker 要执行的闭包给Worker

channel: 结构体 分化为 Job:Box<FnBox + Send + 'static>或Terminate
drop trait:
要结束ThreadPool时,让sender发送workersize次 Terminate
通过 worker.thread.take() + .join()拿去并等待线程结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use std::error;
use std::fmt;
use std::thread;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;

#[derive(Debug, Clone)]
pub struct PoolCreationError;

impl fmt::Display for PoolCreationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Thread pool creation failed!")
}
}

impl error::Error for PoolCreationError {
fn description(&self) -> &str {
"Thread pool creation failed!"
}

fn cause(&self) -> Option<&error::Error> {
None
}
}

pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Message>,
}

enum Message {
NewJob(Job),
Terminate,
}

type Job = Box<FnBox + Send + 'static>;

impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
pub fn new(size: usize) -> Result<ThreadPool, PoolCreationError> {
if size == 0 {
return Err(PoolCreationError);
}

let (sender, receiver) = mpsc::channel();

let receiver = Arc::new(Mutex::new(receiver));

let mut workers = Vec::with_capacity(size);

for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver))); // 启动多个worker
}

Ok(ThreadPool {
workers,
sender,
})
}

pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static
{
let job = Box::new(f);

self.sender.send(Message::NewJob(job)).unwrap();
}
}

impl Drop for ThreadPool {
fn drop(&mut self) {
println!("Sending terminate message to all workers.");

for _ in &mut self.workers {
self.sender.send(Message::Terminate).unwrap();
}

println!("Shutting down all workers.");

for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);

if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}

struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}

trait FnBox {
fn call_box(self: Box<Self>);
}

impl<F: FnOnce()> FnBox for F {
fn call_box(self: Box<F>) {
(*self)()
}
}

impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let message = receiver.lock().unwrap().recv().unwrap();

match message {
Message::NewJob(job) => {
println!("Worker {} get a job; executing.", id);

job.call_box();
},

Message::Terminate => {
println!("Worker {} was told to terminate.", id);
break;
},
}

}
});

Worker {
id,
thread: Some(thread),
}
}
}

最后是两个静态html

404.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello!</title>
</head>
<body>
<h1>Oops!</h1>
<p>Sorry, I don't know what you're asking for.</p>
</body>
</html>

hello.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello!</title>
</head>
<body>
<h1>Hello!</h1>
<p>Hi from Rust</p>
</body>
</html>

最后cargo run然后用浏览器,或者curl/wget就可以尝试访问了

Appendix

附录就不弄过来了 https://doc.rust-lang.org/book/appendix-00.html

参考

https://www.rust-lang.org/learn/get-started

最后一章说but we’ve reached the end of the book,好吧 我最开始就想看个tutorial,最后看了一個book

TODO

重新整理这篇文章,把该加 代码标签的加上代码标签