Rust 初了解

前端工具链的发展,一个明显的趋势------越来越多的高性能前端工具正在用 Rust 重写。Vite 8 现在已经用 Rolldown 作为默认打包器;Biome、Oxc 等工具也在用 Rust 构建,提供更快的 Lint 和格式化体验。

Rust 是一门系统编程语言,由 Mozilla 于 2010 年发布,2015 年发布 1.0 稳定版。它的核心目标有三个:

  • 内存安全:不需要垃圾回收(GC)就能保证内存安全
  • 高性能:性能媲美 C/C++
  • 并发安全:编译时就能防止数据竞争

变量

一、命名规则

  • 使用蛇形命名 (snake_case):my_variable_name
  • 常量使用全大写加下划线:MAX_SIZE
  • 类型名使用驼峰命名:MyStruct

二、变量绑定的本质?

在 Rust 中,你并不是"声明一个变量",而是将一个值绑定到一个标识符 上。这个过程叫变量绑定(variable binding)。

rs 复制代码
fn main() {
    let a = 10;
    println!("a = {}", a)
}

三、变量在使用前必须被初始化

rs 复制代码
fn main() {
    let a:i32;
    println!("a = {}", a);
}
rs 复制代码
fn main() {
    let a:i32;
    a = 10;
    println!("a = {}", a); // 正常打印
}

四、变量的不可变性

Rust 变量最显著的特点:默认是不可变的(immutable) 。这意味着一旦绑定了一个值,就不能改变它。

rs 复制代码
fn main() {
    let a = 10; // 将10绑定到a变量上
    a = 11; // 不能直接修改a的值,因为a是不可变的
    println!("a = {}", a)
}

如果你想修改变量的值,需要显式地使用 mut 关键字。

rs 复制代码
fn main() {
    let mut a = 10;
    println!("a = {}", a);
    a = 11; // 可以修改a的值,因为a是可变的
    println!("a = {}", a);
}

五、变量遮蔽

变量遮蔽是指在代码中,你可以重新绑定一个变量到一个新的值,而不会影响到之前绑定的值。

重新声明同名变量,可改变类型,创建新绑定。

rs 复制代码
fn main() {
    let a = 10; // 将10绑定到a变量上
    println!("a = {}", a); // a = 10

    let a = 20; // 将20绑定到a变量上
    println!("a = {}", a); // a = 20
}
js 复制代码
fn main() {
    let a = 10; // 将10绑定到a变量上
    println!("a = {}", a); // a = 10

    let a = "cds"; // 将20绑定到a变量上
    println!("a = {}", a); // a = 20
}

六、变量的作用域

Rust 的变量作用域是块作用域 (由 {} 界定)。一个绑定从它声明的位置开始,到当前块结束为止有效。

rs 复制代码
fn main() {
    {
        let a = 10;
        println!("a = {}", a);
    } // a 在这里被销毁

    println!("a = {}", a);
}

七、所有权

Rust 最独特的点:变量绑定拥有它绑定的值。所有权规则:

  1. 每个值在 Rust 中有一个所有者(owner)。
  2. 同一时刻只能有一个所有者。
  3. 当所有者离开作用域,值会被丢弃。
rs 复制代码
fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 的所有权被移动到 s2
    // 此后 s1 不再有效
    println!("{}", s2);
    println!("{}", s1);
}

数据类型

Rust 是一门静态类型 语言,意味着所有变量的类型必须在编译时已知。不过得益于类型推断,通常不需要显式标注类型,编译器会根据上下文自动推导。

Rust 的数据类型可分为两大类:标量类型 (Scalar Types)和复合类型(Compound Types),此外还有自定义类型、引用、切片等高级类型。

标量类型

标量类型代表单个值。Rust 有四种基本的标量类型:整数、浮点数、布尔值和字符。

一、整数类型(Integer)

整数是没有小数部分的数字。Rust 提供了多种长度的有符号和无符号整数。

i32 是默认整数类型,通常性能最好。
isizeusize 的长度取决于运行程序的计算机架构(64位系统上为64位,32位系统上为32位),主要用于集合索引和内存偏移。

长度 有符号 无符号
8位 i8 u8
16位 i16 u16
32位 i32 u32
64位 i64 u64
128位 i128 u128
架构相关 isize usize

二、浮点数类型(Floating-Point)

Rust 有两种浮点数:f32(单精度)和 f64(双精度)。f64 是默认类型 ,因为现代 CPU 上它的速度与 f32 相近,但精度更高。

rs 复制代码
fn main() {
    let s:f32 = 3.1415926;
    println!("s = {}", s);
}

控制输出格式

  • 科学计数法 :使用 {:e}{:E}
  • 固定宽度 :使用 {:width.precision} 控制宽度和精度。
  • 对齐 :在宽度前加 -
rs 复制代码
fn main() {
    let s: f32 = 3.1415926;
    println!("s = {:.4}", s); // 保留.4位小数
    println!("s = {:e}", s); // 科学计数法
    println!("s = {:-10.2}", s); // 保留.2位小数
}

三、布尔类型(Boolean)

布尔类型有两个值:truefalse,占用一个字节。

rs 复制代码
fn main() {
    let a: bool = true;
    let b: bool = false;
    println!("a && b = {}", a && b); // 输出:false
    println!("a || b = {}", a || b); // 输出:true
    println!("!a = {}", !a); // 输出:false
}

四、字符类型(Character)

Rust 的 char 类型代表一个 Unicode 标量值,占用 4 个字节。它可以表示中文、日文、韩文、表情符号等。

注意:char单引号 ,字符串用双引号

rs 复制代码
fn main() {
    let c1: char = 'A'; // 英文字母
    let c2: char = '中'; // 中文字符
    let c3: char = '😀'; // 表情符号(Emoji)
    let c4: char = '1'; // 数字字符
    let c5: char = '!'; // 标点符号

    println!("c1 = {} c2 = {} c3 = {} c4 = {} c5 = {}", c1, c2, c3, c4, c5);
}

复合类型

复合类型可以将多个值组合成一个类型。Rust 有两个基本的复合类型:元组和数组。

一、元组(Tuple)

