Rust 学习笔记

基本输入输出

输入并输出

1
2
3
4
5
6
7
use std::io;

fn main() {
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
println!("input is: {}", input);
}
  • stdin() 函数获取标准输入的句柄
  • read_line(buf: &mut String) -> Result<usize> 方法读取一行输入,并将其存储在传入的 String 变量中。成功时,返回读取的字节总数
  • println! 宏用于打印输出,类似于 C 语言中的 printf 函数。它会自动添加换行符
  • unwrap() -> T 方法解包 Result 类型,返回 Ok 中的值或引发 panic
  • 在使用 use 语句时一般引入到所用函数或模块的上一级,可以避免命名冲突

读取多行输入

1
2
3
4
5
6
7
8
use std::io;

fn main() {
let lines = io::stdin().lines();
for line in lines {
println!("got a line: {}", line.unwrap());
}
}
  • lines() 方法返回一个迭代器,迭代器产生的元素末尾不含换行符

格式化输出

  • format! 宏用于格式化字符串,返回一个 String
  • print! 宏格式化文本并打印到标准输出
  • println! 宏格式化文本并打印到标准输出,末尾自动添加换行符
  • eprint! 宏格式化文本并打印到标准错误输出
  • eprintln! 宏格式化文本并打印到标准错误输出,末尾自动添加换行符

以上宏的格式化语法全部相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// {} 用作占位符
println!("Hello, {}!", "world");

// 位置参数
println!("{1} {0}", "world", "hello");
// 多个 {} 的行为类似迭代器,依次填充参数
println!("{0} {} {1} {}", "hello", "world"); // => hello hello world world

// 命名参数
println!("{greeting} {name}", greeting="hello", name="world");
// 可以引用变量
let greeting = "hello";
let name = "world";
println!("{greeting} {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
// 宽度 {:5},表示宽度为5
println!("Hello, {:5}!", "rust"); // "Hello, rust !"
// 宽度值可以使用变量,需要$后缀
println!("Hello, {1:0$}!", 5, "rust");
println!("Hello, {:width$}!", "rust", width=5);

// 对齐
println!("Hello, {:<6}!", "rust"); // "Hello, rust !" 左对齐
println!("Hello, {:^6}!", "rust"); // "Hello, rust !" 居中对齐
println!("Hello, {:>6}!", "rust"); // "Hello, rust!" 右对齐

// 数字格式
println!("{:-^12}", "数字");
println!("Hello, {:+}!", 42); // "Hello, +42!" 始终显示符号
println!("Hello, {:#x}!", 42); // "Hello, 0x2a!" 16进制,如果不带#,则不显示前缀
println!("Hello, {:#X}!", 42); // "Hello, 0x2A!" 16进制大写
println!("Hello, {:#o}!", 42); // "Hello, 0o52!" 8进制
println!("Hello, {:#b}!", 42); // "Hello, 0b101010!" 2进制
println!("Hello, {:#e}!", 42.0); // "Hello, 4.2e1!" 科学计数法
println!("Hello, {:#E}!", 42.0); // "Hello, 4.2E1!" 科学计数法大写
println!("Hello, {:05}!", 42); // "Hello, 00042!" 设置宽度并填充0
println!("Hello, {:.2}!", 42.12345); // "Hello, 42.12!" 设置精度
println!("Hello, {:.1$}!", 42.12345, 2); // "Hello, 42.12!" 使用参数设置精度,命名参数也可以

// 花括号转义,{{ 表示 { , }} 表示 }
println!("{{ Hello }}");

#[derive(Debug)] // 自动实现Debug trait
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 1, y: 2 };
println!("{:?}", p); // debug输出
println!("{:#?}", p); // debug美观输出

Display trait

要在 println! 中以 {} 的形式输出自定义类型,需要实现 Display trait。在 fmt 方法中用 write! 宏格式化输出。

1
2
3
4
5
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}

基本类型

  • 有符号整数:i8i16i32i64i128isize(指针宽度)
  • 无符号整数: u8u16u32u64u128usize(指针宽度)
  • 浮点数: f32f64
  • char:单个 Unicode 字符,如 'a','α' 和 ' 锈'
  • bool:truefalse
  • 单元类型:(),空元组
  • 数组:[T; N],长度为 N 的数组,元素类型为 T
  • 元组:(T1, T2, ...),可以包含不同类型的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 显式类型注解
let x: i32 = 5;
let pi: f64 = 3.14;
let c: char = 'a';

let w:i32 = pi as i32; // 类型转换

// 编译器会根据上下文推断类型
let y = 5; // 整数默认为i32
let z = 5u32; // 字面量加类型后缀指定类型
let f = 0.1; // 浮点数默认为f64
let b = true;

// 数组
let arr: [i32; 5] = [1, 2, 3, 4, 5]; // 数组,长度为5
let zero_arr = [0; 5]; // 初始化为相同值
println!("First element: {}", arr[0]); // 访问数组元素
println!("Array length: {}", arr.len()); // 数组长度
println!("Slice: {:?}", &arr[1..3]); // 切片

// 元组
let tup: (i32, f64, char) = (1, 2.0, 'a'); // 元组
println!("First element: {}", tup.0); // 访问元组元素

变量绑定

  • let 用于将值绑定到变量。
  • 绑定默认不可变
  • 使用 mut 关键字使变量可变
1
2
3
4
5
6
7
8
let x = 5; // 不可变绑定
let mut y = 10; // 可变绑定
y = 20; // 修改可变变量的值
println!("x: {}, y: {}", x, y); // 输出 x: 5, y: 20

let z; // 声明变量z
z = 30; // 变量z在使用前必须初始化
println!("z: {}", z); // 输出 z: 30
  • 变量绑定的作用域限定在一个代码块中
  • 变量可以在同一作用域中被重新绑定,称为遮蔽(shadowing)
1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let mut x = 5;
{
let x = "hello"; // 遮蔽外部变量x,且x为不可变
println!("Inner x: {}", x); // 输出 Inner x: hello

let y = 20;
} // y的作用域结束,在外部不可使用y

x = 10; // 修改外部变量x的值
println!("Outer x: {}", x); // 输出 Outer x: 10
}

自定义类型

结构体

结构体有三种类型: * 元组结构体:没有字段名,只有字段类型 * 命名结构体:有字段名和字段类型 * 单元结构体:没有字段,只有名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Unit; // 单元结构体

