0%

<2025春夏季开源操作系统训练营第一阶段总结报告-1430329301Lsj>

Rust速查表:Rust Language Cheat Sheet

Rust是什么

  • Rust可看作一个在语法层面(编译时)具有严格检查和限制的C语言上位

  • 扩展了面向对象的便捷方法绑定。

  • 编译和运行方式类似于C/C++,可以rustc xxx.rs编译,./xxx运行。

  • 有约定的项目目录格式,可使用Cargo配置toml进行包管理、编译、运行、测试等等。

  • 包资源网站为CratesIO,见src↑

  • 不支持运算符重载,支持多态。其中语句为:表达式+;,语句的值是()

    运算符重载是指为自定义的类或结构体重新定义或赋予运算符(如+、-、*、/等)新的功能。它允许程序员对已有运算符赋予多重含义,使同一运算符作用于不同类型的数据时执行不同的操作。

    特点:

    1. 扩展运算符功能:让运算符不仅能作用于基本数据类型,也能作用于自定义类型
    2. 语法简洁:使自定义类型的操作像基本类型一样自然
    3. 保持直观性:重载后的运算符功能应与原意相符,避免滥用

Rust安装

可使用编译器:VSCode + rust-analyzer,VIM,RustRover

image-20250403110554181

1
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

我最终选择使用rustRover来编译——jetBrain全家桶之一,与Pycham同源使用会比较顺手,且可以直接连接WSL:

image-20250403110943092

Rust基础

rust的编译与运行是分开的,是一种预编译静态类型语言。rust的源文件为xx.rs,最基础的编译为使用rustc xx.rs对代码进行编译。

基础语法

Cargo

Cargo是Rust的构建系统和包管理器:

  • 构建代码
  • 下载依赖库(代码所需的库叫做依赖)

1.使用Cargo构建项目

1
2
$ cargo new the_project
$ cd the_project

在Linux终端输入上面的代码创建rust项目后,会生成如下的文件与目录:

image-20250410180737109

变量

首先必须说明,Rust 是强类型语言,但具有自动判断变量类型的能力。这很容易让人与弱类型语言产生混淆。

默认情况下,Rust 中的变量是不可变的,除非使用 mut 关键字声明为可变变量。

1
2
let a = 123;       // 不可变变量
let mut b = 10; // 可变变量

如果要声明变量,需要使用 let 关键字。例如:

1
let a = 123;

变量和常量还是有区别的。在 Rust 中,以下程序是合法的:

1
2
let a = 123;   // 可以编译,但可能有警告,因为该变量没有被使用
let a = 456;

但是如果 a 是常量就不合法:

1
2
const a: i32 = 123;
let a = 456;

控制流

if 表达式

实例:

1
2
3
4
5
6
let number = 7;
if number < 5 {
println!("小于 5");
} else {
println!("大于等于 5");
}

loop 循环: loop 是 Rust 中的无限循环,可以使用 break 退出循环。

实例

1
2
3
4
5
6
7
let mut counter = 0;
loop {
counter += 1;
if counter == 10 {
break;
}
}

while 循环

1
2
3
4
5
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}

for 循环

1
2
3
for number in 1..4 {
println!("{}!", number);
}

结构体 (Structs)

结构体用于创建自定义类型,字段可以包含多种数据类型。

实例

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

let user1 = User {
username: String::from("someusername"),
email: String::from("someone@example.com"),
sign_in_count: 1,
active: true,
};

枚举 (Enums)

枚举允许定义可能的几种数据类型中的一种。

1
2
3
4
5
6
7
enum IpAddrKind {
V4,
V6,
}

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

模式匹配 (match)

match 是 Rust 中强大的控制流工具,类似于 switch 语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}

错误处理

Rust 有两种主要的错误处理方式:Result<T, E>Option

Result:

1
2
3
4
5
6
7
8
9
10
11
12
enum Result<T, E> {
Ok(T),
Err(E),
}

fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}

Option:

1
2
3
4
5
6
7
fn get_element(index: usize, vec: &Vec<i32>) -> Option<i32> {
if index < vec.len() {
Some(vec[index])
} else {
None
}
}

所有权与借用的生命周期

Rust 使用生命周期来确保引用的有效性。生命周期标注用 ‘a 等来表示,但常见的情况下,编译器会自动推导。

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

重影(Shadowing)

重影的概念与其他面向对象语言里的”重写”(Override)或”重载”(Overload)是不一样的。重影就是刚才讲述的所谓”重新绑定”,之所以加引号就是为了在没有介绍这个概念的时候代替一下概念。

