Rust 核心概念
所有权 Ownership
Rust 通过所有权系统来管理内存,确保内存安全且无需垃圾回收器。所有权机制的三个主要规则是:
- 每个值都有一个所有者(变量)。
- 每个值同时只能有一个所有者。
- 当所有者超出作用域时,值将被释放。
两种主要的所有权转移方式:
- 移动(Move):当变量被赋值给另一个变量时,所有权会被转移,原变量将不再有效。
- 借用(Borrow):Rust 允许通过引用(Reference)来借用数据,而不需要转移所有权,从而提高代码的灵活性和安全性。
引用分为两种类型:
- 不可变引用(&T):允许读取数据,但不能修改。
- 可变引用(&mut T):允许修改数据,但在同一时间只能有一个可变引用。
1
2
3
4
5
6
7
8
9
| fn borrow_example() {
let s = String::from("hi");
takes_ref(&s); // borrow
takes_own(s); // move
// s cannot be used after moved
}
fn takes_ref(s: &String) { println!("{}", s); }
fn takes_own(s: String) { println!("{}", s); }
|
statements 和 expressions
Rust 中的语句(statements)执行某些操作但不返回值,而表达式(expressions)计算并返回一个值。大多数代码块都是表达式。
1
2
| let x = 5; // 语句,结尾有分号,没有返回值
x + 1 // 表达式,结尾无分号,有返回值(6)
|
模式匹配 Pattern Matching
Rust 提供了强大的模式匹配功能,可以方便地解构数据结构。
match 语句需要覆盖所有可能的情况,可以使用以下两种方式来处理未匹配的情况:
other:可以匹配所有未被前面模式匹配到的情况,并绑定到变量 other 上。_:可以匹配所有未被前面模式匹配到的情况,但不会绑定到任何变量上。
1
2
3
4
5
6
| match some_value {
Pattern1 => { /* handle Pattern1 */ },
Pattern2 => { /* handle Pattern2 */ },
_ => { /* handle other cases */ },
}
|
错误处理 Error Handling
Rust 使用 Result 和 Option 类型来处理错误和缺失值,避免了传统的异常机制。
生命周期 Lifetime
生命周期用于描述引用的有效范围,帮助编译器确保引用在使用时是有效的。
1
2
3
| fn first<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
|
Cargo 项目管理工具
Cargo 本身是一个 Rust Package,它管理 Rust 项目的构建、依赖和发布。Cargo 使用Cargo.toml文件来定义项目的元数据和依赖关系。
1
2
3
4
5
6
7
8
9
10
11
| cargo new myproj # create new project
cargo build # build
cargo run # build & run
cargo test # run tests
cargo fmt # format
cargo clippy # lint
cargo doc --open # generate docs
cargo install <pkg> # install package
cargo add <dep> # add dependency
cargo update # update dependencies
|
Rust 项目按照大小和复杂度可以分为不同类型:
- Package:包含一个或多个 Crate 的集合,通常包含一个
Cargo.toml文件。 - Crate:Rust 编译单元,可以是二进制项目或库项目。
- 二进制项目(Binary Crate):生成可执行文件,包含一个
main.rs文件。 - 库项目(Library Projects):生成库文件,包含一个
lib.rs文件。
- Module:代码组织单元,用于将代码划分为不同的命名空间。
Rust 语法速查
变量赋值
1
2
3
4
5
6
7
| let n = 5; // 将数值5赋值给变量n,变量n的类型是由编译器自动推导出的。
let n : i32 = 5; // 将数值5赋值给变量n,变量n的类型是i32。
let n; // 创建一个名为n的占位变量,开发者可以在后面为其赋值(但只能赋值1次)。
let mut n = 5; // 将数值5赋值给变量n,变量n是可变的,可以在后续修改它的值。
let n = i == 5; // 将表达式i==5执行后的结果(true或false)赋值给变量n,变量n的类型是由编译器自动推导出的。
x = y; // 如果变量y是一个不可被复制的类型,则将变量y的值移动到变量x中(此后变量y就不能再被使用了)。
// 如果变量y实现了[derive(Copy)],则会制作一份备份赋值给x,变量y还可以继续使用。
|
结构体
1
2
3
4
5
| struct S {x:i32} // 创建一个结构体,使之包含一个名为x,类型为i32的字段,可以通过s.x的形式来访问这个字段。
struct S (i32); // 创建一个元组结构体,使之包含一个i32类型的字段,可以通过s.0的形式来访问这个字段。
struct S; // 创建一个单元结构体,它将在编译时被优化算法从程序中移除。
let s = S { x: 3 }; // 创建一个结构体实例,并将字段x的值设为3。
|
枚举体
1
2
| enum E { A, B } // 定义一个枚举体类型,它有A和B两个可选的值。
enum E { A(i32), B } // 定义一个枚举体,它有A和B两个可选的值,其中A选项可以额外包含一个i32类型的数据。
|
控制流
1
2
3
4
5
6
7
8
| if x {...} else {...} // 如果x是true,则运行第一个代码块;否则,运行第二个代码块。
while x {...} // 当表达式x的求值结果为true时,重复运行括号中的代码。
loop { break; } // 重复运行括号中的代码,直到break;被调用。
for i in 0..4 {...} // 在x变量取值为0、1、2、3的情况下分别运行一次括号中的代码。循环范围并不包含最后一个数字。
for i in 0..=4 {...} // 在x变量取值为0、1、2、3、4的情况下分别运行一次括号中的代码。循环范围包含最后一个数字。
for i in vec.iter() {...} // 对于迭代器中的每一个元素,执行一次括号中的代码。
vec.iter.for_each(|n|...) // 和上面代码效果一致,为迭代器中的每一个元素执行一次闭包。
|
函数
1
2
3
4
5
6
| fn my_func() {...} // 声明函数my_func,它没有参数列表和返回值类型。
fn my_func(i: i32) {...} // 声明函数my_func,它有一个i32类型的参数i。
fn n2(n: i32) -> i32 {n*2} // 声明函数n2,它接收一个i32类型的参数,并返回n*2。
|| {...} // 创建一个没有参数的闭包。
|| 3 // 创建一个没有参数的闭包,它会返回数字3。
|a| a*3 // 创建一个闭包,它接收一个名为a的参数,并返回a*3。
|
成员函数、关联函数
1
2
3
4
5
6
7
| impl MyStruct { // 为结构体MyStruct定义成员函数和关联函数。
fn assoc() {...} // 关联函数,通过MyStruct::assoc()的方式来调用它。
fn member(&self) {...} // 成员函数,通过my_instance.member()的方式来调用。
fn mem_mut(&mutself) {...} // 可变成员函数,通过my_instance.member_mut()的方式来调用。这个函数可以修改结构体实例的值。
}
impl Trait for MyStruct {...} // 为MyStruct定义Trait中要求的成员函数。
|
Option 类型变量
1
2
3
4
5
| option.unwrap() // 拆开一个Option类型变量,如果是空的,则触发Panic或使程序崩溃。
option.expect("Fail") // 拆开一个Option类型变量,如果是空的,则打印指定消息后让程序崩溃。
option.unwrap_or(3) // 拆开一个Option类型变量,如果是空的,则使用3作为它的默认值。
if let Some(x) = maybe_val { println!("{}", x); } // 使用if let语句提取Option类型变量maybe_val内部的值,如果其中有值,则括号中的代码可以通过变量x来访问其中存储的值。
|
Result 类型变量
1
2
3
4
5
6
| result.unwrap() // 拆开一个Result类型变量,如果有错误,则触发Panic或使程序崩溃。
result.expect("Fail") // 拆开一个Result类型变量,如果有错误,则打印指定消息后让程序崩溃。
result.unwrap_or(3) // 拆开一个Result类型变量,如果有错误,则使用3作为它的默认值。
if let Ok(result) = maybe_res {...} // 使用if let语句提取Result类型变量maybe_res内部的值。
function_that_might_fail()? // 对于返回Result类型的函数来说,可以使用?来作为unwrap的简便写法。
|
元组与解构
1
2
3
| let i = (1, 2); // 将1和2两个数字分别作为元组i的第0个和第1个成员。
let (i, j) = (1, 2); // 将元组(1, 2)解构到变量i和j中。
i.0 // 访问元组的第一个成员。
|
模块与导入
1
2
3
4
| mod m; // 引用模块m,它会查找m.rs或者m/mod.rs这两个文件。
mod m {...} // 以内联的形式定义一个模块,可以通过m::x的形式来访问其内部的成员。
use m::*; // 将模块m中的所有成员导入当前作用域中,从而可以直接使用它们。
use m::a; // 将模块m中的成员a导入当前的作用域中,这样就可以直接使用a,而不是m::a了。
|
迭代器链式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| iter // 代表一个迭代器,它可以是在集合类型上调用iter()得到,也可以是调用其他返回迭代器的函数而得到。
.for_each(|n| ... ) // 为迭代器中的每一个成员调用一次闭包。
.collect::<T>() // 将迭代器中的所有成员收集到一个新的类型为T的集合中。
.count() // 获得迭代器中成员的数量。
.filter(|n| ... ) // 过滤迭代器中的元素,只保留下那些能使闭包返回true的元素。
.filter_map(|n| ... ) // 过滤迭代器中的元素,返回第一个能使闭包返回Some(x)的结果,如果要忽略掉某个元素,让闭包返回None即可。
.find(|n| ... ) // 在迭代器中寻找指定成员,如果没有找到,则返回None。
.fold(|acc, x| ... ) // 将迭代器中的所有成员叠加到acc变量上。
.map(|n| ...) // 将迭代器中的每一个元素都替换为对应闭包返回的值。
.max() // 在迭代器中找到最大的一个值(仅对数值类型有效)。
.max_by(|n| ... ) // 在迭代器中找到最大的一个值,比较大小的规则由闭包指定。
.min() // 在迭代器中找到最小的一个值(仅对数值类型有效)。
.min_by(|n| ... ) // 在迭代器中找到最小的一个值,比较大小的规则由闭包指定。
.nth(n) // 返回迭代器中位于第n个位置的元素。
.product() // 将迭代器中的所有元素相乘(仅对数值类型有效)。
.rev() // 翻转迭代器的顺序。
.skip(n) // 跳过迭代器中接下来的n个元素。
.sum() // 将迭代器中所有的元素进行累加(仅对数值类型有效)。
.zip(other_it) // 与另一个迭代器做合并,合并后的结果按照A、B、A、B的模式交织在一起。
|
常用集合类型
1
2
3
4
| let v = vec![1, 2, 3]; // 创建一个包含3个整数的动态数组。
let s = String::from("hello"); // 创建一个字符串。
let hs: HashSet<i32> = HashSet::new(); // 创建一个空的HashSet集合。
let hm: HashMap<String, i32> = HashMap::new(); // 创建一个空的HashMap集合,键类型为String,值类型为i32。
|
常用宏
1
2
3
4
5
6
7
8
9
| println!("Hello, {}", name); // 打印格式化字符串到控制台。
format!("Hello, {}", name); // 返回一个格式化后的字符串。
todo!(); // 占位宏,表示这里的代码尚未实现,调用时会触发Panic。
unreachable!(); // 占位宏,表示这里的代码不应该被执行,调用时会触发Panic。
dbg!(&var); // 打印变量var的调试信息,包括文件名和行号。
assert!(x > 0); // 断言表达式x>0为真,否则触发Panic。
assert_eq!(a, b); // 断言变量a和b相等,否则触发Panic。
|