struct Point(i32, i32); // 元组结构体

#[derive(Debug)]
struct Person {
name: String,
age: u8,
} // 命名结构体

let p = Point(1, 2); // 创建元组结构体实例
// let p: Point = (1, 2); // 错误,元组结构体和对应结构的元组不是同一类型
let (x, y) = (3, 4);
let p = Point(x, y); // 用变量创建元组结构体实例
let p = Point { 0: 5, 1: 6 }; // 使用字段索引创建元组结构体实例
println!("Point: ({}, {})", p.0, p.1); // 访问元组结构体字段


let alice = Person { name: String::from("Alice"), age: 22 }; // 创建命名结构体实例
let name = String::from("Bob");
let age = 25;
let bob = Person { name, age }; // 变量名和字段名相同,可以省略字段名
let charlie = Person { name: String::from("Charlie"), ..person }; // 结构体更新语法,省略其他字段

枚举

枚举用于定义一个类型,它可以是多个不同的值之一。Rust 中的枚举有点类似于 C 语言中的联合体,但更强大。

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Direction {
Up,
Down,
Left,
Right,
} // 类似其他语言中的枚举

// 枚举可以包含数据,类似于联合体
enum Action {
Move { x: i32, y: i32 },
Speak(String),
Stop,
}

关联函数和方法

  • 结构体和枚举都可以使用 impl 块来实现关联函数或方法
  • 方法是依赖于实例的函数,必须有一个 self 参数。self 是结构体或枚举的实例
  • 关联函数和方法类似于面向对象语言中的静态方法和实例方法
1
2
3
4
5
6
7
8
9
10
11
12
13
impl Person {
fn new(name: String, age: u8) -> Person { // 关联函数
Person { name, age }
}

fn greet(&self) { // 方法,&self表示不可变引用
println!("Hello, my name is {} and I'm {} years old.", self.name, self.age);
}

fn rename(&mut self, name: String) { // 可变方法,&mut self表示可变引用
self.name = name;
}
}

类型别名

类型别名用于给现有类型起一个新的名字。它不会创建新的类型,只是给现有类型起一个别名。类似于 C 语言中的 typedef

1
2
3
4
5
type Int = i32; // 给i32起一个别名Int
type Point = (i32, i32); // 给元组类型起一个别名Point

let x: Int = 5; // 使用类型别名
let p: Point = (1, 2);

控制流

判断条件不加括号

if 语句

1
2
3
4
5
6
7
8
9
if x > 5 {
println!("x is greater than 5");
} else if x < 5 {
println!("x is less than 5");
} else {
println!("x is equal to 5");
}

let positive = if x > 0 { true } else { false }; // 类似于三元运算符的用法

loop 循环

loop 是无限循环

1
2
3
4
5
6
7
8
9
let cnt = 0;

loop {
println!("cnt: {}", cnt);
if cnt == 10 {
break; // 退出循环
}
cnt += 1;
}

可以从 loop 循环中返回值

1
2
3
4
5
6
7
8
let mut x = 1;
let result = loop {
x = x * 2
if x > 1000 {
break x; // 返回值
}
};
println!("Result: {}", result); // 输出 Result: 1024

while 循环

1
2
3
4
5
let mut x = 1;
while x < 5 {
println!("x: {}", x);
x += 1;
}

for 循环

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
for i in 1..5 { // 1..5表示从1到4,如果要包含5,使用1..=5
println!("i: {}", i);
}

for i in (1..5).rev() { // 反向迭代
println!("i: {}", i);
}

let arr = [1, 2, 3, 4, 5];
for i in arr { // 可以遍历数组
println!("i: {}", i);
}

let mut names = vec!["Alice", "Bob", "Charlie"];
for name in names.iter() { // iter()迭代中的元素是不可变引用
println!("Name: {}", name);
}

for name in names.iter_mut() { // iter_mut()迭代中的元素是可变引用
*name = "Rust"; // 修改数组元素
}

// 等同于 for name in names
for name in names.into_iter() { // into_iter()迭代中的元素会被消耗
println!("Name: {}", name);
}

// 不能再使用names,因为它已经被消耗

循环标签

Rust 允许在循环前加标签,以便在嵌套循环中使用 breakcontinue 跳出指定的循环

1
2
3
4
5
6
7
8
'outer: for i in 0..5 {
for j in 0..3 {
if i == 2 && j == 2 {
break 'outer; // 跳出外层循环
}
println!("i: {}, j: {}", i, j);
}
}

模式匹配

可以在 matchif letwhile letforlet 和函数参数中使用模式匹配。

match 语句

match 语句用于模式匹配,可以匹配枚举、整数、字符、字符串等类型。类似于 switch 语句,但更强大

1
2
3
4
5
6
7
8
// 匹配字面值
let x = 5;
match x {
1 => println!("One"), // 匹配单个值
2 | 3 => println!("Two or Three"), // 匹配多个值
4..=10 => println!("Four to Ten"), // 匹配范围
_ => println!("Other"), // 匹配其他值
}
1
2
3
4
5
6
7
// 匹配变量
let x = Some(5);
match x {
Some(1) => println!("One"),
Some(n) => println!("Some {}", n), // 匹配变量
_ => println!("None"),
}

if letwhile let

if let 在表达式满足模式时执行代码块。它是 match 的简化版本,适用于只关心一个模式的情况。

1
2
3
4
5
6
let x = Some(5);
if let Some(n) = x { // if let 模式 = 表达式
println!("Some {}", n); // 匹配成功
} else { // else分支是可选的
println!("None"); // 匹配失败
}

while let 在表达式满足模式时执行循环。

1
2
3
4
5
6
7
8
9
10
let mut x = Some(1);
while let Some(n) = x { // while let 模式 = 表达式
if n > 9 {
println!("x is {}, enough", n); // 匹配成功
x = None;
} else {
println!("x is {}, not enough", n); // 匹配成功
x = Some(n + 1); // 更新x的值
}
}

守卫语句和 @绑定

守卫语句可以在模式匹配时添加额外的 if 条件。

1
2
3
4
5
6
7
8
let x = 5;
let y = 10;
match x {
1 => println!("One"),
n if n < y => println!("{} is less than {}", n, y), // 守卫语句
n if n > y => println!("{} is greater than {}", n, y),
_ => println!("Other"),
}

@绑定可以在测试一个值是否匹配模式的同时将该值绑定到变量上

