Rust 基础

参考 Rust DocumentationTour of Rustcrates.io

Getting Started

Hello, World!

1
2
3
fn main() {
println!("Hello, world!");
}
1
2
3
$ rustc main.rs
$ ./main
Hello, world!

Common Programming Concepts

Variables and Mutability

使用 let 声明变量,变量默认是不可变的,使用 mut 关键字使其可变。使用 const 声明常量,不允许对常量使用 mut,并且必须显示指定其类型。

1
2
let mut x = 5;
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

和其他语言不同,Rust 允许在相同作用域中遮蔽(shadowing)变量,而且允许使用不同的类型。

1
2
let spaces = "   ";
let spaces = spaces.len();

Data Types

Rust 数据类型分为两种:标量类型(scalar)和复合类型(compound)。有四种基本的标量类型:整型、浮点类型、布尔类型和字符类型。编译器通常可以推断出类型,如果不能则需要显式写出类型。

1
let guess: u32 = "42".parse().expect("Not a number!");

Scalar Types

有符号整型有 i8i16i32i64i128isize,其中 isize 类型和机器相关。无符号整型类似,只是将 i 开头替换为 u 开头。整型字面量:十进制 98_222、十六进制 0xff、八进制 0o77、二进制 0b1111_0000 和单字节字符 b'A'(仅限 u8 类型)。可以使用标准库函数显示处理整型溢出:wrapping_*checked_*overflowing_*saturating_*

浮点类型有 f32f64,默认使用 f64。布尔类型 bool 的值有 truefalse 两种。字符类型 char 大小为 4 字节,使用 UTF-32 编码方式。

Compound Types

Rust 有两个原生的复合类型,元组(tuple)和数组(array)。元组可以将一个或多个不同类型的值组合起来,声明之后长度固定不变。可以使用 .index、模式匹配(pattern matching)和解构(destructure)获取元组元素,其中 index 是元素的索引。不包含任何值的元组 () 被称为单元(unit),表示空值或空返回类型,如果表达式不返回其他值,则会隐式地返回单元值。

1
2
3
4
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
1
2
3
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {y}");

数组中每个元素的类型都必须相同,数组长度也是固定的,在栈上分配空间。在声明类型时,需要在方括号中包含元素类型和数量。在初始化时,可以使用方括号包含初始值和元素数量,创建每个元素都相同的数组。

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

Functions & Control Flow

Rust 代码中的变量和函数名使用 snake case 风格,使用下划线分隔单词。Rust 是基于表达式的语言(expression-based),函数体由一系列语句(statement)和可选地结尾表达式(expression)组成,语句不会返回值而表达式会。表达式结尾没有分号,如果加上分号它就变为语句。如果函数没有返回值,则可以省略返回类型,会隐式地返回空元组,否则需要使用 -> 显式声明。代码块 {}if 都是表达式,可以在 let 语句右侧使用。

1
2
3
fn five() -> i32 {
5
}
1
2
3
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");

Rust 有三种循环 loopwhileforloop 表示无限循环,可以使用 break 终止并指定返回值。

1
2
3
4
5
6
7
8
9
10
let mut counter = 0;

let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};

println!("The result is {result}");
1
2
3
4
5
for i in 0..n { ... }
for i in (0..n).rev() { ... }
for item in &arr { ... }
for item in &mut arr { ... }
for (i, item) in arr.iter().enumerate() { ... }

Understanding Ownership

What Is Ownership?

所有权(ownership)是 Rust 用于管理内存的一组规则。栈中的所有数据都必须占用已知且固定的大小,在编译时大小未知或大小可能变化的数据,需要存储在堆上,所有权主要目的就是管理堆数据。所有权规则:① Rust 中的每个值都有所有者(owner);② 每个值在任意时刻有且仅有一个所有者;③ 当所有者离开作用域之后,该值将被丢弃。