元组可以将多个不同类型的值组合成一个固定大小的序列。创建元组使用圆括号。

  1. 默认情况下,元组是 不可变 的。
  2. 使用 mut 关键字可声明 可变元组,但只能修改元素的值,不能改变长度或类型。
  3. 单元素元组需要在元素后添加逗号,否则会被视为普通括号表达式。
  4. 空元组 () 是一个特殊的元组,表示"无值"或"空"。
rs 复制代码
fn main() {
    // 定义一个包含整数、浮点数和字符串的元组
    let tup: (i32, f64, &str) = (10, 3.14, "hello");

    // 类型推断:Rust 会自动推断元组类型
    let another_tup: (bool, char, i32) = (true, 'A', 42);

    println!("tup 元素:{}、{}、 {}", tup.0, tup.1, tup.2);

    let (a, b, c) = another_tup; // 解构
    println!("another_tup 元素:a = {}, b = {}, c = {}", a, b, c);
}
rs 复制代码
fn main() {
    let mut tup = (10, 20);
    tup.0 = 100;
    println!("修改后:{:?}", tup);
}

单元素元组需要在元素后添加逗号,否则会被视为普通括号表达式。

rs 复制代码
let single_tuple = (5,); // 单元素元组(正确)
let not_tuple = (5);      // 只是整数 5(非元组)
println!("单元素元组:{:?}", single_tuple); // 输出:(5,)

空元组 () 是一个特殊的元组,表示"无值"或"空"。

rs 复制代码
fn main() {
    let result = greet();
    println!("函数返回值:{:?}", result); // 输出:()
}

fn greet() -> () {
    println!("Hello!");
}

二、 数组(Array)

数组中的每个元素类型必须相同,且长度固定。数组分配在上,而不是堆上。

  • 默认情况下,数组是不可变的(元素值不可修改)。
  • 使用 mut 关键字可声明可变数组,但长度仍固定,只能修改元素值。

【示例】 声明一个数组

rs 复制代码
fn main() {
    // 声明一个包含 5 个 i32 类型元素的数组
    let arr: [i32; 5] = [1, 2, 3, 4, 5];
    println!("arr = {:?}", arr);
}

【示例】 声明一个数组

rs 复制代码
fn main() {
    // 声明一个包含 10 个元素的数组,每个元素值为 0
    let arr: [i32; 10] = [0; 10];
    println!("arr = {:?}", arr);
}

【示例】 修改数组元素

rs 复制代码
fn main() {
    let mut arr: [i32; 3] = [1, 2, 3];
    arr[0] = 100;
    println!("修改后:{:?}", arr);
}

【示例】 遍历数组

rs 复制代码
fn main() {
    let arr = [1, 2, 3, 4, 5];
    println!("数组长度:{}", arr.len()); // 输出:5

    let arr2 = [10, 20, 30];
    for element in &arr2 {
        println!("for遍历元素:{}", element);
    }

    let arr3 = [10, 20, 30];
    for i in 0..arr3.len() {
        println!("索引遍历 {}: {}", i, arr3[i]);
    }
}

字符串类型