语法为 variable @ pattern

1
2
3
4
5
6
let x = 5;

match x {
n @ 1..=10 => println!("value in range 1 to 10: {}", n), // n 绑定为 5
_ => println!("other range"), // 匹配其他值
}

可以绑定整个模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Point {
x: i32,
y: i32,
}

let point = Point { x: 10, y: 20 };

match point {
p @ Point { x: 0..=10, y } => {
println!("点在 x 轴上 0 到 10 的范围内: {:?}, y = {}", p, y)
},
p @ Point { x, y: 0..=10 } => {
println!("点在 y 轴上 0 到 10 的范围内: {:?}, x = {}", p, x)
},
Point { x, y } => println!("点在其他位置: ({}, {})", x, y),
}

解构

解构元组

1
2
3
4
5
6
7
8
9
10
let point = (1, 2);
let (x, y) = point; // 解构元组
println!("x: {}, y: {}", x, y); // 输出 x: 1, y: 2

match point {
(0, 0) => println!("Origin"),
(x, 0) => println!("On the x-axis at {}", x),
(0, y) => println!("On the y-axis at {}", y),
(x, y) => println!("Point at ({}, {})", x, y),
}

解构结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Point {
x: i32,
y: i32,
}

let point = Point { x: 1, y: 2 };
let Point { x: i, y: j } = point; // 解构结构体
let Point { x, y } = point; // 当变量与字段名相同时的简化写法

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

解构枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Action {
Move { x: i32, y: i32 },
Speak(String),
Stop,
}

let action = Action::Move { x: 1, y: 2 };

match action {
Action::Move { x, y } => println!("Move to ({}, {})", x, y),
Action::Speak(message) => println!("Speak: {}", message),
Action::Stop => println!("Stop"),
}

嵌套解构

解构的模式类似于初始化时的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Point {
x: i32,
y: i32,
}

struct Circle {
center: Point,
radius: i32,
}

let circle = Circle {
center: Point { x: 1, y: 2 },
radius: 5,
};

let Circle { center: Point { x, y }, radius } = circle; // 嵌套解构
println!("Circle center: ({}, {}), radius: {}", x, y, radius); // 输出 Circle center: (1, 2), radius: 5

忽略模式

在模式匹配中,可以忽略某些值

使用 _ 忽略整个值

下划线 _ 是一个通配符,可以匹配任何值但不绑定到变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn main() {
let x = 5;

// 忽略函数返回值
let _ = calculate_value();

// 在match中忽略某些情况
match x {
1 => println!("One"),
2 => println!("Two"),
_ => println!("Other"), // 匹配所有其他值但不绑定
}
}

fn calculate_value() -> i32 {
// 一些计算...
42
}

使用下划线忽略部分值

可以在模式中使用 _ 忽略元组、结构体或者模式的一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 忽略元组中的某些元素
let tuple = (1, 2, 3, 4);
let (first, _, third, _) = tuple;
println!("first: {}, third: {}", first, third);

// 忽略结构体中的某些字段
struct Point {
x: i32,
y: i32,
z: i32,
}

let point = Point { x: 1, y: 2, z: 3 };
let Point { x, y: _, z } = point;
println!("x: {}, z: {}", x, z);

// 在函数参数中忽略
fn foo(_: i32, y: i32) {
println!("y: {}", y);
}
foo(3, 4); // 忽略第一个参数

使用 .. 忽略剩余值

双点号 .. 可以用来忽略模式中剩余的部分:

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
// 忽略元组中间的值
let tuple = (1, 2, 3, 4, 5);
let (first, .., last) = tuple;
println!("first: {}, last: {}", first, last);

// 忽略结构体中的大部分字段
struct Point3D {
x: i32,
y: i32,
z: i32,
label: String,
visible: bool,
}

let point = Point3D {
x: 10,
y: 20,
z: 30,
label: String::from("point1"),
visible: true,
};

// 只关心x字段
let Point3D { x, ..} = point;
println!("x: {}", x);

// 在match中结合..使用
match point {
Point3D { x: 0, .. } => println!("On the YZ plane"),
Point3D { x: 10, y, .. } => println!("x is 10, y is {}", y),
Point3D { x, y, z, .. } => println!("Located at ({}, {}, {})", x, y, z),
}

.. 必须是无歧义的。例如,(first, .., second, ..) 会导致编译错误,因为无法确定哪些值应该匹配第一个 .. 和哪些值应该匹配第二个 ..

使用前缀下划线忽略未使用变量

如果你需要绑定一个变量但不打算使用它,可以给变量名加上下划线前缀来避免未使用变量的警告:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 没有下划线前缀,编译器会警告 unused_variables
fn process_data(data: &str) {
let (name, age) = get_user_info(data);
println!("Name: {}", name);
// age 未使用,编译器会警告
}

// 使用下划线前缀避免警告
fn process_data_better(data: &str) {
let (name, _age) = get_user_info(data);
println!("Name: {}", name);
// _age 未使用,但编译器不会警告
}

fn get_user_info(data: &str) -> (String, u32) {
// 解析数据...
(String::from("Alice"), 30)
}

下划线前缀 _age 与单个下划线 _ 的区别在于,_age 仍然会绑定值,而 _ 根本不会绑定。

函数和闭包

使用 fn 关键字定义函数

1
2
3
4
5
6
7
8
9
10
11
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
// 函数体
if condition {
return value; // 用return显式或提前返回值
}
expression // 函数最后的表达式自动作为返回值
}

fn do_nothing() { // 没有返回值的函数,返回类型为 ()

}

函数可以作为参数,其类型是 fn

1
2
3
4
5
6
7
8
9
fn function(){
println!("call function");
}

fn call(f:fn()) {
f()
}

call(function);

闭包 (Closure)

闭包类似其他语言中的 lambda 表达式。它们可以捕获周围的环境。闭包的语法如下:

1
2
3
4
5
6
let closure_name = |param1: Type1, param2: Type2| -> ReturnType {
// 闭包体,对于单个表达式可以省略大括号
};

let add = |x: i32, y: i32| x + y; // 闭包,返回两个数的和
println!("{}", add(1, 2)); // 调用闭包,返回3

闭包可以捕获上下文中的变量,闭包有三种捕获方式:

  • 通过引用: &T
  • 通过可变引用: &mut T
  • 通过值: T
1
2
3
4
5
6
7
8
9
10
11
let name = String::from("Alice");