1
2
3
4
{
let s = String::from("hello"); // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no longer valid

所有权(Ownership)机制让 Rust 无需 GC 就能保证内存安全。GC 是运行时根据可达性分析回收内存,而所有权是在编译时确定变量的作用域,当内存的所有者变量离开作用域之后,相应内存就可以被释放。当变量离开作用域时,Rust 会自动调用 drop 函数来释放内存,类似 C++ 的 RAII 机制。

字符串 String 的数据存储在堆上,字符串变量包含指向堆中数据的指针、数据的长度和容量,其存储在栈上。使用 let s2 = s1; 只会复制引用(浅拷贝),为避免 s1s2 离开作用域之后,释放相同内存两次(调用 drop 函数),该语句执行之后 Rust 会使 s1 无效,不需要在其离开作用域之后回收内存,该操作被称为移动(move)。

1
2
3
let s1 = String::from("hello");
let s2 = s1;
println!("{s1}, world!"); // error[E0382]: borrow of moved value: `s1`

当给已有值的变量赋新值时,Rust 会立即调用 drop 释放原始值的内存。如果想要执行深拷贝,可以使用 clone 函数。如果类型实现 Copy 特征,则旧变量在被赋值给其他变量之后仍然有效。不能为实现 Drop 特征的类型实现 Copy 特征。

1
2
3
let mut s = String::from("hello");
s = String::from("ahoy");
println!("{s}, world!");

References and Borrowing

引用(reference)类似指针,它是一个地址,但是和指针不同,Rust 保证引用指向某个特定类型的有效值。传递引用值不会转移所有权,所以可以在 main 中继续使用 s1。函数 calculate_length 中的局部变量 s 是引用类型,其不拥有值的所有权,所以离开作用域之后,其指向的值也不会被回收,所以创建引用的行为被称为借用(borrowing)。

1
2
3
4
5
6
7
8
9
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
s.len()
}

默认不能通过引用 & 修改指向的值,除非声明的是可变引用 &mut,只能对可变变量(使用 let mut 声明)创建可变引用。如果变量有不可变引用,则原变量可读但不可写。如果变量有可变引用,则原变量不可读写。如果变量有一个可变引用,则该变量不能有其他引用,不论是否可变。以上机制可以保证在编译时就避免数据竞争(data race),因为最多有一个线程可以修改变量,而且此时其他线程都无法读取该变量。

1
2
3
4
5
6
7
8
fn main() {
let mut s = String::from("hello");
change(&mut s);
}

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

引用的作用域从声明的地方开始直到最后一次使用为止,下面的不可变引用 r1r2 的最后一次使用,发生在 r3 的声明之前,所以不会发生编译错误。

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

let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{r1} and {r2}");
// Variables r1 and r2 will not be used after this point.

let r3 = &mut s; // no problem
println!("{r3}");

Rust 编译器保证数据不会在其引用之前离开作用域,即不允许悬垂引用,否则会发生编译错误。在其他语言中,C++ 存在悬垂引用,Java 中没有原始指针类型不存在这个问题,Go 中的变量生命周期由可达性决定而不是作用域,通过逃逸分析局部变量会在堆上分配,所以也可以避免悬垂引用。

1
2
3
4
5
6
7
8
fn main() {
let reference_to_nothing = dangle();
}

fn dangle() -> &String { // error[E0106]: missing lifetime specifier
let s = String::from("hello");
&s // error[E0515]: cannot return reference to local variable `s`
}

The Slice Type

切片(slice)是引用类型,不拥有值的所有权。切片仅由指向数据的指针和长度组成,不像 Go 中还包含容量字段。字符串切片的索引必须位于有效的 UTF-8 字符边界上,如果从多字节字符的中间位置创建切片,则会产生运行时错误。

1
2
3
4
let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..];

字符串切片的类型有 &str&mut str,区别在于指向的数据是否可变。使用 let mut 声明变量,只是表示可以修改变量绑定的切片,和切片数据是否可变无关。字符串字面量的类型是 &str,所以是不可变的,如果想要获取可变字符串切片,需要使用 String 结合 mut

1
let mut s = "Hello, world!";
1
2
let mut s = String::from("hello");
let slice = &mut s[..];

Defining and Instantiating Structs