在 Rust 中,字符串类型主要分为两种:字符串切片(&str堆分配字符串(String

字符串切片

&str:字符串切片,通常以字面量形式出现,存储在可执行文件的只读内存中。

1、字符串切片的基本特性?

  1. 不可变性&str 本身是不可变的(指向的数据不可修改)。
  2. 内存布局 :存储在 上,包含两个字段:
    1. 指向字符串数据的指针(*const u8)。
    2. 字符串长度(usize)。
  3. 无所有权&str 是引用类型,不拥有数据的所有权,仅临时借用。

2、如何创建字符串切片?

【示例】从字符串字面量创建,字符串字面量默认是 &'static str 类型(静态生命周期),存储在程序的只读内存区域。

rs 复制代码
fn main() {
    let static_slice: &str = "Hello, Rust!"; // 类型为 &'static str
    println!("static_slice = {}", static_slice);
}

【示例】 从 String 转换,通过 as_str() 方法或直接引用 &sString 转换为 &str

js 复制代码
fn main() {
    let s: String = String::from("Hello");
    let slice1: &str = s.as_str(); // 方法 1
    let slice2: &str = &s; // 方法 2(更简洁)
    println!("slice1 = {}", slice1);
    println!("slice2 = {}", slice2);
}

堆分配字符串(String)

String 是一个可变的、堆分配的 UTF-8 字符串类型,长度可动态调整。

1、基本特性?

内存布局 :存储在上,包含三个字段(在栈上的结构体):

  • 指向堆内存的指针(*mut u8)。
  • 字符串长度(usize)。
  • 堆内存容量(usize,即已分配的空间大小)。

所有权String 拥有其数据的所有权,离开作用域时会自动释放堆内存。

2、创建字符串

【示例】空字符串 :使用 String::new()

rs 复制代码
fn main() {
    // 1、创建一个空的字符串变量
    let mut s: String = String::new();
    s.push_str("Hello, Rust!"); // 追加字符串
    println!("s = {}", s);
}

【示例】从字符串字面量创建 :使用 String::from()to_string() 方法。

js 复制代码
// 2、从字符串字面量创建
let s2: String = String::from("Hello, Rust!");
let s3: String = "Hello".to_string();
println!("s2 = {}", s2);        
println!("s3 = {}", s3);

【示例】&str 创建

rs 复制代码
// 3、从&str创建
let slice: &str = "Hello";
let s4: String = String::from(slice);
println!("s4 = {}", s4);

3、字符串操作

【示例】 追加字符串 :使用 push_str() 方法。

rs 复制代码
fn main() {
    let mut s = String::from("Hello");
    s.push_str(", Rust!"); // 结果:"Hello, Rust!"
}

【示例】 追加单个字符 :使用 push() 方法。

js 复制代码
let mut s1 = String::from("Hello");
s1.push('!'); // 结果:"Hello!"
println!("s1 = {}", s1);

【示例】使用 + 运算符+ 运算符右侧必须是 &str,且会消耗左侧的 String 所有权。

js 复制代码
let ss1 = String::from("Hello");
let ss2 = String::from(" Rust");
let ss3 = ss1 + &ss2; // ss1 被消耗,ss3 是新的 String

println!("ss3 = {}", ss3);

【示例】使用 format!:更灵活的拼接方式,不消耗原有字符串。

js 复制代码
let s_1 = String::from("Hello");
let s_2 = String::from("Rust");
let s_3 = format!("{} {}", s_1, s_2); // 结果:"Hello Rust"

println!("s_3 = {}", s_3);

引用

在 Rust 中,引用(Reference) 是一种无需获取所有权就能访问数据的方式,是 Rust 所有权系统的核心组成部分。引用允许我们临时借用数据的访问权,而不会转移所有权,从而避免了不必要的复制和内存开销。

引用是指向数据的指针,通过 & 符号创建,类型为 &T(不可变引用)或 &mut T(可变引用)。

  • 无所有权:引用不拥有数据的所有权,因此离开作用域时不会释放数据。
  • 借用:引用的过程称为"借用"(Borrowing)。
  • 生命周期:引用的生命周期受限于其指向的数据,确保引用始终有效。

借用规则(Borrowing Rules)

Rust 的借用规则确保了引用的安全性,避免了数据竞争:

  • 不可变引用规则
    • 可以同时存在多个不可变引用(共享访问)。
    • 不可变引用存在时,不能创建可变引用(避免数据竞争)。
  • 可变引用规则
    • 同一时间只能有一个可变引用(独占访问)。
    • 可变引用存在时,不能创建不可变引用(避免数据竞争)。
  • 引用作用域
    • 引用的作用域从创建开始,到最后一次使用结束。
    • 作用域结束后,引用会被自动释放,允许创建新的引用。

悬垂引用(Dangling References)

悬垂引用是指向已释放内存的引用,Rust 通过生命周期系统防止这种情况。

产生原因?当引用指向的数据在引用仍然有效时被释放,就会产生悬垂引用。

【示例】函数返回局部变量的引用

rs 复制代码
fn dangle() -> &String {
    let s = String::from("Hello"); // s 是局部变量,存储在栈上
    &s // 尝试返回 s 的引用
} // s 离开作用域,被自动释放(内存被回收)

fn main() {
    let r = dangle(); // r 成为悬垂引用,指向已释放的内存
    println!("{}", r); // 访问已释放的内存,可能导致未定义行为
}

4、正确的解决方案

【示例】返回所有权而非引用。如果需要从函数返回数据,直接转移所有权。

rs 复制代码
// 返回所有权而非引用
fn dangle() -> String {
    let s = String::from("Hello"); // s 是局部变量,存储在栈上
    s // 尝试返回 s 的所有权
}

fn main() {
    let r = dangle();
    println!("{}", r);
}

【示例】使用静态生命周期('static)。如果数据需要在整个程序运行期间存在,可以使用 'static 生命周期

rs 复制代码
// 使用静态生命周期('static)
fn dangle() -> &'static str {
    "hello"
}

fn main() {
    let r = dangle();
    println!("{}", r);
}

【示例】使用 Box<T> 或其他智能指针。对于需要在堆上分配且生命周期较长的数据,可以使用 Box<T> 等智能指针。

rs 复制代码
// 使用 Box<T>
fn dangle() -> Box<i32> {
    Box::new(42) // 返回 Box<i32>,所有权转移给调用者
}

fn main() {
    let r = dangle();
    println!("{}", r);
}

自定义类型

在 Rust 中,自定义类型主要通过以下几种方式实现:结构体(Struct)枚举(Enum)联合(Union)类型别名(Type Alias)

结构体(struct)

结构体是一种将多个相关值打包在一起的自定义数据类型。Rust 有三种结构体:具名字段结构体、元组结构体、单元结构体。

一、具名字段结构体

具名结构体 是结构体最常见的形态------每个字段都有名称类型

rs 复制代码
#[derive(Debug)]
struct User {
    #[allow(dead_code)]
    active: bool,
    #[allow(dead_code)]
    username: String,
    #[allow(dead_code)]
    email: String,
    #[allow(dead_code)]
    sign_in_count: u64,
}

fn main() {
    // 创建实例,必须为所有字段提供值
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    println!("{:?}", user1);
}

尝试使用 {:?} 格式化输出 User 类型的值(例如在 println!("{:?}", user)dbg! 宏中),但 User 类型没有实现 Debug trait。Rust 要求类型显式实现 Debug 才能以调试格式打印。

User 结构体的某些字段被定义了,但在代码中从未被读取 (即从未访问过它们的值)。这属于 dead_code lint(死代码警告),默认开启。

快速修复:自动派生 Debug。在 User 定义上方添加 #[derive(Debug)]。如果某些字段暂时不需要,添加 #[allow(dead_code)]

【示例】通过 impl 添加方法

rs 复制代码
fn main() {
    struct User {
        username: String,
        email: String,
    }

    impl User {
        fn get_name(&self) -> (&str, &str) {
            (&self.username, &self.email)
        }
    }

    let user = User {
        username: String::from("lili"),
        email: String::from("lili@example.com"),
    };

    println!("username = {}, email = {}", user.get_name().0, user.get_name().1);
}

二、元组结构体

【示例】字段没有名字,只有类型。

rs 复制代码
fn main() {
    #[allow(dead_code)]
    struct Point(i32, i32);
    #[derive(Debug)]
    #[allow(dead_code)]
    struct Color(u8, u8, u8);

    let origin = Point(0, 0);
    let red = Color(255, 0, 0);

    println!("x = {}", origin.0); // 通过索引访问
    println!("{:?}", red);
}

三、单元结构体 Unit-Like Struct

单元结构体在内存中不占用任何空间 (零大小类型,ZST - Zero Sized Type),但它是一个具体的类型,可以用来表达某种"标记"或"状态"。

rs 复制代码
fn main() {
    #[derive(Debug)]
    struct Once; // 定义一个单元结构体
    let x = Once; // 创建实例,不需要 `{}` 或 `()`
    let y = Once; // 可以有多个实例,但它们是完全相同的"空"值

    println!("{:?}", x);
    println!("{:?}", y);
}

枚举(enum)

枚举定义一个类型可能取值的集合,每个变体可以带有不同类型和数量的数据。枚举是 Rust 中实现"代数数据类型"的核心。

rs 复制代码
#[derive(Debug)]
enum Direction {
    Up,
    #[allow(dead_code)]
    Down,
    #[allow(dead_code)]
    Left,
    #[allow(dead_code)]
    Right,
}

#[derive(Debug)]
enum Message {
    #[allow(dead_code)]
    Quit,                    // 无数据
    #[allow(dead_code)]
    Move { x: i32, y: i32 }, // 匿名结构体(见下面)
    #[allow(dead_code)]
    Write(String),           // 元组变体
    #[allow(dead_code)]
    ChangeColor(u8, u8, u8), // 元组变体
}

#[derive(Debug)]
enum Shape {
    #[allow(dead_code)]
    Circle { radius: f64 },
    #[allow(dead_code)]
    Rectangle { width: f64, height: f64 },
}

fn main() {
    let dir = Direction::Up;
    let msg = Message::Write(String::from("hello"));
    let circle = Shape::Circle { radius: 10.0 };

    println!("{:?}", dir);
    println!("{:?}", msg);
    println!("{:?}", circle);
}

联合体(union)

联合体类似于 C 语言的 union,所有字段共享同一块内存。

Rust 中的联合体是 unsafe 的,需要放在 unsafe 块中访问。极少使用,主要用于 FFI(外部函数接口)或极度追求内存布局的场景。

通常建议用枚举代替联合体,除非明确需要节省内存并与 C 代码交互。

rs 复制代码
union MyUnion {
    i: i32,
    f: f32,
}

fn main() {
    let u = MyUnion { i: 42 };
    unsafe {
        println!("{}", u.i); // 42
        println!("{}", u.f); // 未定义行为?把 i32 位模式解释为 f32
    }
}

类型别名(type alias)

使用 type 关键字为现有类型创建新名字。类型别名不创建新类型,只是别名,因此可以互换使用。

注意:类型别名是 弱类型(只是别名),如果需要真正的类型区分,应使用元组结构体或 newtype 模式。

rs 复制代码
fn main() {
    type Kilometers = i32;

    let x: i32 = 5;
    let y: Kilometers = 5;
    println!("x + y = {}", x + y); // 合法,因为它们是同一类型
}

控制语句

控制语句是编程语言的基础,用于决定程序的执行路径。Rust 的控制流设计强调表达式优先 (几乎所有控制结构都是表达式)、安全性 (如 match 必须穷举所有可能)和简洁性

条件语句:ifelse

if 表达式根据条件执行分支。条件必须是 bool 类型,Rust 不会自动将整数或指针转换为布尔值。

rs 复制代码
fn main() {
    let x: i32 = 5;
    if x > 0 {
        println!("positive");
    } else if x < 0 {
        println!("negative");
    } else {
        println!("zero");
    }
}

重要特性if 是表达式,可以用于赋值。所有分支必须返回相同类型的值。 注意:if 作为表达式时,分支末尾不能有分号,否则会变成 () 类型。

rs 复制代码
fn main() {
   let x: i32 = 5;   
   let number: i32 = if x > 0 { 1 } else { -1 };
   println!("number = {}", number);
}

循环:loopwhilefor

【示例】loop 重复执行一个代码块,直到遇到 break。它是 Rust 中最基础的循环,常用于需要明确退出条件的场景。

rs 复制代码
fn main() {
    let mut count: i32 = 0;
    loop {
        count += 1;
        if count == 3 {
            break; // 退出循环
        }
    }
    println!("count = {}", count);
}

【示例】loop 也可以作为表达式,通过 break 返回值:

rs 复制代码
fn main() {
    let result: i32 = loop {
        let mut input: String = String::new();
        std::io::stdin().read_line(&mut input).unwrap();
        if let Ok(num) = input.trim().parse::<i32>() {
            break num; // 返回解析成功的数字
        }
    };
    println!("You entered: {}", result);
}

【示例】while 在条件为 true 时重复执行。

rs 复制代码
fn main() {
    let mut x: i32 = 3;
    while x > 0 {
        println!("{}", x);
        x -= 1;
    }
}

【示例】for 用于遍历迭代器,是 Rust 中最常用的循环,因为不容易出错(不会越界)。

注意for 会消耗迭代器,默认会获得元素的所有权。如果只想借用,可以使用 .iter()&collection

rs 复制代码
fn main() {
    // 遍历范围
    for i in 0..5 {
        // 0,1,2,3,4
        println!("{}", i);
    }

    // 包含上界
    for i in 0..=5 {
        // 0,1,2,3,4,5
        println!("{}", i);
    }

    // 遍历集合
    let arr = [10, 20, 30];
    for elem in arr.iter() {
        println!("{}", elem);
    }

    // 遍历集合(获取索引和值)
    for (idx, val) in arr.iter().enumerate() {
        println!("arr[{}] = {}", idx, val);
    }
}

breakcontinue 与循环标签

  • break:立即退出当前循环。
  • continue:跳过本次循环剩余部分,进入下一次迭代。

【示例】当有嵌套循环时,可以使用循环标签'label)来指定作用于哪个循环。

rs 复制代码
fn main() {
    'outer: for i in 0..3 {
        for j in 0..3 {
            if i == 1 && j == 1 {
                break 'outer; // 直接退出外层循环
            }
            println!("({}, {})", i, j);
        }
    }
    // 输出: (0,0) (0,1) (0,2) (1,0)
}

模式匹配 match

match 是 Rust 中最强大的控制流结构,它将一个值与一系列模式进行匹配,并根据匹配的模式执行代码。match 必须穷举所有可能的情况。

rs 复制代码
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn main() {
    let nickel_value = value_in_cents(Coin::Nickel);
    let dime_value = value_in_cents(Coin::Dime);
    let quarter_value = value_in_cents(Coin::Quarter);
    let penny_value = value_in_cents(Coin::Penny);

    println!("nickel_value = {}", nickel_value);
    println!("dime_value = {}", dime_value);
    println!("quarter_value = {}", quarter_value);
    println!("penny_value = {}", penny_value);
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1, // 单行表达式
        Coin::Nickel => {
            println!("Nickel");
            5 // 多行用花括号
        }
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

函数

基本概念

1、函数定义与命名

使用 fn 关键字定义一个函数。Rust 采用 蛇形命名(snake_case)风格。

函数可以在任意位置定义(包括在 main 函数之后),Rust 没有"必须先声明后使用"的限制。

rs 复制代码
// 无参数、无返回值
fn greet() {
    println!("Hello!");
}

2、函数参数

Rust 是静态类型语言,每个参数都必须注明类型。

多个参数用逗号分隔。与变量一样,参数默认是不可变 的。如果需要修改参数的值,可以在函数体内用 mut 绑定

rs 复制代码
// 带参数
fn main() {
    add(1, 2);
}
fn add(a: i32, b: i32) {
    println!("Sum: {}", a + b);
}

当参数为非引用类型时,调用函数会转移所有权(移动语义)

js 复制代码
fn take_ownership(s: String) {
    println!("{}", s);
} // s 离开作用域,被释放

fn main() {
    let s = String::from("Hello");
    take_ownership(s);
    // println!("{}", s); // 错误:s 已失去所有权
}

使用引用参数可避免所有权转移,提高性能:

  • 不可变引用&T):允许多个读取者。
  • 可变引用&mut T):允许单个修改者。