// 通过引用捕获 name
let greet = || {
println!("Hello, {}!", name);
};

greet(); // 打印: Hello, Alice!

// name 仍然可用,因为闭包只借用了它
println!("Name is still available: {}", name);
1
2
3
4
5
6
7
8
9
10
11
12
13
let mut counter = 0;

// 通过可变引用捕获 counter
let mut increment = || {
counter += 1;
println!("Counter: {}", counter);
};

increment(); // 打印: Counter: 1
increment(); // 打印: Counter: 2

// counter 仍然可用且已被修改
println!("Final counter: {}", counter); // 打印: Final counter: 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let name = String::from("Bob");

// 通过值捕获 name
let greet = || {
println!("Hello, {}!", name);
take_ownership(name); // 将所有权转移到函数
}

greet(); // 打印: Hello, Bob!

// 错误!name 已被闭包消耗
// println!("Can't use name anymore: {}", name);

fn take_ownership(s: String) {
println!("Goodbye {}!", s);
}
1
2
3
4
5
6
7
8
9
10
11
let name = String::from("Charlie");

// 可以使用 move 关键字强制通过值捕获
let greeting = move || {
println!("Goodbye, {}!", name);
};

// 错误!name 的所有权已转移到闭包
// println!("Can't use name anymore: {}", name);

greeting(); // 打印: Goodbye, Bob!

上面三种捕获方式直接影响闭包的 trait 实现:

  • Fn: 通过引用捕获,闭包可以多次调用,但在最后一次调用前不能可变借用被捕获的变量
  • FnMut: 通过可变引用捕获,闭包可以多次调用,但在最后一次调用前不能再次借用被捕获的变量
  • FnOnce: 通过值捕获,闭包只能调用一次,因为它消耗了捕获的变量

三种 trait 的关系:FnOnce > FnMut > Fn

只要闭包中有变量是通过值捕获的,就实现 FnOnce;如果没有值捕获,但有可变引用捕获,则实现了 FnMut;如果只有引用捕获,则实现了 Fn

闭包可以作为函数的参数或返回值。在返回闭包时必须使用 move 关键字来捕获变量的所有权,否则在函数退出后产生无效引用。

1
2
3
4
5
6
7
8
fn apply<F>(f: F) where F: FnOnce() {
f(); // 调用闭包
}

fn create_closure() -> impl FnOnce() {
let name = String::from("Dave");
move || println!("Hello, {}!", name) // 返回闭包
}

高阶函数

高阶函数是指接受一个或多个函数作为参数,并 / 或返回另一个函数的函数。在迭代器中经常使用高阶函数。

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

let sum_of_squares_of_odds: i32 = numbers.iter()
.filter(|&&x| x % 2 != 0)
.map(|&x| x * x)
.sum();

println!("{}", sum_of_squares_of_odds); // 输出 35

发散函数

发散函数不会返回,使用 ! 标记返回类型。

1
2
3
fn no_return() -> ! {
panic!("This function never returns!");
}

! 可以被转换为任何类型,这在有些时候很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn sum_odd_numbers(up_to: u32) -> u32 {
let mut acc = 0;
for i in 0..up_to {
// 注意这个 match 表达式的返回值必须为 u32,
// 因为 addition 变量是这个类型。
let addition: u32 = match i%2 == 1 {
// “i” 变量的类型为 u32,这毫无问题。
true => i,
// continue 表达式不返回任何值,所以是 ! 类型
// 不会有类型的问题
false => continue,
};
acc += addition;
}
acc
}
println!("Sum of odd numbers up to 9 (excluding): {}", sum_odd_numbers(9));

包、crate 和模块

crate 是 Rust 中的最小编译单元。crate 有两种类型:二进制项和库。二进制项是可执行的程序,库是可以被其他 crate 使用的代码库。

包 (package) 是一个或多个 crate 的集合。包可以包含任意多个二进制项和至多一个库。cargo new 命令就会创建一个包。

main.rs 是与包同名的二进制项,lib.rs 是与包同名的库。其他的二进制项可以在 src/bin 目录下创建。

模块

crate 中可以包含多个模块。模块类似于其他语言中的命名空间,用于组织代码。

模块可以内联在使用它的文件中,也可以在单独的文件中定义。

对于在 main.rs 中声明的 my_module 的模块,编译器会在以下位置查找模块代码:

  • src/my_module.rs
  • src/my_module/mod.rs

可以在模块中声明子模块,比如在 my_module.rs 中声明子模块 sub_module,编译器会在以下位置查找子模块代码:

  • src/my_module/sub_module.rs
  • src/my_module/sub_module/mod.rs
1
2
3
4
5
6
7
8
9
10
// src/main.rs

mod inline_module { // 内联模块
pub fn inline_function() {
println!("This is an inline module function");
}
}

// 编译器会在 src/my_module.rs 或 src/my_module/mod.rs 中查找模块代码
mod my_module;
1
2
3
4
5
6
7
8
// src/my_module.rs
pub fn my_function() {
println!("This is a function in my_module");
}

fn my_private_function() {
println!("This is a private function in my_module");
}

模块路径

crate 代表当前 crate 的根模块,可以用绝对路径和相对路径两种方法来访问模块。

绝对路径从 crate 开始,使用:: 分隔模块名。如 crate::my_module::my_function()

相对路径从当前模块开始,使用 super:: 访问父模块,使用 self:: 访问当前模块,self:: 可以省略。

如在 main.rs 中可以使用 my_module::my_function() 来访问 my_module 中的函数。

1
2
3
4
5
6
// src/main.rs

fn main() {
inline_module::inline_function(); // 调用内联模块中的函数
my_module::my_function(); // 调用 my_module 中的函数
}

可见性

模块中的代码默认是私有的,只能在同一模块或子模块中可以访问,可以使用 pub 将代码声明为公共的。

1
2
3
4
5
6
7
8
9
10
11
// src/my_module.rs

// my_function 是公共的,可以在其他模块中访问
pub fn my_function() {
println!("This is a function in my_module");
}

// my_private_function 是私有的,只能在 my_module 中访问
fn my_private_function() {
println!("This is a private function in my_module");
}

使用 pub mod 将一个模块声明为公共的,让除父模块以外的其他地方也能访问该模块。

1
2
// src/my_module.rs
mod sub_module; // 声明子模块
1
2
3
4
// src/my_module/sub_module.rs
pub fn sub_function() {
println!("This is a function in sub_module");
}
1
2
3
4
5
6
7
// src/main.rs