在初始化结构体时,如果字段名称和初始化参数相同,则可以省略。如果要使用旧实例的大部分值创建新实例,可以使用结构体更新语法(struct update syntax),.. 表示剩余未显式设置的字段和给定旧实例具有相同的值。该语法类似使用 = 进行赋值,所以未实现 Copy 特征的类型会被转移所有权,旧实例的相应字段会失效。

1
2
3
4
5
6
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
1
2
3
4
5
6
7
8
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
1
2
3
4
5
6
7
8
fn main() {
// --snip--

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

可以定义元组结构体(tuple struct),元组结构体的字段没有名称只有类型。即使两个元组结构体有相同类型的字段,它们也是不同的类型。可以解构元组结构体,但是需要指定结构体的类型。

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

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

没有任何字段的结构体被称为类单元结构体(unit-like struct)。如果在结构体中存储引用类型 &str,而不是自身拥有所有权的 String 类型,就需要使用生命周期,否则会发生编译错误。

1
2
3
4
5
struct AlwaysEqual;

fn main() {
let subject = AlwaysEqual;
}
1
2
3
4
5
6
struct User {
active: bool,
username: &str, // error[E0106]: missing lifetime specifier
email: &str, // error[E0106]: missing lifetime specifier
sign_in_count: u64,
}

Method Syntax

方法的第一个参数必须是 self,表示调用该方法的结构体实例,&self 实际上是 self: &Self 的缩写。在 impl 块中,Self 类型是 impl 块类型的别名,在下面就是 Rectangle。方法可以选择不可变借用 &self、可变借用 &mut self 或获取所有权 self。方法的名称可以和结构体的字段相同(Java 中也可以,Go 中不行)。当调用方法时,Rust 会自动根据方法签名添加 &&mut*,所以不需要显式转换。

1
2
3
4
5
6
7
8
9
10
struct Rectangle {
width: u32,
height: u32,
}

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

impl 中定义的函数被称为关联函数(associated function),其第一个参数不是 self(类似 Java 中的静态方法)。调用关联函数需要使用 Rectangle::square(x) 形式,表示函数 square 位于结构体 Rectangle 的命名空间中。每个结构体可以有多个 impl 块,不过必须和结构体在相同的 crate 中。

1
2
3
4
5
6
7
8
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}

Enums and Pattern Matching

Defining an Enum

枚举类型的字段被称为变体(variant),可以将数据附加到变体上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum IpAddrKind {
V4,
V6,
}

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

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

let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
1
2
3
4
5
6
7
8
enum IpAddr {
V4(String),
V6(String),
}

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

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

每个变体附加数据的类型和数量可以不同,可以使用 impl 为枚举类型定义方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

impl Message {
fn call(&self) {
// method body would be defined here
}
}

let m = Message::Write(String::from("hello"));
m.call();

The match Control Flow Construct

可以使用变量名称之后跟 => 表示通配模式,放在最后以匹配其他情况,如果不使用变量则可以使用 _ => 形式。

1
2
3
4
enum Option<T> {
None,
Some(T),
}
1
2
3
4
5
6
7
8
9
10
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);

Concise Control Flow with if let and let else

1
2
3
4
5
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {max}"),
_ => (),
}
1
2
3
4
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {max}");
}
1
2
3
4
5
6
7
8
9
10
11
fn describe_state_quarter(coin: Coin) -> Option<String> {
let Coin::Quarter(state) = coin else {
return None;
};

if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
}

Common Collections

Storing Lists of Values with Vectors

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

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

let third: Option<&i32> = v.get(2);
match third {
Some(third) => println!("The third element is {third}"),
None => println!("There is no third element."),
}
1
2
3
4
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}

Storing UTF-8 Encoded Text with Strings

Rust 中有两种字符串,字符串切片 str 和字符串 String,它们使用的都是 UTF-8 编码,String 是对 Vec<u8> 的封装。使用 + 运算符拼接 String 字符串,会获取第一个参数的所有权,以及第二个参数的引用,类似下面的 add 函数。

