Post

一文看懂rust

一文看懂rust

Rust 核心概念

所有权 Ownership

Rust 通过所有权系统来管理内存,确保内存安全且无需垃圾回收器。所有权机制的三个主要规则是:

  1. 每个值都有一个所有者(变量)。
  2. 每个值同时只能有一个所有者。
  3. 当所有者超出作用域时,值将被释放。

两种主要的所有权转移方式:

  • 移动(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 项目按照大小和复杂度可以分为不同类型:

  1. Package:包含一个或多个 Crate 的集合,通常包含一个Cargo.toml文件。
  2. Crate:Rust 编译单元,可以是二进制项目或库项目。
    1. 二进制项目(Binary Crate):生成可执行文件,包含一个main.rs文件。
    2. 库项目(Library Projects):生成库文件,包含一个lib.rs文件。
  3. 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。
This post is licensed under CC BY 4.0 by the author.