mod my_module; // 声明 my_module 模块

fn main() {
// my_module::sub_module::sub_function(); // 无法访问
}

以上示例中,除非让 sub_module 模块为公共的,否则无法在 main.rs 中访问 sub_function 函数。即便该函数是公共的。

还可以让名称仅在特定路径内可见,如:

  • pub(crate),表示该名称在当前 crate 内可见;
  • pub(super) 表示该名称在父模块内可见;
  • pub(self) 表示该名称在当前模块内可见;
  • pub(in path) 表示该名称在指定路径内可见。

use 关键字

可以使用 use 关键字简化模块路径的书写。

在引用函数时路径一般写到函数所在的模块为止,这样可以避免来自不同模块的同名函数冲突。

在引用结构体、枚举等时,一般写出完整路径。

可以使用 as 关键字为引用的模块或函数起别名,但起别名后不能使用原名。

当使用 use 将某个名称导入当前作用域时,该名称就能在当前作用域中使用了,但在此作用域之外仍是私有的。可以使用 pub use 将名称重导出,就好像该名称是在当前作用域中定义的一样。

引入同一模块下的不同名称是可以使用嵌套路径避免太多行。如 use std::io::{self, Write};

还可以用 * 来引入模块中的所有公共项。如 use std::collections::*;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/main.rs
use my_module::sub_module;
use my_module::MyStruct; // 引用结构体
use my_module::MyEnum as OurEnum; // 引用枚举并起别名

// use my_module::{sub_module, MyStruct, MyEnum as OurEnum}; // 以上导入可以简化为这一行

mod my_module;

fn main() {
sub_module::sub_function(); // 调用子模块中的函数

let my_struct = MyStruct::new(); // 创建结构体实例
let my_enum = OurEnum::Variant1; // 创建枚举实例
// let my_enum2 = MyEnum::Variant2; // 错误!MyEnum 已被重命名为 OurEnum

my_module::SubStruct::new(); // 使用重导出的结构体,就好像SubStruct是在my_module.rs中定义的一样
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/my_module.rs
pub mod sub_module; // 声明子模块

pub use sub_module::SubStruct; // 重导出子模块中的结构体

pub struct MyStruct {
pub field1: i32,
pub field2: String,
}

pub enum MyEnum {
Variant1,
Variant2,
}
1
2
3
4
5
6
7
8
9
// src/my_module/sub_module.rs
pub fn sub_function() {
println!("This is a function in sub_module");
}

pub struct SubStruct {
pub field1: i32,
pub field2: String,
}

结构体的可见性

结构体的字段默认是私有的,只有在同一模块或子模块中可以访问。可以使用 pub 将结构体的字段声明为公共的。

错误处理

panic

在遇到不可恢复的错误时,Rust 会 panic。

当发生 panic 时,程序默认会 unwinding (回溯栈并清理资源) 然后退出。另一种选择是 abort ,不清理资源直接退出,由操作系统来清理。

要使用 abort 模式,可以在 Cargo.toml 中添加以下配置:

1
2
3
4
5
[profile.debug]
panic = "abort"

[profile.release]
panic = "abort"

abort 模式下生成的二进制文件要更小。

可以使用 panic! 宏来显式触发 panic,并附加错误信息。

1
2
3
fn main() {
panic!("This is a panic message");
}

另一个类似的宏是 unimplemented!,可以用于占位符,表示该函数尚未实现。实质上是语义更明确的一种 panic!

1
2
3
fn some_function() {
unimplemented!("This function is not implemented yet");
}

Option

Option 是一个枚举类型,用于表示一个值可能存在或不存在。它有两个变体:

  • Some(T):表示存在一个值
  • None:表示不存在值

在使用 Option 时,可以使用模式匹配或用 unwrap 方法来处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn main() {
let some_value: Option<i32> = Some(5);
let none_value: Option<i32> = None;

match some_value {
Some(v) => println!("Value is: {}", v),
None => println!("No value"),
}

// 使用 unwrap 方法
let value = some_value.unwrap(); // 如果是 None,会 panic
println!("Unwrapped value: {}", value);

// 使用 unwrap_or 方法
let default_value = none_value.unwrap_or(0); // 如果是 None,返回默认值
println!("Default value: {}", default_value);
}

可以用 ? 运算符解开 Option,相比于模式匹配更简洁。x 是一个 Option 变量,当 xSome 时,x? 会返回 x 的值;当 xNone 时,函数会终止并返回 None

1
2
3
4
fn add_one(x: Option<i32>) -> Option<i32> {
let value = x?; // 如果 x 是 None,函数会直接返回 None
Some(value + 1)
}

一些常用的方法:

  • is_someis_none:检查是否有值,返回布尔值
  • unwrap:提取值,如果没有值则 panic
  • unwrap_or:提取值,如果没有值则返回提供的默认值
  • unwrap_or_else:提取值,如果没有值则调用提供的闭包
  • map: 将值从一种类型转换为另一种类型,如果没有值则返回 None

SomeNone 可分别看作是 truefalse,因此有以下方法:

  • and:如果 selfNone,返回 None,否则返回另一个 Option
  • or:如果 selfSome,返回 self,否则返回另一个 Option
  • and_then:如果 selfSome,则调用闭包并返回其结果,否则返回 None (处理有值的情况)
  • or_else:如果 selfNone,则调用闭包并返回其结果,否则返回 self (处理无值的情况)

Result

Result 是一个枚举类型,用于表示一个操作的结果。它有两个变体:

  • Ok(T):表示操作成功,并包含一个值
  • Err(E):表示操作失败,并包含一个错误值

Result 有许多和 Option 类似的方法,如 unwrapexpectmapand_then 等。

1
2
3
4
5
fn add(x: &str, y: &str) -> i32 {
let first = x.parse::<i32>().unwrap_or(0);
let second = y.parse::<i32>().unwrap_or(0);
first + second
}

同样可以用 ? 运算符将 Result 解开或向上传播错误。x 是一个 Result 变量,当 xOk 时,x? 会返回 x 的值;当 xErr 时,函数会返回该错误。

泛型

语法

使用泛型时,在名称后面加上尖括号 <>,并在尖括号中指定类型参数。注意在实现方法时 impl 块后也要加上泛型参数。

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
// 泛型函数
fn generic_function<T>(arg: T) -> T {
arg
}

// 泛型结构体
struct GenericStruct<T> {
field: T,
}

// 泛型枚举
enum GenericEnum<T> {
Variant(T),
}

// 泛型方法
impl<T> GenericStruct<T> {
fn new(value: T) -> Self {
GenericStruct { field: value }
}

fn get_field(&self) -> &T {
&self.field
}
}

// 为特定类型实现方法
impl GenericStruct<i32> {
fn add(&self, other: &GenericStruct<i32>) -> i32 {
self.field + other.field
}
}

约束

可以在泛型参数后面添加约束,限制泛型参数的类型。约束使用 where 关键字或在尖括号中指定。

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
// 泛型函数,约束 T 必须实现 Display trait
fn print_value<T: std::fmt::Display>(value: T) {
println!("{}", value);
}

// 或者使用 where 关键字
fn print_value_where<T>(value: T) where T: std::fmt::Display {
println!("{}", value);
}

// 多重约束
fn print_value_multiple<T: Debug + Display>(value: T) {
println!("{:?}", value);
}

// 多重参数的 where 写法
fn print_value_multiple_where<T>(value: T)
where
T: Debug + Display,
{
println!("{:?}", value);
}

// 一些约束只能通过 where 来实现
fn print_option<T>(value: Option<T>)
where
Option<T>: Debug,
{
println!("{:?}", value);
}

trait

trait 类似于其他语言中的接口,定义了一组方法的签名。实现 trait 的类型必须实现这些方法。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
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
trait Shape {
fn area(&self) -> f64;
fn perimeter(&self) -> f64;

fn description(&self) -> String { // 默认实现
format!("Area: {}, Perimeter: {}", self.area(), self.perimeter())
}
}

struct Circle {
radius: f64,
}

struct Rectangle {
width: f64,
height: f64,
}

// 为类型实现 trait
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}