实际上,&s2 会被转换为 &s2[..],从而满足 add 函数的 &str 参数类型,该技术被称为 dref coercion。s3 会获取 s1 的所有权,然后将 s2 复制到其末尾。也可以使用 format! 宏执行字符串拼接,该宏不会获取任何参数的所有权。

1
2
3
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
1
fn add(self, s: &str) -> String {
1
2
3
4
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{s1}-{s2}-{s3}");

由于字符串使用 UTF-8 编码,所以不支持使用索引访问,字符串的长度是其包含的字节数而不是字符数。在 Go 语言中,可以直接使用索引,访问的就是对应的字节。在 Rust 中,虽然不能使用索引访问字符串,但是可以使用范围获取字符串切片,前提是范围位于有效的 UTF-8 字符边界上。

1
2
let s1 = String::from("hi");
let h = s1[0]; // error[E0277]: the type `str` cannot be indexed by `{integer}`
1
2
let hello = "Здравствуйте";
let s = &hello[0..4];

使用 chars() 方法遍历字符,bytes 方法遍历字节。

1
2
3
4
5
6
for c in "Зд".chars() {
println!("{c}");
}
for b in "Зд".bytes() {
println!("{b}");
}

Storing Keys with Associated Values in Hash Maps

在使用 insert 时,对于实现 Copy 特征的类型,其值会复制到哈希表中,而拥有所有权的类型,其值会被移动到哈希表中。如果将引用插入哈希表,则引用指向的值,必须至少和哈希表的生命周期一样长(避免悬垂引用)。哈希表使用 SipHash 哈希函数,可以抵御涉及哈希表的拒绝服务攻击,允许用户指定其他哈希函数。

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

let mut scores = HashMap::new();

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

let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);

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

Error Handling

Unrecoverable Errors with panic!

panic 表示不可恢复的错误,在遇到 BUG 或显示调用 panic! 宏时产生。当出现 panic 时,程序默认会执行展开(unwinding),这意味着 Rust 会回溯函数栈并清理函数的数据。另一种选择是直接终止(abort),不清理数据就退出程序,此时程序所使用的内存由操作系统清理。

Recoverable Errors with Result

可以使用闭包和 unwrap_or_else 方法替代 match 来简化代码。使用 unwrap 方法,如果 Result 值是 Ok,则返回其中的值,否则会调用 panic!expect 方法类似,只是可以自定义 panic! 的错误信息。

1
2
3
4
enum Result<T, E> {
Ok(T),
Err(E),
}
1
2
3
4
5
6
7
8
9
10
use std::fs::File;

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

let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {error:?}"),
};
}
1
2
3
4
5
6
7
8
use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?;
Ok(username)
}

Generic Types, Traits, and Lifetimes

Rust 在编译时对泛型代码单态化(monomorphization)来保证效率,即为使用的具体类型生成对应类型的代码。在其他语言中,C++ 的做法类似,而 Java 执行类型擦除。

1
fn largest<T>(list: &[T]) -> &T {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Point<T> {
x: T,
y: T,
}

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

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

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

println!("p.x = {}", p.x());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Point<X1, Y1> {
x: X1,
y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
Point {
x: self.x,
y: other.y,
}
}
}

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

let p3 = p1.mixup(p2);

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

Traits: Defining Shared Behavior

孤儿规则(orphan rule):只有在特征或类型至少有一个属于当前 crate 时,才能对类型实现特征。也就是说,可以为内部类型实现外部特征、为外部类型实现内部特征,但是不能为外部类型实现外部特征。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pub trait Summary {
fn summarize_author(&self) -> String;

fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}

pub struct SocialPost {
pub username: String,
pub content: String,
pub reply: bool,
pub repost: bool,
}