js 复制代码
// 不可变引用
fn print_length(s: &String) {
    println!("Length: {}", s.len());
}

// 可变引用
fn append_world(s: &mut String) {
    s.push_str(", World!");
}

fn main() {
    let s1 = String::from("Hello");
    print_length(&s1); // 传递不可变引用
    println!("s1: {}", s1); // s1 仍然可用

    let mut s2 = String::from("Hello");
    append_world(&mut s2); // 传递可变引用
    println!("s2: {}", s2); // 输出:Hello, World!
}

3、返回值

Rust 区分语句(statements)和表达式(expressions)。 语句执行操作但不返回值(如 let x = 5;,以分号结尾)。表达式计算出一个值(如 5x + y{ ... } 块)。

【示例】使用箭头 -> 声明返回类型。函数的最后一个表达式的值会被隐式返回 ,也可以使用 return 关键字提前返回。

rs 复制代码
fn main() {
    let sum = add(1, 2);
    println!("Sum: {}", sum);
}
// 隐式返回
fn add(a: i32, b: i32) -> i32 {
    a + b
}

【示例】返回多个值。Rust 不支持直接返回多个值,但可以使用元组

rs 复制代码
//  返回单元类型 ()
fn no_return() -> () {
    println!("No return value");
}

嵌套函数