fn perimeter(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
}

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

fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
}

// trait可以作为函数参数
fn print_shape_info<T: Shape>(shape: &T) {
println!("{}", shape.description());
}

// 另一种写法
fn print_shape_info2(shape: &impl Shape) {
println!("{}", shape.description());
}

// trait 作为返回值
fn create_rectangle(width: f64, height: f64) -> impl Shape {
Rectangle { width, height }
}

// 当可能返回不同类型时,不能使用impl Trait作为返回值。需要使用Box<dyn Trait>
fn create_shape(shape_type: &str) -> Box<dyn Shape> {
match shape_type {
"circle" => Circle { radius: 1.0 },
"rectangle" => Rectangle { width: 1.0, height: 2.0 },
_ => panic!("Unknown shape type"),
}
}

关联类型

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
// 泛型trait
trait GenericTrait<A, B> {}

// 泛型参数列表冗长
fn func1<A, B, T>(t: T) where
T: GenericTrait<A, B>
{
// ...
}

// 同一类型可以有多种泛型trait实现
impl GenericTrait<i32, i32> for MyStruct {
// ...
}

impl GenericTrait<i32, f64> for MyStruct {
// ...
}

// 关联类型
trait AssociatedTrait {
type A;
type B;
}

// 泛型参数列表简化
fn func2<T: AssociatedTrait>(t: T) {

}

// 一个类型只能有一种trait实现
impl AssociatedTrait for MyStruct {
type A = i32;
type B = i32;
}

泛型 trait 适用于一个 trait 对不同类型有不同实现的情况,例如类型转换的 From<T> trait

关联类型 trait 适用于一个类型只能有一种 trait 实现的情况,例如 Iterator trait

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

可以为 trait 定义默认的泛型参数,这样在实现 trait 时可以省略类型参数。

一个例子是运算符相关的 trait,可以为类型实现这些 trait 来重载运算符。

Add trait 的泛型参数默认是 Self,表示与实现该 trait 的类型相同。

1
2
3
trait Add<Rhs = Self> {
fn add(self, rhs: Rhs) -> Self;
}
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
use std::ops::Add;

struct Meter(i32);
struct Kilometer(i32);

// 省略了泛型参数
impl Add for Meter {
type Output = Meter;

fn add(self, rhs: Meter) -> Meter {
Meter(self.0 + rhs.0)
}
}

// 指定泛型参数
impl Add<Kilometer> for Meter {
type Output = Meter;

fn add(self, rhs: Kilometer) -> Meter {
Meter(self.0 + rhs.0 * 1000)
}
}

fn main() {
let m1 = Meter(100);
let m2 = Meter(200);
let km1 = Kilometer(1);

let total_meters = m1 + m2 + km1;
println!("Total meters: {}", total_meters.0); // 输出 1300
}

不同 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
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
trait Business {
fn run(&self);
fn exit();
}

trait Program {
fn run(&self);
fn exit();
}

struct Robot;

impl Robot {
fn run(&self) {
println!("Moving quickly");
}

fn exit() {
println!("Moving out");
}
}

impl Business for Robot {
fn run(&self) {
println!("Managing business");
}

fn exit() {
println!("Quit business");
}
}

impl Program for Robot {
fn run(&self) {
println!("Starting program");
}

fn exit() {
println!("Stop program");
}
}

fn main() {
let robot = Robot;

robot.run(); // 调用 Robot 的 run 方法
Business::run(&robot); // 调用 Business trait 的 run 方法
Program::run(&robot); // 调用 Program trait 的 run 方法

Robot::exit(); // 调用 Robot 的 exit 关联函数
<Robot as Business>::exit(); // 调用 Business trait 的 exit 关联函数
<Robot as Program>::exit(); // 调用 Program trait 的 exit 关联函数
}

超 trait

一个 trait 可以依赖于另一个 trait(超 trait)。这允许在实现 trait 时使用超 trait 的方法,类似于继承。

1
2
3
trait Human {}
// Student trait 依赖于 Human trait
trait Student: Human {}

所有权和生命周期

所有权规则

Rust 的所有权系统遵循三个基本规则:

  1. 每个值都有一个所有者(变量)
  2. 值在任意时刻只能有一个所有者
  3. 当所有者离开作用域时,值会被丢弃
1
2
3
4
5
6
{
let s1 = String::from("hello"); // s1是字符串的所有者
let s2 = s1; // s2获取s1的所有权,s1不再有效
// println!("{}", s1); // 错误!s1已经失效
println!("{}", s2);
} // s2超出作用域,内存被释放,s2不再有效