重影就是指变量的名称可以被重新使用的机制:

1
2
3
4
5
6
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x);
}

数据类型

整数型(Integer)

整数型简称整型,按照比特位长度和有无符号分为以下种类:

位长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

isize 和 usize 两种整数类型是用来衡量数据大小的,它们的位长度取决于所运行的目标平台,如果是 32 位架构的处理器将使用 32 位位长度整型。

浮点数型(Floating-Point)

Rust 与其它语言一样支持 32 位浮点数(f32)和 64 位浮点数(f64)。默认情况下,64.0 将表示 64 位浮点数,因为现代计算机处理器对两种浮点数计算的速度几乎相同,但 64 位浮点数精度更高。

1
2
3
4
fn main() {
let x = 2.0; *// f64*
let y: f32 = 3.0; *// f32*
}

布尔型

布尔型用 bool 表示,值只能为 true 或 false。

字符型

字符型用 char 表示。

Rust的 char 类型大小为 4 个字节,代表 Unicode标量值,这意味着它可以支持中文,日文和韩文字符等非英文字符甚至表情符号和零宽度空格在 Rust 中都是有效的 char 值。

注释

1
2
3
4
5
6
7
8
9
// 这是第一种注释方式

/* 这是第二种注释方式 */

/*
* 多行注释
* 多行注释
* 多行注释
*/

函数

1
fn <函数名> ( <参数> ) <函数体>

函数参数

Rust 中定义函数如果需要具备参数必须声明参数名称和类型:

1
2
3
4
5
6
7
8
fn main() {
another_function(5, 6);
}

fn another_function(x: i32, y: i32) {
println!("x 的值为 : {}", x);
println!("y 的值为 : {}", y);
}

函数返回值

在函数体中,随时都可以以 return 关键字结束函数运行并返回一个类型合适的值。这也是最接近大多数开发者经验的做法:

1
2
3
fn add(a: i32, b: i32) -> i32 {
return a + b;
}

但是 Rust 不支持自动返回值类型判断!如果没有明确声明函数返回值的类型,函数将被认为是”纯过程”,不允许产生返回值,return 后面不能有返回值表达式。这样做的目的是为了让公开的函数能够形成可见的公报。

Rust特色

所有权

Rust 中的所有权是独特的内存管理机制,核心概念包括所有权 (ownership)、借用 (borrowing) 和引用 (reference)。

所有权规则:

  • Rust 中的每个值都有一个所有者。
  • 每个值在任意时刻只能有一个所有者。
  • 当所有者超出作用域时,值会被删除。
1
2
3
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权被转移给了 s2
// println!("{}", s1); // 此处编译会报错,因为 s1 已不再拥有该值

借用和引用: 借用允许引用数据而不获取所有权,通过 & 符号实现。

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

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

组织管理

Rust 中有三个重要的组织概念:箱、包、模块。

箱(Crate)

“箱”是二进制程序文件或者库文件,存在于”包”中。

“箱”是树状结构的,它的树根是编译器开始运行时编译的源文件所编译的程序。

注意:”二进制程序文件”不一定是”二进制可执行文件”,只能确定是是包含目标机器语言的文件,文件格式随编译环境的不同而不同。

包(Package)

当我们使用 Cargo 执行 new 命令创建 Rust 工程时,工程目录下会建立一个 Cargo.toml 文件。工程的实质就是一个包,包必须由一个 Cargo.toml 文件来管理,该文件描述了包的基本信息以及依赖项。

一个包最多包含一个库”箱”,可以包含任意数量的二进制”箱”,但是至少包含一个”箱”(不管是库还是二进制”箱”)。

当使用 cargo new 命令创建完包之后,src 目录下会生成一个 main.rs 源文件,Cargo 默认这个文件为二进制箱的根,编译之后的二进制箱将与包名相同。

模块(Module)

对于一个软件工程来说,我们往往按照所使用的编程语言的组织规范来进行组织,组织模块的主要结构往往是树。Java 组织功能模块的主要单位是类,而 JavaScript 组织模块的主要方式是 function。

这些先进的语言的组织单位可以层层包含,就像文件系统的目录结构一样。Rust 中的组织单位是模块(Module)。

Rust 宏(Macros)是一种在编译时生成代码的强大工具,它允许你在编写代码时创建自定义语法扩展。