Rust 允许在函数内部定义嵌套函数(也称为局部函数)。

js 复制代码
fn outer() {
    fn inner() {
        println!("Inner function");
    }
    inner(); // 调用嵌套函数
}

fn main() {
    outer(); // 输出:Inner function
}

函数可以递归调用自身,但需要显式指定返回类型。

rs 复制代码
fn factorial(n: u32) -> u32 {
    if n <= 1 {
        1
    } else {
        n * factorial(n - 1)
    }
}

fn main() {
    println!("5! = {}", factorial(5)); // 输出:120
}

函数指针

函数指针(function pointer)是 Rust 中一种指向函数定义的类型,允许你将函数作为参数传递、存储在变量中或作为返回值返回。它是 Rust 支持"函数是一等公民"的一种方式。

【示例】 函数指针作为参数

Rust 中,函数本身也是一等公民,可以作为参数传递或存储在变量中。函数类型用 fn 关键字表示(小写)。

rs 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn subtract(a: i32, b: i32) -> i32 {
    a - b
}

// 接受函数指针作为参数
// 函数指针的类型使用 fn 关键字(小写),后跟参数和返回值类型
fn calculate(operation: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
    operation(a, b)
}

fn main() {
    let sum = calculate(add, 10, 5);
    let diff = calculate(subtract, 10, 5);
    println!("Sum: {}, Diff: {}", sum, diff); // 输出:Sum: 15, Diff: 5
}

【示例】 函数指针作为返回值

js 复制代码
fn get_math_op(op: char) -> fn(i32, i32) -> i32 {
    match op {
        '+' => add,
        '-' => subtract,
        _ => panic!("unknown op"),
    }
}

fn add(x: i32, y: i32) -> i32 {
    x + y
}
fn subtract(x: i32, y: i32) -> i32 {
    x - y
}

fn main() {
    let f: fn(i32, i32) -> i32 = get_math_op('+');
    println!("{}", f(5, 3)); // 8
}

【示例】函数项类型,function item type

在 Rust 中,每个具体的函数都有一个独一无二的、零大小 的类型,称为函数项类型(function item type)。它不是函数指针,而是一个编译时确定的、无额外运行时空开销的类型。

当需要将函数作为值传递时,函数项类型会隐式强制转换 为函数指针类型 fn

rs 复制代码
fn main() {
    fn foo() {
        println!("foo");
    }

    let a = foo; // a 的类型是函数项类型(零大小)
    let b: fn() = foo; // b 是函数指针
    a();
    b(); 
}

闭包函数

闭包(Closure)是 Rust 中一种可以捕获周围环境变量的匿名函数。

闭包使用 || 定义,竖线内是参数,后面跟着函数体。函数体可以是一个表达式(隐式返回)或一个语句块(用 {} 包裹)。

rs 复制代码
let add_one = |x: i32| -> i32 { x + 1 };   // 完整类型注解
let add_one = |x| x + 1;                   // 类型推断,隐式返回
let print = || println!("Hello");           // 无参数
rs 复制代码
fn main() {
    let x = 5;
    
    // 闭包捕获 x
    let add_x = |y| x + y;
    
    println!("5 + 3 = {}", add_x(3)); // 输出:8
}

Rust 闭包如何捕获变量取决于闭包体中对变量的使用方式。编译器会自动推导并选择合适的捕获方式:不可变借用可变借用移动所有权

【示例】如果闭包只读取变量,则会以不可变借用方式捕获。

js 复制代码
fn main() {
    let list: Vec<i32> = vec![1, 2, 3];
    let only_borrow = || println!("{:?}", list);
    only_borrow();
    println!("Still accessible: {:?}", list); // ✅ 可以继续使用
}

【示例】可变借用

js 复制代码
fn main() {
    let mut counter = 0;
    let mut inc = || counter += 1; // 捕获 &mut counter
    inc();
    inc();
    println!("{}", counter); // 2
}