所有权转移

当涉及堆数据时,赋值操作会导致所有权转移:

1
2
3
4
let s1 = String::from("hello"); // s1 -----> 堆上的"hello"
let s2 = s1; // s1 X s2 -----> 堆上的"hello"

// s1不再有效,这避免了"二次释放"错误

实现了 Copy trait 的类型不会发生所有权转移,而是进行复制。以下类型默认实现了 Copy trait:

  • 所有整数类型,如 u32
  • 布尔类型,bool
  • 浮点数类型,如 f64
  • 字符类型,char
  • 仅包含以上类型的元组,如 (i32, i32)
1
2
3
let x = 5;
let y = x; // x的值被复制给y
println!("x = {}, y = {}", x, y); // 都有效

克隆(Clone)

如果需要深度复制堆数据而不是转移所有权,可以使用 clone 方法

1
2
3
let s1 = String::from("hello");
let s2 = s1.clone(); // 堆数据的深拷贝
println!("s1 = {}, s2 = {}", s1, s2); // 都有效

函数与所有权

函数参数和返回值也遵循所有权规则

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
fn main() {
let s1 = String::from("hello");
take_ownership(s1); // s1的所有权被转移
// println!("{}", s1); // 错误!s1已无效

let s2 = gives_ownership(); // 接收函数返回值的所有权
println!("{}", s2);

let s3 = String::from("world");
let s4 = takes_and_gives_back(s3); // s3所有权转移到函数,函数又返回新的所有权
// println!("{}", s3); // 错误!s3已无效
println!("{}", s4);
}

fn take_ownership(some_string: String) {
println!("{}", some_string);
} // some_string超出作用域被丢弃

fn gives_ownership() -> String {
let some_string = String::from("yours");
some_string // 返回并转移所有权
}

fn takes_and_gives_back(a_string: String) -> String {
a_string // 返回并转移所有权
}

引用和借用

为了避免每次传递值都转移所有权,Rust 提供了 "引用" 的概念,允许使用值但不获取其所有权。

引用基础

创建引用的行为称为 "借用"

1
2
3
4
5
6
7
8
9
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 传递 s1 的引用
println!("'{}' 的长度是 {}.", s1, len); // s1 仍然有效
}

fn calculate_length(s: &String) -> usize { // s 是指向 String 的引用
s.len()
} // s 离开作用域,但它不拥有所指向的值,所以什么也不会发生

引用的示意

1
2
3
4
s1 -----> String("hello")
^
|
s

可变引用

引用默认是不可变的。如果想修改借用的值,需要使用可变引用

1
2
3
4
5
6
7
8
9
fn main() {
let mut s = String::from("hello");
change(&mut s); // 传递可变引用
println!("{}", s); // 输出 "hello, world"
}

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

引用规则

引用有两条核心规则:

  1. 在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用
  2. 引用必须始终有效(不能指向已释放的内存)

这些规则在编译时防止数据竞争(data race)

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

// 例1:不能同时拥有可变和不可变引用
let r1 = &s; // 不可变引用
let r2 = &s; // 不可变引用
// let r3 = &mut s; // 错误!不能同时有可变和不可变引用
println!("{} and {}", r1, r2);

// r1和r2从此处不再使用

let r3 = &mut s; // 现在可以使用可变引用
println!("{}", r3);

// 例2:不能有多个可变引用
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // 错误!不能有多个可变引用

悬垂引用

Rust 编译器确保引用永远不会成为悬垂引用(指向已释放的内存)

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

fn dangle() -> &String { // 错误!返回了悬垂引用
let s = String::from("hello");
&s // 返回了s的引用,但s在函数结束时会被释放
} // s超出作用域被释放

解决方法是直接返回 String

1
2
3
4
fn no_dangle() -> String {
let s = String::from("hello");
s // 返回s并转移所有权
}

解引用与自动解引用

使用 * 操作符可以访问引用指向的值

1
2
3
4
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y); // 使用*解引用

Rust 在许多情况下会自动解引用,特别是

  1. 方法调用:object_ref.method()
  2. 字段访问:object_ref.field
  3. 比较操作:ref1 == ref2
  4. 与模式匹配结合使用:match *ref_val { ... }
1
2
3
4
5
6
7
8
9
10
struct Point {
x: i32,
y: i32,
}

let point = Point { x: 1, y: 2 };
let point_ref = &point;

// 自动解引用,不需要使用(*point_ref).x
println!("坐标: ({}, {})", point_ref.x, point_ref.y);

切片(Slice)

切片是对集合中部分连续元素的引用,不拥有所有权

1
2
3
4
let s = String::from("hello world");
let hello = &s[0..5]; // 等价于&s[..5]
let world = &s[6..11]; // 等价于&s[6..]
let whole = &s[..]; // 等价于&s[0..s.len()]

字符串字面量是字符串切片

1
let s: &str = "Hello, world!"; // s是&str类型,指向二进制程序中的位置

函数参数应优先使用切片类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 更灵活的接口,可接受String和&str类型
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}

// 使用示例
let my_string = String::from("hello world");
let word = first_word(&my_string[..]); // 以切片形式传递String

let my_literal = "hello world";
let word = first_word(my_literal); // 直接传递字符串字面量

数组切片

1
2
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // 类型是&[i32]

生命周期

生命周期是 Rust 另一个独特的概念,用于确保引用在使用时始终有效。

生命周期的概念

生命周期是引用有效的范围。大多数情况下,生命周期是隐含的,但有时需要显式标注

1
2
3
4
5
6
7
8
9
// 错误示例:引用超出生命周期
fn main() {
let r;
{
let x = 5;
r = &x; // x将在内部作用域结束时被释放
} // x超出作用域
println!("r: {}", r); // 错误!r引用了已释放的内存
}

生命周期标注语法

生命周期标注以撇号(')开头,通常使用小写字母如 'a

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

函数中的生命周期标注

当函数有多个引用参数并返回引用时,编译器可能无法推断生命周期之间的关系

1
2
3
4
5
6
7
8
// 没有生命周期标注,无法编译
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}

使用生命周期标注,明确参数和返回值的关系

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

这表示 x 和 y 至少与返回的引用存活一样长。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";

let result = longest(string1.as_str(), string2);
println!("最长的字符串是: {}", result);

// 错误示例:返回值的生命周期受限于最短的参数生命周期
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
// result的生命周期受限于string2
} // string2超出作用域
// println!("最长的字符串是: {}", result); // 错误!result已无效
}