宏(Macro)是一种在代码中进行元编程(Metaprogramming)的技术,它允许在编译时生成代码,宏可以帮助简化代码,提高代码的可读性和可维护性,同时允许开发者在编译时执行一些代码生成的操作。

宏在 Rust 中有两种类型:声明式宏(Declarative Macros)和过程宏(Procedural Macros)。

本文主要介绍声明式宏。

宏的定义

在 Rust 中,使用 macro_rules! 关键字来定义声明式宏。

1
2
3
4
5
6
7
macro_rules! my_macro {
// 模式匹配和展开
($arg:expr) => {
// 生成的代码
// 使用 $arg 来代替匹配到的表达式
};
}

声明式宏使用 macro_rules! 关键字进行定义,它们被称为 “macro_rules” 宏。这种宏的定义是基于模式匹配的,可以匹配代码的结构并根据匹配的模式生成相应的代码。这样的宏在不引入新的语法结构的情况下,可以用来简化一些通用的代码模式。

注意:

  • 模式匹配:宏通过模式匹配来匹配传递给宏的代码片段,模式是宏规则的左侧部分,用于捕获不同的代码结构。
  • 规则:宏规则是一组由 $ 引导的模式和相应的展开代码,规则由分号分隔。
  • 宏的展开:当宏被调用时,匹配的模式将被替换为相应的展开代码,展开代码是宏规则的右侧部分。
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
*// 宏的定义*
macro_rules! vec {
*// 基本情况,空的情况*
() => {
Vec::new()
};

*// 递归情况,带有元素的情况*
($($element:expr),+ $(,)?) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($element);
)+
temp_vec
}
};
}

fn main() {
*// 调用宏*
let my_vec = vec![1, 2, 3];
println!("{:?}", my_vec); *// 输出: [1, 2, 3]*

let empty_vec = vec![];
println!("{:?}", empty_vec); *// 输出: []*
}

在这个例子中,vec! 宏使用了模式匹配,以及 $($element:expr),+ $(,)?) 这样的语法来捕获传递给宏的元素,并用它们创建一个 Vec。

注意,$(,)?) 用于处理末尾的逗号,使得在不同的使用情境下都能正常工作。

过程宏(Procedural Macros)

过程宏是一种更为灵活和强大的宏,允许在编译时通过自定义代码生成过程来操作抽象语法树(AST)。过程宏在功能上更接近于函数,但是它们在编写和使用上更加复杂。

过程宏的类型:

  • 派生宏(Derive Macros):用于自动实现trait(比如CopyDebug)的宏。
  • 属性宏(Attribute Macros):用于在声明上附加额外的元数据,如#[derive(Debug)]

过程宏的实现通常需要使用 proc_macro 库提供的功能,例如 TokenStream 和 TokenTree,以便更直接地操纵源代码。

智能指针

智能指针(Smart pointers)是一种在 Rust 中常见的数据结构,它们提供了额外的功能和安全性保证,以帮助管理内存和数据。

在 Rust 中,智能指针是一种封装了对动态分配内存的所有权和生命周期管理的数据类型。

智能指针通常封装了一个原始指针,并提供了一些额外的功能,比如引用计数、所有权转移、生命周期管理等。

在 Rust 中,标准库提供了几种常见的智能指针类型,例如 Box、Rc、Arc 和 RefCell。

智能指针的使用场景:

  • 当需要在堆上分配内存时,使用 Box<T>
  • 当需要多处共享所有权时,使用 Rc<T>Arc<T>
  • 当需要内部可变性时,使用 RefCell<T>
  • 当需要线程安全的共享所有权时,使用 Arc<T>
  • 当需要互斥访问数据时,使用 Mutex<T>
  • 当需要读取-写入访问数据时,使用 RwLock<T>
  • 当需要解决循环引用问题时,使用 Weak<T>

错误处理

Rust 有一套独特的处理异常情况的机制,它并不像其它语言中的 try 机制那样简单。

首先,程序中一般会出现两种错误:可恢复错误和不可恢复错误。

可恢复错误的典型案例是文件访问错误,如果访问一个文件失败,有可能是因为它正在被占用,是正常的,我们可以通过等待来解决。

但还有一种错误是由编程中无法解决的逻辑错误导致的,例如访问数组末尾以外的位置。

大多数编程语言不区分这两种错误,并用 Exception (异常)类来表示错误。在 Rust 中没有 Exception。

对于可恢复错误用 Result<T, E> 类来处理,对于不可恢复错误使用 panic! 宏来处理。