【示例】移动所有权

js 复制代码
fn main() {
    let data = vec![1, 2, 3]; // data 变量拥有这个向量的所有权

    // std::thread::spawn() 创建一个新的线程
    // move 关键字将 data 的所有权从主线程转移到新线程中。
    //为什么? 确保新线程可以安全访问 data,避免主线程结束后 data 被销毁导致的悬垂引用。
    std::thread::spawn(move || {
        println!("{:?}", data); // data 所有权移入线程
    })
    .join()
    .unwrap();
    // data 不能再使用
}

切片

切片是对集合中一段连续元素的引用,不拥有所有权。例如字符串切片(&str)和数组切片(&[T])。

数组切片

  1. 切片是数组的一个可变或不可变视图(引用),长度可动态调整。
  2. 语法:&arr[start..end](包含 start,不包含 end)。
  3. 切片类型为 &[T](不可变)或 &mut [T](可变)。
rs 复制代码
fn main() {
    // 不可变切片
    let arr: [i32; 5] = [1, 2, 3, 4, 5];
    let slice: &[i32] = &arr[1..3]; 
    println!("切片:{:?}", slice); 

    // 可变切片
    let mut arr2: [i32; 5] = [1, 2, 3, 4, 5];
    let mut_slice: &mut [i32] = &mut arr2[1..4];
    mut_slice[0] = 10; 
    println!("修改后数组:{:?}", arr2);
}

切片 (如 &[i32]&mut [i32])是对原数据的引用 (Reference),而非对数据的所有权 (Ownership)获取。 引用的作用是临时借用数据的访问权,而不会转移数据的所有权。

Rust 的借用规则:

  1. 不可变引用&T):可以有多个不可变引用同时存在(共享访问)。
  2. 可变引用&mut T):同一时间只能有一个可变引用,且不能与不可变引用同时存在(独占访问)。
rs 复制代码
fn main() {
    let arr: [i32; 5] = [1, 2, 3, 4, 5];
    let slice: &[i32] = &arr[1..3]; // 创建从索引 1 到 3 的切片(元素 2, 3)
    let slcie2:&[i32] = &arr[1..4]; // 创建从索引 1 到 4 的切片(元素 2, 3, 4)
    println!("切片:{:?}", slice); // 输出:[2, 3]
    println!("切片:{:?}", slcie2); // 输出:[2, 3, 4]
}

字符串切片

见上文的字符串类型部分。

集合与泛型

Rust 的标准库提供了丰富的集合类型 (collections),它们都是泛型的------可以存储任意类型的元素。集合与泛型紧密结合,让你能够高效地操作数据,同时保持类型安全。

Vec<T>

Vec<T> 是 Rust 标准库中最常用的动态数组 类型,位于 std::vec::Vec。它可以在堆上分配一块连续内存,并允许在运行时增长或收缩。

  1. 动态大小 :元素个数可以在运行时变化(pushpopinsertremove 等)。
  2. 连续内存:元素在内存中紧密排列,缓存友好,支持 O(1) 索引访问。
  3. 所有权Vec<T> 拥有其元素的所有权,当 Vec 被销毁时,其所有元素也会被销毁。
  4. 泛型 :可以存储任何类型的元素 T
  5. 尾部操作高效pushpop 的均摊时间复杂度为 O(1)。
  6. 中间插入/删除:时间复杂度 O(n),因为需要移动后续元素。

【示例】 空 Vec

rs 复制代码
fn main() {
    let v1: Vec<i32> = Vec::new();
    let v2 = Vec::<i32>::new();
    // 使用类型推断
    let mut v = Vec::new();
    v.push(1); // 编译器推断出 v 是 Vec<i32>

    println!("{:?}", v);
    println!("{:?}", v1);
    println!("{:?}", v2);
}

【示例】 使用宏 vec!

rs 复制代码
fn main() {
    let v1 = vec![1, 2, 3]; // Vec<i32>
    let v2 = vec![0; 10]; // 包含 10 个 0 的 Vec<i32>

    println!("{:?}", v1);
    println!("{:?}", v2);
}

【示例】基本操作

rs 复制代码
fn main() {
    let mut v = Vec::new();
    v.push(42);
    let x = v.pop();
    
    // 过索引获取元素,返回类型为 `Option<&T>`(不可变引用)
    let first = v.get(0);
    println!("{:?}", x); // Some(42)
    println!("{:?}", first); // None
}

VecDeque<T>

VecDeque<T>(双端队列)是 Rust 标准库提供的一个可增长的双端队列 ,位于 std::collections::VecDeque。它支持在头部和尾部以 O(1) 时间复杂度高效地插入或删除元素,非常适合实现队列(先进先出)或双端队列(两端操作)场景。

rs 复制代码
use std::collections::VecDeque;

// 空队列
let mut deque: VecDeque<i32> = VecDeque::new();
rs 复制代码
// 预分配容量(避免频繁扩容)
let mut deque = VecDeque::with_capacity(100);
rs 复制代码
// 从数组创建 (Rust 1.56+)
let deque = VecDeque::from([1, 2, 3]);
rs 复制代码
// 从 Vec 转换
let vec = vec![1, 2, 3];
let deque: VecDeque<_> = vec.into_iter().collect();

HashMap<K, V>

HashMap 是 Rust 标准库中最常用的键值存储集合,位于 std::collections::HashMap。它基于哈希表实现,提供平均 O(1) 的插入、查找和删除操作。

注意:HashMap 的键必须实现 HashEq trait,值可以是任意类型。

rs 复制代码
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    // 插入键值对
    map.insert("key".to_string(), 100);

    let value = map.get("key"); // Option<&V> 
    // 最后一次使用不可变引用(作用域结束)
    println!("{:?}", value);

    let or_default = map.entry("key".to_string()).or_insert(0);

    println!("{:?}", or_default);
}

错误是 Rust 借用规则的经典冲突:当存在不可变引用时,无法创建可变引用

Rust 的借用规则规定:同一时间不能同时存在不可变引用和可变引用

【示例】创建与插入

rs 复制代码
use std::collections::HashMap;