生命周期省略规则

Rust 有三条生命周期省略规则,让代码更简洁:

  1. 每个引用参数都有自己的生命周期参数
  2. 如果只有一个输入生命周期参数,它会被赋给所有输出生命周期参数
  3. 如果有多个输入生命周期参数,但其中一个是 &self&mut self,则 self 的生命周期被赋给所有输出生命周期参数

例如,以下两个函数签名是等价的

1
2
fn first_word(s: &str) -> &str { /* ... */ }
fn first_word<'a>(s: &'a str) -> &'a str { /* ... */ }

结构体中的生命周期

当结构体持有引用时,需要标注生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct ImportantExcerpt<'a> {
part: &'a str,
}

fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("找不到句号");

let excerpt = ImportantExcerpt {
part: first_sentence,
};

println!("{}", excerpt.part);
} // novel和excerpt都在这里超出作用域

静态生命周期

'static 生命周期表示引用在整个程序运行期间都有效。所有字符串字面量都有 'static 生命周期

1
let s: &'static str = "我有静态生命周期";

生命周期约束与泛型

生命周期可以与泛型和 trait 约束结合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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!("公告:{}", ann);
if x.len() > y.len() {
x
} else {
y
}
}

测试

迭代器

迭代器的核心是 Iterator trait,它定义了迭代器的基本行为。

1
2
3
4
trait Iterator {
type Item; // 迭代器返回的元素类型
fn next(&mut self) -> Option<Self::Item>; // 返回下一个元素
}

迭代器有三种类型: * Iter:对集合的不可变引用进行迭代。在集合上调用 iter 方法返回 * IterMut:对集合的可变引用进行迭代,可以修改集合中的元素。在集合上调用 iter_mut 方法返回 * IntoIter:拥有集合的迭代器,会消耗集合中的元素。在集合上调用 into_iter 方法返回

实现迭代器只需指定 Item 关联类型,并实现 next 方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct MyList {
data: Vec<i32>,
index: usize,
}

impl Iterator for MyList {
type Item = i32;

fn next(&mut self) -> Option<Self::Item> {
if self.index < self.data.len() {
let item = self.data[self.index];
self.index += 1;
Some(item)
} else {
None
}
}
}

迭代器的强大之处在于其提供了许多有用的方法,这些方法可以链式调用,从而实现强大的功能。

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
let numbers = vec![1, 2, 3, 4, 5];

// next() 方法返回迭代器中的下一个元素
let mut iter = numbers.iter();
assert_eq!(iter.next(), Some(&1)); // 返回第一个元素
assert_eq!(iter.next(), Some(&2)); // 返回第二个元素

// last() 方法返回迭代器中的最后一个元素
assert_eq!(numbers.iter().last(), Some(&5));

// nth() 方法返回迭代器中的第 n 个元素
assert_eq!(numbers.iter().nth(2), Some(&3));

// take() 方法返回一个新的迭代器,包含前 n 个元素
let first_two: Vec<_> = numbers.iter().take(2).collect();
assert_eq!(first_two, vec![&1, &2]);

// count() 方法返回迭代器中的元素数量
assert_eq!(numbers.iter().count(), 5);

// map() 方法将迭代器中的每个元素应用一个函数,并返回一个新的迭代器
let doubled: Vec<_> = numbers.iter().map(|&x| x * 2).collect();
assert_eq!(doubled, vec![2, 4, 6, 8, 10]);

// filter() 方法返回一个新的迭代器,包含满足条件的元素
let even: Vec<_> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
assert_eq!(even, vec![&2, &4]);

// fold() 方法将迭代器中的元素应用一个函数,并返回一个累积结果
let factorial: i32 = numbers.iter().fold(1, |acc, &x| acc * x);
assert_eq!(factorial, 120); // 1 * 2 * 3 * 4 * 5 = 120

// sum() 方法返回迭代器中所有元素的和
let sum: i32 = numbers.iter().sum();
assert_eq!(sum, 15);

// chain() 方法将多个迭代器连接在一起
let more_numbers = vec![6, 7, 8];
let combined: Vec<_> = numbers.iter().chain(more_numbers.iter()).collect();
assert_eq!(combined, vec![&1, &2, &3, &4, &5, &6, &7, &8]);

// enumerate() 方法返回一个新的迭代器,包含元素的索引和值
let indexed: Vec<_> = numbers.iter().enumerate().collect();
assert_eq!(indexed, vec![(0, &1), (1, &2), (2, &3), (3, &4), (4, &5)]);

// for_each() 方法对迭代器中的每个元素执行一个闭包
numbers.iter().for_each(|&x| println!("{}", x)); // 打印每个元素

// rev() 方法返回一个新的迭代器,反向迭代
let reversed: Vec<_> = numbers.iter().rev().collect();
assert_eq!(reversed, vec![&5, &4, &3, &2, &1]);

// all() 方法检查迭代器中的所有元素是否满足条件
let all_positive = numbers.iter().all(|&x| x > 0);
assert!(all_positive); // 所有元素都是正数

// any() 方法检查迭代器中是否有任何元素满足条件
let any_greater_than_three = numbers.iter().any(|&x| x > 3);
assert!(any_greater_than_three); // 有元素大于3

collect() 将迭代器转换为集合类型,具体类型取决于上下文。可以用 collect::<type>() 指定类型。

1
2
3
4
5
6
7
8
9
10
11
12
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect(); // 转换为 Vec<i32>

// collect() 可以生成字符串
let words = vec![String::from("hello"), String::from(" "), String::from("world")];
let string: String = chars.iter().map(|s| s.to_uppercase()).collect(); // 转换为 String
let char_list: Vec<String> = chars.iter().map(|s| s.to_uppercase()).collect(); // 转换为 Vec<String>

// 用于`Result`类型的集合时,可以产生两种不同的结果,取决于上下文
let results = [Ok(1), Ok(2), Ok(3)];
let result_with_list: Result<Vec<i32>, String> = results.iter().cloned().collect(); // 返回 Result<Vec<i32>, String>
let list_of_results: Vec<Result<i32, String>> = results.iter().cloned().collect(); // 返回 Vec<Result<i32, String>>

Rust 学习笔记
http://blog.qzink.me/posts/Rust学习笔记/
作者
Qzink
发布于
2025年4月22日
许可协议