impl Summary for SocialPost {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
1
2
3
pub fn notify(item: &(impl Summary + Display)) {

pub fn notify<T: Summary + Display>(item: &T) {
1
2
3
4
5
6
7
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

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

Validating References with Lifetimes

Rust 中每个引用都有生命周期(lifetime),通常生命周期可以被隐式地推断出来,否则需要显式写出。生命周期的主要目标是避免悬垂引用,Rust 编译器使用借用检查器(borrow checker)比较作用域,确保所有的借用都是有效的。rx 的生命周期分别被标记为 'a'b,编译器会发现 r 引用的变量 x 的生命周期比自身更小,这会导致悬垂引用,所以编译器会报错。

1
2
3
4
5
6
7
8
9
10
fn main() { // error[E0597]: `x` does not live long enough
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {r}"); // |
} // ---------+
1
2
3
&i32        // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

生命周期注解语法用于将函数参数和返回值的生命周期相关联,生命周期也是泛型。以下函数签名表示,泛型生命周期参数 'a 的具体生命周期是 xy 中生命周期的较小者,返回值不能在生命周期 'a 结束之后使用。由于下面的 resulty 生命周期结束之后访问,所以会引发编译错误。

1
2
3
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
1
2
3
4
5
6
7
8
9
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()); // error[E0597]: `string2` does not live long enough
}
println!("The longest string is {result}");
}

结构体可以存储引用类型,不过此时需要使用生命周期注解。下面注解意味着,ImportantExcerpt 实例不能在其字段 part 的生命周期结束之后使用。

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

生命周期省略规则(lifetime elision rules):① 为每个引用参数分配不同的生命周期;② 如果只有一个输入生命周期参数,则将其生命周期赋给所有输出生命周期参数;③ 如果输入生命周期参数有 &self&mut self,则将 self 的生命周期赋给所有输出生命周期参数。如果应用规则之后,仍没有计算出引用的生命周期,则会引发编译错误。

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

静态生命周期 'static 表示能够在程序运行期间存活,所有字符串字面量都具有静态生命周期。

1
let s: &'static str = "I have a static lifetime.";
1
2
3
4
5
6
7
8
9
10
11
12
13
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 }
}

Functional Language Features: Iterators and Closures

Closures: Anonymous Functions That Capture Their Environment

闭包(closures)是可以保存在变量中或作为参数传递给其他函数的匿名函数,和函数不同,它允许捕获其被定义时所在作用域中的值。通常不需要为闭包的参数和返回值声明类型注解,编译器可以推断出类型。必须调用 add_one_v3add_one_v4 闭包才能通过编译,编译器会根据调用上下文推断类型,对相同闭包使用不同的类型会引发编译错误。

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 ;

闭包有三种捕获变量的方式:不可变借用、可变借用和获取所有权。前两种方式是隐式推断的,获取所有权使用 move

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

fn main() {
let list = vec![1, 2, 3];
println!("Before defining closure: {list:?}");

thread::spawn(move || println!("From thread: {list:?}"))
.join()
.unwrap();
}

闭包可以执行:将捕获的值移出闭包、既不移动也不修改值、

闭包捕获和处理值的方式会影响闭包实现哪些特征:FnOnceFnMutFn 特征分别在获取所有权、可变借用和不可变借用时实现。它们之间是父子关系(和继承无关,只是指定依赖关系),FnOnce <- FnMut <- Fn。如果闭包不需要从环境中捕获值,可以直接使用函数而不是闭包,例如在 Option<Vec<T>> 值上调用 unwrap_or_else(Vec::new)

1
2
3
4
5
6
7
8
9
10
11
impl<T> Option<T> {
pub fn unwrap_or_else<F>(self, f: F) -> T
where
F: FnOnce() -> T
{
match self {
Some(x) => x,
None => f(),
}
}
}
1
2
3
4
5
6
7
8
9
impl<T> [T] {
pub fn sort_by_key<K, F>(&mut self, mut f: F)
where
F: FnMut(&T) -> K,
K: Ord,
{
stable_sort(self, |a, b| f(a).lt(&f(b)));
}
}

Processing a Series of Items with Iterators

iter 方法生成不可变引用的迭代器,iter_mut 生成可变引用的迭代器,into_iter 生成获取所有权的迭代器。

1
2
3
4
5
6
7
pub trait Iterator {
type Item;

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

// methods with default implementations elided
}
1
2
3
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]);