fn main() {
    // 显式创建空 HashMap
    let mut scores: HashMap<String, u32> = HashMap::new();

    // 使用 insert 插入键值对
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Red"), 25);

    println!("{:?}", scores);
    
    // 使用宏 from 创建(Rust 1.56+)
    // 同名变量 scores,会覆盖之前的 scores
    let scores = HashMap::from([("Blue".to_string(), 10), ("Red".to_string(), 25)]);

    println!("{:?}", scores);
}

BTreeMap<K, V>

BTreeMap<K, V> 是 Rust 标准库提供的基于 B 树的有序键值映射 ,位于 std::collections::BTreeMap。它根据键的顺序(通过 Ord trait)存储元素,支持高效的范围查询、有序遍历和日志时间复杂度的插入、删除、查找。

HashSet<T>

HashSet<T> 是 Rust 标准库提供的基于哈希表的无序集合 ,位于 std::collections::HashSet。它存储不重复的值,并支持平均 O(1) 的插入、删除和查找操作。

BTreeSet<T>

BTreeSet<T> 是 Rust 标准库提供的基于 B 树的有序集合 ,位于 std::collections::BTreeSet。它存储不重复的值 ,并且始终按照元素的顺序(通过 Ord trait)进行排序)。

BinaryHeap<T>

BinaryHeap<T> 是 Rust 标准库提供的基于二叉堆的优先级队列 ,位于 std::collections::BinaryHeap。它是一个最大堆 (max-heap),意味着堆顶始终是集合中的最大值 。如果需要最小堆,可以通过自定义 Ord 实现或使用 std::cmp::Reverse 包装类型。

LinkedList<T>

LinkedList<T> 是 Rust 标准库提供的双向链表 实现,位于 std::collections::LinkedList。它是一个线性集合,每个元素都包含指向前一个和后一个元素的指针。

重要提示 :在绝大多数 Rust 使用场景中,LinkedList 性能不佳 ,通常应优先使用 Vec<T>VecDeque<T>。只有在需要频繁的中间插入/删除 且同时需要在链表两端进行高效操作 时,才考虑使用链表。即便如此,现代 CPU 缓存特性使得连续内存的 VecDeque 往往更快。

Trait

Trait 是 Rust 中定义共享行为 的核心机制。你可以把它理解为其他语言中的 接口(interface) ,但功能更强大:它支持默认实现、关联类型、泛型约束,并且能同时用于静态分发和动态分发。

【示例】带默认实现的 Trait:实现时可以不重写。

rs 复制代码
trait Greeter {
    fn greet(&self) {
        println!("Hello!"); // 默认实现
    }
}

struct Robot;

impl Greeter for Robot {} // 直接使用默认实现

fn main() {
    let r = Robot;
    r.greet(); // Hello!
}

【示例】定义与实现

rs 复制代码
// Trait 定义
trait Speak {
    fn speak(&self); // 只有签名,无默认实现
    fn greet(&self) {
        // 可以带默认实现
        println!("Hello!");
    }
}

// Dog 结构体:是一个单元结构体(无字段)
struct Dog;

// 为 Dog 类型实现 Speak trait
impl Speak for Dog {
    fn speak(&self) {
        println!("Woof!");
    }
    // greet 使用默认实现
}

fn main() {
    // 创建 Dog 实例:let d = Dog;(单元结构体的创建方式)
    let d = Dog;
    d.speak(); // Woof!
    d.greet(); // Hello!
}

在 Rust 中,trait 可以作为函数参数,表示函数可以接受任何实现了该 trait 的类型 。这是 Rust 实现多态的核心方式,分为静态分发动态分发两种。

静态分发(编译时确定具体类型)

  • 编译器为每种具体类型生成单独的函数副本(单态化)。
  • 零运行时开销,可内联。
  • 函数内部无法将参数存储到不同具体类型的容器中(类型单一)。

动态分发(运行时确定具体类型)

  • 参数是 trait 对象(胖指针:数据指针 + 虚表指针)。
  • 通过虚表动态调用方法,有轻微运行时开销
  • 可以将不同类型(只要实现同一 trait)传入同一函数调用。
  • 常用于需要异构集合(如 Vec<&dyn Speaker>)的场景。

模式解构(Pattern Destructuring)

模式解构是 Rust 中一种强大的语法特性,它允许你将一个复合数据结构(如元组、结构体、枚举、数组等)分解成其组成部分 ,并同时绑定到变量。这常用于 match 表达式、if letwhile let、函数参数、let 语句等场景。

【示例】元组解构

rs 复制代码
let point = (3, 5);
let (x, y) = point; // 解构元组,x=3, y=5
println!("x={}, y={}", x, y);

【示例】解构结构体

rs 复制代码
struct Point {
    x: i32,
    y: i32,
}
let p = Point { x: 10, y: 20 };
let Point { x, y } = p; // 字段名与变量名相同
let Point { x: a, y: b } = p; // 重命名变量
println!("x={}, y={}", x, y);
println!("a={}, b={}", a, b);

【示例】解构枚举

rs 复制代码
fn main() {
    enum Message {
        #[allow(dead_code)]
        Quit,
        Move {
            x: i32,
            y: i32,
        },
        #[allow(dead_code)]
        Write(String),
        #[allow(dead_code)]
        ChangeColor(u8, u8, u8),
    }

    let msg = Message::Move { x: 1, y: 2 };
    match msg {
        Message::Quit => println!("退出"),
        Message::Move { x, y } => println!("移动到 ({},{})", x, y),
        Message::Write(text) => println!("写入: {}", text),
        Message::ChangeColor(r, g, b) => println!("RGB({},{},{})", r, g, b),
    }
}

生命周期

生命周期注解语法

通常情况下,你不需要手动标注生命周期------Rust 编译器会自动推断。但在某些复杂场景,编译器无法确定引用之间的关系,就需要你显式标注。

生命周期注解使用撇号'a,读作"生命周期 a")表示,放在类型之前:

rs 复制代码
&'a T          // 生命周期为 'a 的不可变引用
&'a mut T      // 生命周期为 'a 的可变引用

生命周期注解本身不改变引用的实际存活时间,只是让编译器理解各个引用之间的存活关系。

生命周期省略规则(Lifetime Elision)

为了减少冗余注解,Rust 有一组规则,可以在常见情况下自动推断生命周期。这些规则被称为生命周期省略规则

规则适用于函数或方法的签名:

  1. 每个引用参数都有自己的生命周期
    例如:fn foo(x: &i32) → 等同于 fn foo<'a>(x: &'a i32)
  2. 如果只有一个输入生命周期,输出生命周期被赋予该生命周期
    例如:fn foo(x: &i32) -> &i32 → 等同于 fn foo<'a>(x: &'a i32) -> &'a i32
  3. 如果有多个输入生命周期,但其中一个是 &self&mut self(方法),那么输出生命周期被赋予 self 的生命周期。这使方法更容易使用。

如果应用这三条规则后,仍然无法确定输出引用的生命周期,编译器就会报错,要求你手动标注。

&'static

在 Rust 中,&'static 是一个生命周期注解 ,表示静态生命周期& 表示这是一个引用类型 (Reference)。 'static 表示该引用的生命周期是静态的 ,即在整个程序运行期间都有效

存储位置?

字符串字面量 (如 "Hello")默认是 &'static str 类型,因为它们存储在程序的只读内存区域.rodata 段),在程序启动时加载,程序结束时释放。

静态变量 (使用 static 关键字声明)的引用也具有 &'static 生命周期。

rs 复制代码
fn main() {
    let s: &str = "Hello, Rust!";
    let static_str: &'static str = "Static string";
    println!("static_str = {}", static_str);
    println!("s = {}", s);
}
rs 复制代码
static GLOBAL_VAR: i32 = 42;

fn main() {
    let global_ref: &'static i32 = &GLOBAL_VAR;
    println!("Global variable: {}", global_ref); // 42
}

macro_rules!

声明宏通过模式匹配来生成代码。

常用模式匹配标记

  1. item ,条目(函数、结构体等) fn foo() {}
  2. block, 代码块 { ... }
  3. stmt, 语句 let x = 1;
  4. pat, 模式 Some(x)
  5. expr, 表达式 2 + 2
  6. ty, 类型 u32
  7. ident, 标识符 foo
  8. path, 路径 std::collections::HashMap
  9. tt, 单个 token 树 任意 token 序列
  10. meta, 属性内的元项 cfg(test)

重复模式语法

  1. $( ... )* --- 零次或多次
  2. $( ... )+ --- 一次或多次
  3. $( ... ),* --- 零次或多次,元素间用逗号分隔
  4. $( ... );+ --- 一次或多次,用分号分隔

【示例】 简化 println!

js 复制代码
macro_rules! log {
    ($($arg:tt)*) => {
        println!("[LOG] {}", format!($($arg)*));
    };
}
macro_rules! warn {
    ($($arg:tt)+) => {
        println!("[WARN] {}", format!($($arg)+));
    };
}
fn main() {
    log!("Hello, {}!", "world"); // 输出 [LOG] Hello, world!
    warn!("Hello, {},{}!", "world", "lili"); // 输出 [WARN] Hello, world!
}

解引用

【示例】基础解引用

js 复制代码
fn main() {
    let x = 42;
    let r = &x; // r 的类型是 &i32
    // assert_eq! 是 Rust 标准库提供的一个断言宏,用于验证两个值是否相等
    // 如果两个值不相等,程序会立即 panic 并显示详细的错误信息
    assert_eq!(*r, 42); // *r 解引用得到 i32 值 
}
  1. assert! 断言一个表达式为 true assert!(x > 0)
  2. assert_eq! 断言两个值相等 assert_eq!(x, y)
  3. assert_ne! 断言两个值不相等 assert_ne!(x, y)

【实例】修改解引用

js 复制代码
fn main() {
    let mut x = 100;
    let r = &mut x;
    *r += 50; // 通过可变引用修改原值
    println!("x = {}", x); // 输出 150
}

【示例】 智能指针Box<T>解引用

js 复制代码
fn main() {
    let b = Box::new(5);
    assert_eq!(*b, 5);   // Box 实现了 Deref,*b 调用 deref 返回 &i32,再解引用
    
    let s = Box::new(String::from("hello"));
    let owned = *s;      // 从 Box 中移出 String(String 未实现 Copy)
    // println!("{}", s); // 错误:s 已无效
    println!("{}", owned); // 输出 hello
}

【示例】手动解引用

js 复制代码
fn main() {
    let x = Some(Box::new(10));
    match x {
        Some(b) => {
            let value = *b; // 手动解引用 Box,得到 i32
            println!("{}", value);
        }
        None => (),
    }
}

在 Rust 中,SomeOption 枚举的一个变体 (variant),用于表示存在某个值的情况。

Option 枚举是 Rust 中处理可能为空值的安全方式,它有两个变体:

  • Some(T) - 表示存在一个值,包含一个类型为 T 的值
  • None - 表示不存在值

【示例】

js 复制代码
fn main() {
    let x = &mut 42;
    // 先解引用再加法,正确。
    *x += 1;
    println!("{}", x); // 43
}

【示例】链式解引用

js 复制代码
use std::rc::Rc;

fn main() {
    let rc = Rc::new(Box::new(99));
    let value = **rc;   // 第一次解引用 Rc -> Box,第二次 Box -> i32
    println!("{}", value);
}

最后

  1. 集合
相关推荐
朝阳5811 天前
rust 交叉编译指南
开发语言·后端·rust
redreamSo1 天前
Turso:用 Rust 重写 SQLite,让数据库跑在每一个边缘节点
数据库·rust·sqlite
花间相见1 天前
【终端效率工具01】—— Yazi:Rust 编写的现代化终端文件管理器,告别繁琐操作
前端·ide·git·rust·极限编程
fox_lht1 天前
7.3.结构体-方法
开发语言·后端·rust
Tomhex1 天前
Rust生命周期标注核心原理
rust
代码羊羊1 天前
Rust基础类型与变量全解析
开发语言·后端·rust
Tomhex1 天前
Rust交叉编译用rust-lld配置指南
rust
朝阳5811 天前
MAVLink 消息处理指南
rust·mavlink
小杍随笔1 天前
【Rust 1.95.0 正式发布!语言特性、标准库、平台支持全面升级,一文带你看完整更新】
开发语言·rust·策略模式