Rust编程语言:从基础到进阶介绍 – wiki基地


Rust 编程语言:从基础到进阶的深度探索

引言:为什么选择 Rust?

在当今软件开发领域,我们面临着性能、安全性和并发性等多重挑战。传统的系统级语言如 C++ 提供了高性能和底层控制,但其手动内存管理极易引入内存安全问题(如空指针、数据竞争、缓冲区溢出)。而带有垃圾回收(GC)的语言(如 Java, Go, Python)虽然提供了内存安全和开发效率,但在需要确定性性能、低延迟或无运行时开销的场景(如操作系统、嵌入式系统、游戏引擎、高性能网络服务)中则显得力不从心。

正是在这样的背景下,Rust 编程语言应运而生。自 2015 年发布 1.0 版本以来,Rust 以其独特的“内存安全且不带垃圾回收”的特性迅速崛起,赢得了开发者社区的广泛赞誉。Rust 旨在提供 C++ 的性能和控制能力,同时通过创新的所有权(Ownership)系统和编译时检查来根除困扰 C++ 多年的内存安全和并发数据竞争问题。它不仅适合编写系统级软件,其强大的抽象能力、现代化的包管理工具和日益成熟的生态系统也使其成为 Web 后端、命令行工具、WebAssembly 等诸多领域的有力竞争者。

本文将带您从 Rust 的基础语法开始,逐步深入其核心概念,直至探索其进阶特性和在实际应用中的强大之处。

第一部分:Rust 基础——搭建基石

1. 安装 Rust 与 Cargo

Rust 的安装非常便捷。推荐使用 rustup 工具链安装器。在大多数类 Unix 系统或 Windows 上,只需在终端运行:

bash
curl --proto '=https' --tlsv1.2 -sSF https://sh.rustup.rs | sh

或下载 Windows 安装程序。rustup 会自动安装 Rust 编译器 rustc、包管理器和构建工具 cargo,以及其他必要的工具。

Cargo 是 Rust 的构建系统和包管理器,类似于其他语言中的 Maven, npm, pip 或 Go Modules。它简化了项目创建、依赖管理和构建过程。创建一个新的 Rust 项目只需:

bash
cargo new hello_rust
cd hello_rust

这会创建一个名为 hello_rust 的目录,其中包含一个基本的 Cargo.toml 文件(项目配置文件)和一个 src/main.rs 文件(主程序入口)。

2. Hello, World!

打开 src/main.rs 文件,您会看到经典的 Hello, World 程序:

rust
fn main() {
println!("Hello, world!");
}

  • fn main(): 定义一个名为 main 的函数,这是 Rust 可执行程序的入口点。
  • println!: Rust 中的宏(macro),用于打印文本到控制台。注意宏调用是以 ! 结尾的。

使用 Cargo 运行程序:

bash
cargo run

Cargo 会编译(如果需要)并执行程序。

3. 变量与可变性

在 Rust 中,变量默认是不可变的。使用 let 关键字声明变量:

“`rust
let x = 5; // x 是不可变的
println!(“The value of x is: {}”, x);

// x = 6; // 错误!x 是不可变的

let mut y = 5; // 使用 mut 关键字使 y 可变
println!(“The value of y is: {}”, y);
y = 6; // 允许修改
println!(“The value of y is: {}”, y);
“`

不可变性是 Rust 强调安全和并发性的方式之一。它帮助防止意外的副作用。

4. 数据类型

Rust 是一种静态类型语言,但它通常可以通过类型推断确定变量的类型。基本数据类型包括:

  • 标量类型 (Scalar Types):

    • 整型 (Integers): i8, i16, i32, i64, i128 (有符号), u8, u16, u32, u64, u128 (无符号), isize, usize (平台相关的指针大小)。默认是 i32
    • 浮点型 (Floating-Point Numbers): f32 (单精度), f64 (双精度)。默认是 f64
    • 布尔型 (Booleans): bool (值为 truefalse)。
    • 字符型 (Characters): char (表示一个 Unicode 标量值,占 4 个字节)。
  • 复合类型 (Compound Types):

    • 元组 (Tuples): 将多个不同类型的值组合成一个固定长度的类型。
      rust
      let tup: (i32, f64, u8) = (500, 6.4, 1);
      let (x, y, z) = tup; // 解构元组
      println!("The value of y is: {}", y);
      let five_hundred = tup.0; // 通过索引访问
    • 数组 (Arrays): 将多个相同类型的值组合成一个固定长度的类型。
      rust
      let a = [1, 2, 3, 4, 5];
      let first = a[0]; // 访问元素
      // let index = 10;
      // let element = a[index]; // 运行时可能 panic!,Rust 在编译时和运行时都会检查数组越界
    • 切片 (Slices): 动态大小的、对其他数据结构(如数组、Vec)的引用。它们允许您引用集合的一部分,而无需复制数据。这与所有权系统紧密相关,将在后面详细介绍。

5. 函数

使用 fn 关键字定义函数。函数可以有参数和返回值。

“`rust
fn main() {
another_function();
print_labeled_measurement(5, ‘h’);
let x = five();
println!(“The value of x is: {}”, x);
let y = plus_one(5);
println!(“The value of y is: {}”, y);
}

fn another_function() {
println!(“Another function.”);
}

fn print_labeled_measurement(value: i32, unit: char) {
println!(“The measurement is: {}{}”, value, unit);
}

fn five() -> i32 { // 使用 -> 声明返回值类型
5 // 没有分号的表达式是返回值
}

fn plus_one(x: i32) -> i32 {
x + 1 // 表达式作为返回值
}
“`

注意 Rust 的函数体由一系列语句(statements)和可选的末尾表达式(expression)组成。语句执行动作但不返回值;表达式求值并返回值。函数体末尾没有分号的表达式会被隐式地作为函数的返回值。

6. 控制流

Rust 提供常见的控制流结构:

  • if 表达式:
    “`rust
    let number = 3;
    if number < 5 {
    println!(“condition was true”);
    } else {
    println!(“condition was false”);
    }

    // if 也可以用于 let 语句
    let condition = true;
    let number = if condition { 5 } else { 6 };
    println!(“The value of number is: {}”, number);
    ``
    注意
    if的条件必须是bool` 类型,不能是其他类型(如 C/C++ 中非零即真)。

  • 循环 (Loops):

    • loop: 无限循环,可以使用 break 退出,也可以返回一个值。
      rust
      let mut counter = 0;
      let result = loop {
      counter += 1;
      if counter == 10 {
      break counter * 2; // break 可以带一个值返回
      }
      };
      println!("The result is: {}", result);
    • while: 条件循环。
      rust
      let mut number = 3;
      while number != 0 {
      println!("{}!", number);
      number -= 1;
      }
      println!("LIFTOFF!!!");
    • for: 遍历集合。Rust 的 for 循环遍历实现了 IntoIterator 特征的集合。
      “`rust
      let a = [10, 20, 30, 40, 50];
      for element in a.iter() { // 使用 .iter() 遍历元素的引用
      println!(“The value is: {}”, element);
      }

      // 遍历范围
      for number in (1..4).rev() { // (1..4) 是 Range,.rev() 反转
      println!(“{}!”, number);
      }
      println!(“LIFTOFF!!!”);
      “`

第二部分:Rust 的核心——所有权系统

所有权系统是 Rust 最独特、也是最核心的概念,它使得 Rust 能够在没有垃圾回收的情况下保证内存安全。理解所有权是掌握 Rust 的关键。

1. 所有权规则

所有权是一组规则,管理程序如何使用内存。这些规则在编译时由编译器检查。如果任何规则被违反,程序将无法编译。

核心规则:

  • 每个值都有一个变量作为其所有者(Owner)。
  • 同一时间,一个值只能有一个所有者。
  • 当所有者超出作用域(scope)时,值将被丢弃(drop),内存被释放。

2. 作用域(Scope)

一个项(item)有效的范围称为其作用域。在 Rust 中,作用域通常由 {} 花括号定义。当一个变量离开其作用域时,Rust 会自动调用其类型的 drop 函数(如果存在),清理资源。

rust
fn main() {
{ // s 在这里无效,因为它还没有声明
let s = String::from("hello"); // s 在这里生效
// s 可以使用
} // 作用域结束,s 不再有效,内存被释放 (drop)
}

String 类型与基本类型不同,它将数据存储在堆上,并且需要手动管理其内存。Rust 的所有权系统正是为了自动化这个手动管理过程。

3. Move (移动)

在 Rust 中,当一个变量赋值给另一个变量,或者作为参数传递给函数时,对于某些类型(如 String),会发生移动。原始变量将不再有效。

rust
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权被移动到 s2
// println!("{}, world!", s1); // 错误!s1 不再有效
println!("{}, world!", s2); // s2 是有效的

这与 C++ 的浅拷贝不同,Rust 不会复制堆上的数据,而是使原始绑定无效。这防止了双重释放错误。

对于基本类型(如整数、布尔值),它们是存储在栈上的,并且实现了 Copy 特征。在这种情况下,赋值或传递会进行拷贝,原始变量仍然有效。

rust
let x = 5;
let y = x; // x 的值被拷贝给 y
println!("x = {}, y = {}", x, y); // x 和 y 都有效

4. Borrowing (借用)

如果不想转移所有权,但仍然需要使用一个值,可以使用引用(references)。引用允许您“借用”一个值,而无需获得其所有权。这称为借用。

引用默认是不可变的。

“`rust
fn main() {
let s1 = String::from(“hello”);
let len = calculate_length(&s1); // 将 s1 的不可变引用传递给函数
println!(“The length of ‘{}’ is {}.”, s1, len); // s1 仍然有效
}

fn calculate_length(s: &String) -> usize { // 接收 String 的不可变引用
s.len()
} // s 在这里超出作用域,但它不拥有 s1,所以什么都不会发生
“`

可变借用 (Mutable Borrowing):

如果您需要修改被借用的值,可以使用可变引用 &mut

“`rust
fn main() {
let mut s = String::from(“hello”); // s 需要是可变的
change(&mut s); // 传递 s 的可变引用
println!(“{}”, s);
}

fn change(some_string: &mut String) { // 接收 String 的可变引用
some_string.push_str(“, world”);
}
“`

借用的规则 (Borrowing Rules):

在同一作用域内,对于特定的数据,只能进行以下两种借用之一:

  • 一个可变引用 (&mut T)
  • 任意数量的不可变引用 (&T)

您不能在拥有一个活动的可变引用的同时创建不可变引用,反之亦然。也不能在拥有一个可变引用的同时创建另一个可变引用。

这些规则由 Rust 的借用检查器 (Borrow Checker) 在编译时强制执行,从而消除了数据竞争(data races),这是并发编程中常见的难题。数据竞争可能导致未定义行为。

5. Slices (切片)

切片是一种特殊的引用,它允许您引用集合中连续的一系列元素,而不是整个集合。切片是动态大小的类型。

“`rust
fn first_word(s: &String) -> &str { // 函数接收 String 的不可变引用,返回字符串切片
let bytes = s.as_bytes(); // 将 String 转换为字节数组

for (i, &item) in bytes.iter().enumerate() {
    if item == b' ' { // 查找第一个空格的字节
        return &s[0..i]; // 返回从开始到空格前的切片
    }
}

&s[..] // 如果没有找到空格,返回整个字符串切片

}

fn main() {
let mut s = String::from(“hello world”);
let word = first_word(&s); // word 是 s 的不可变引用切片

// s.clear(); // 错误!不能在拥有不可变引用的同时修改 s

println!("the first word is: {}", word);

s.clear(); // 现在 word 已经超出作用域,s 可以修改

}
“`

字符串字面量 ("hello world") 本身就是字符串切片 &'static str

第三部分:Rust 的进阶特性

掌握了所有权和借用后,我们可以深入 Rust 更强大的特性。

1. Structs (结构体)

结构体允许您创建自定义的复合数据类型,将多个字段组合在一起。

“`rust
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}

fn main() {
let user1 = User {
email: String::from(“[email protected]”),
username: String::from(“someusername123”),
active: true,
sign_in_count: 1,
};

// 访问字段
println!("User email: {}", user1.email);

// 修改可变结构体的字段
let mut user2 = User {
    email: String::from("[email protected]"),
    username: String::from("anotherusername567"),
    active: true,
    sign_in_count: 1,
};
user2.email = String::from("[email protected]");

// 结构体更新语法
let user3 = User {
    email: String::from("[email protected]"),
    username: user1.username, // username 字段从 user1 移动
    ..user2 // 复制 user2 剩余字段的值 (active, sign_in_count)
}; // 注意 user1.username 因为移动不再有效

}
“`

2. Enums (枚举) 与 Pattern Matching (模式匹配)

枚举允许您定义一个类型,它可能是几个预设的变体之一。枚举在 Rust 中非常强大,可以关联数据。

“`rust
enum IpAddrKind {
V4,
V6,
}

enum IpAddr {
V4(String), // 变体可以关联数据
V6(String),
}

enum IpAddrComplex {
V4(u8, u8, u8, u8), // 变体可以关联不同类型和数量的数据
V6(String),
}

enum Message {
Quit,
Move { x: i32, y: i32 }, // 变体可以关联结构体
Write(String),
ChangeColor(i32, i32, i32),
}

impl Message { // 枚举也可以实现方法
fn call(&self) {
// 在这里定义方法体
}
}

fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));

let m = Message::Write(String::from("hello"));
m.call();

}
“`

模式匹配 (match) 是 Rust 中处理枚举和其他类型值的强大工具。它可以精确地匹配值的不同可能形态,并执行相应的代码。

“`rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // 关联数据
}

enum UsState {
Alabama,
Alaska,
// … many more
}

fn value_in_cents(coin: Coin) -> u8 {
match coin { // match 后跟要匹配的值
Coin::Penny => { // 每个分支是一个模式 => 对应的代码块
println!(“Lucky penny!”);
1 // 返回值
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => { // 匹配并提取关联的数据
println!(“State quarter from {:?}!”, state);
25
},
}
}

fn main() {
let coin = value_in_cents(Coin::Quarter(UsState::Alabama));
println!(“Value is: {}”, coin);

// Option 枚举:处理可能存在或不存在的值
let five = Some(5);
let six = plus_one(five); // Some(6)
let none = plus_one(None); // None

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None, // 处理 None 情况
        Some(i) => Some(i + 1), // 处理 Some(i) 情况,并提取 i
    }
}

// _ 模式:匹配所有其他情况
let dice_roll = 9;
match dice_roll {
    7 => println!("You win!"),
    _ => println!("Roll again!"), // 匹配除了 7 以外的所有值
}

// if let 精简模式匹配
let config_max = Some(3u8);
if let Some(max) = config_max { // 如果 config_max 是 Some(max) 模式,则执行块
    println!("The maximum is configured to be {}", max);
}

}
“`

Option<T> 枚举是 Rust 标准库中用于处理可能存在或不存在值的关键类型,它比使用 null 更安全,因为它强制开发者显式处理 None 的情况。类似地,Result<T, E> 用于处理可能成功 (Ok(T)) 或失败 (Err(E)) 的操作,这是 Rust 标准的错误处理方式。

3. Error Handling (错误处理)

Rust 区分可恢复错误和不可恢复错误。

  • 不可恢复错误 (panic!): 当程序遇到无法处理的致命问题时,使用 panic! 宏。它会打印错误信息,展开(或中止)栈,并退出程序。通常用于编程错误或不可恢复的状态。
  • 可恢复错误 (Result<T, E>): 对于可能失败但可以由调用者处理的操作,使用 Result<T, E> 枚举。T 代表成功时返回的值类型,E 代表失败时返回的错误类型。

“`rust
use std::fs::File;
use std::io::ErrorKind;

fn main() {
let greeting_file_result = File::open(“hello.txt”);

let greeting_file = match greeting_file_result {
    Ok(file) => file, // 打开成功,返回文件句柄
    Err(error) => match error.kind() { // 打开失败,根据错误类型进一步处理
        ErrorKind::NotFound => match File::create("hello.txt") { // 文件不存在,尝试创建
            Ok(fc) => fc, // 创建成功
            Err(e) => panic!("Problem creating the file: {:?}", e), // 创建失败,不可恢复
        },
        other_error => { // 其他错误类型,不可恢复
            panic!("Problem opening the file: {:?}", other_error);
        }
    },
};

// 使用 .unwrap() 和 .expect() 简化错误处理 (但在生产代码中需谨慎)
// let greeting_file = File::open("hello.txt").unwrap(); // 失败则 panic!
// let greeting_file = File::open("hello.txt").expect("Failed to open hello.txt"); // 失败则 panic! 并附带自定义信息

// 使用 ? 运算符简化 Result 的传播
// fn read_username_from_file() -> Result<String, io::Error> {
//     let mut username_file = File::open("hello.txt")?; // 传播 Ok 或 Err
//     let mut username = String::new();
//     username_file.read_to_string(&mut username)?; // 传播 Ok 或 Err
//     Ok(username) // 成功时返回 Ok
// }

}
“`

? 运算符是处理 ResultOption 的语法糖,它可以在 ErrNone 时提前从函数返回该错误或 None,在 Ok(T)Some(T) 时提取内部的值 T

4. Generics (泛型)

泛型允许您编写可以处理多种类型而不是特定具体类型的代码,从而实现代码的复用和抽象。

“`rust
// 没有泛型,需要为每种类型写函数
// fn largest_i32(list: &[i32]) -> i32 { … }
// fn largest_char(list: &[char]) -> char { … }

// 使用泛型
fn largest(list: &[T]) -> T { // T 是泛型类型参数,要求实现 PartialOrd 和 Copy 特征
let mut largest = list[0];
for &item in list.iter() { // 注意这里 item 是 T 的引用,因为 list 是 T 的引用切片
if item > largest {
largest = item;
}
}
largest
}

fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!(“The largest number is {}”, result);

let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);

}

// 泛型也可以用于结构体和枚举
struct Point {
x: T,
y: U,
}

impl Point { // 在 impl 后面声明泛型参数
fn x(&self) -> &T {
&self.x
}
}

impl Point { // 可以为特定泛型类型实现方法
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}

fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
let mixed = Point { x: 5, y: 4.0 };
}
“`

5. Traits (特征)

特征是 Rust 提供的一种抽象机制,类似于其他语言中的接口或抽象类。它定义了类型可以提供的功能。

“`rust
// 定义一个特征
trait Summary {
// 默认实现 (可选)
fn summarize_author(&self) -> String;

// 只需要实现此方法
fn summarize(&self) -> String {
    format!("(Read more from {}...)", self.summarize_author())
}

}

struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}

// 为 NewsArticle 实现 Summary 特征
impl Summary for NewsArticle {
fn summarize_author(&self) -> String {
format!(“{}”, self.author)
}

// 可以选择不实现 summarize,使用默认实现
// fn summarize(&self) -> String {
//     format!("{}, by {} ({})", self.headline, self.author, self.location)
// }

}

struct Tweet {
username: String,
content: String,
reply: bool,
retweet: bool,
}

// 为 Tweet 实现 Summary 特征
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!(“@{}”, self.username)
}

fn summarize(&self) -> String {
    format!("{}: {}", self.username, self.content)
}

}

fn main() {
let tweet = Tweet {
username: String::from(“horse_ebooks”),
content: String::from(“of course, as you probably already know, people”),
reply: false,
retweet: false,
};

println!("1 new tweet: {}", tweet.summarize());

let article = NewsArticle {
    headline: String::from("Penguins win the Stanley Cup Championship!"),
    location: String::from("Pittsburgh, PA, USA"),
    author: String::from("Iceburgh"),
    content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
};

println!("New article available: {}", article.summarize());

}
“`

特征约束 (Trait Bounds) 可以与泛型结合使用,要求泛型类型实现特定的特征,以便可以使用特征定义的方法。

“`rust
// 函数 greet 要求 item 实现 Summary 特征
fn notify(item: &impl Summary) { // 这是 impl Trait 语法糖
println!(“Breaking news! {}”, item.summarize());
}

// 等同于 trait bound 语法
// fn notify(item: &T) {
// println!(“Breaking news! {}”, item.summarize());
// }

// 要求实现多个特征
// fn notify(item: &T) { … }

// Where 子句简化复杂约束
// fn notify(item1: &T, item2: &U) -> String
// where T: Summary + Display,
// U: Clone + Debug
// { … }
“`

6. Lifetimes (生命周期)

生命周期是 Rust 独有的一个概念,它与借用系统紧密相关。生命周期的主要目的是防止悬垂引用(dangling references)——即引用指向已经被释放的内存。

生命周期注解 ('a, 'b, etc.) 告诉借用检查器多个引用的生命周期之间的关系,确保所有引用都是有效的。大多数时候,借用检查器可以自行推断生命周期,但某些情况下(尤其是函数签名中),需要手动注解。

“`rust
// 这是一个编译错误,因为 Rust 不知道返回的引用是来自于 x 还是 y,如果 x 或 y 提前失效,返回的引用就会悬垂
// fn longest(x: &str, y: &str) -> &str {
// if x.len() > y.len() {
// x
// } else {
// y
// }
// }

// 使用生命周期注解来指定返回的引用与输入引用的生命周期关系
// ‘a 是生命周期参数,表示 longest 函数返回的引用的生命周期
// 与输入参数 x 和 y 中生命周期较短的那个相同
fn longest<‘a>(x: &’a str, y: &’a str) -> &’a str {
if x.len() > y.len() {
x
} else {
y
}
}

struct ImportantExcerpt<‘a> { // 结构体包含引用时,需要生命周期注解
part: &’a str,
}

fn main() {
let string1 = String::from(“abcd”);
let string2 = “xyz”;

let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);

let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let i = ImportantExcerpt { part: first_sentence }; // i 的生命周期不能超过 first_sentence

}
“`

生命周期注解 改变引用的实际生命周期,它们只是帮助借用检查器分析引用之间的有效性关系。掌握生命周期需要时间和实践,它是 Rust 强制内存安全的关键机制之一。

7. Concurrency and Parallelism (并发与并行)

Rust 在并发编程方面表现出色,它通过其所有权和类型系统有效地防止了数据竞争。

  • 线程 (Threads): 使用标准库中的 std::thread::spawn 创建新线程。
    “`rust
    use std::thread;
    use std::time::Duration;

    fn main() {
    let handle = thread::spawn(|| { // 闭包作为新线程的入口
    for i in 1..10 {
    println!(“hi number {} from the spawned thread!”, i);
    thread::sleep(Duration::from_millis(1));
    }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
    
    handle.join().unwrap(); // 等待新线程完成
    

    }
    * **消息传递 (Message Passing):** 使用通道 (channels) 在线程间安全地传递数据。`std::sync::mpsc` 提供了多生产者、单消费者 (Multiple Producer, Single Consumer) 通道。rust
    use std::sync::mpsc;
    use std::thread;
    use std::time::Duration;

    fn main() {
    let (tx, rx) = mpsc::channel(); // 创建通道:发送者 tx,接收者 rx

    let tx1 = tx.clone(); // 克隆发送者,允许多个生产者
    thread::spawn(move || { // 将 tx1 移动到新线程
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];
    
        for val in vals {
            tx1.send(val).unwrap(); // 发送值
            thread::sleep(Duration::from_secs(1));
        }
    });
    
    thread::spawn(move || { // 另一个生产者
        let vals = vec![
            String::from("more"),
            String::from("messages"),
        ];
        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });
    
    for received in rx { // 接收所有发送过来的值
        println!("Got: {}", received);
    }
    

    }
    * **共享状态并发 (Shared-State Concurrency):** 使用原子引用计数 `Arc` 和互斥锁 `Mutex` 安全地在多个线程间共享和修改数据。Rust 的类型系统和借用检查器确保您不会在没有正确锁的情况下访问数据。`Arc<T>` 提供了线程安全的引用计数;`Mutex<T>` 提供了互斥访问。rust
    use std::sync::{Mutex, Arc};
    use std::thread;

    fn main() {
    // Arc 用于在多个线程间共享所有权
    // Mutex 用于提供可变性的安全访问
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter); // 克隆 Arc 智能指针
        let handle = thread::spawn(move || { // 将克隆的 Arc 移动到新线程
            let mut num = counter.lock().unwrap(); // 获取 Mutex 锁,得到 MutexGuard
            *num += 1; // 修改共享数据
        }); // MutexGuard 在这里超出作用域,锁被自动释放
    
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Result: {}", *counter.lock().unwrap());
    

    }
    ``
    Rust 通过
    SendSync这两个标记特征来确保线程安全。实现了Send的类型可以在线程间安全地转移所有权;实现了Sync` 的类型可以安全地被多个线程持有不可变引用。Rust 的标准库类型大都实现了这些特征,但对于用户自定义类型,需要确保其内部成员也实现了这些特征。

8. Unsafe Rust (不安全的 Rust)

尽管 Rust 极力保证安全,但有时需要绕过编译器的安全检查,例如与外部 C 代码交互 (FFI)、实现操作系统底层功能、或者创建高性能的数据结构。这时可以使用 unsafe 关键字。

unsafe 块并不会关闭所有安全检查,它只是允许您执行以下五种不安全操作:

  1. 解引用裸指针 (*const T, *mut T)
  2. 调用不安全的函数或方法
  3. 访问或修改可变的静态变量
  4. 实现不安全的特征 (Unsafe Traits)
  5. 访问 union 的字段 (Rust 1.19+ 稳定)

“`rust
fn main() {
let mut num = 5;

let r1 = &num as *const i32; // 创建裸指针 (不可变)
let r2 = &mut num as *mut i32; // 创建裸指针 (可变)

unsafe { // 在 unsafe 块中解引用裸指针
    println!("r1 is: {}", *r1);
    println!("r2 is: {}", *r2);
}

unsafe fn dangerous() { // 不安全的函数
    println!("This is dangerous!");
}
unsafe {
    dangerous(); // 在 unsafe 块中调用不安全函数
}

}
“`

使用 unsafe 是强大的,但也意味着您需要对内存安全负起责任。通常,unsafe 代码会被封装在安全的抽象中(如标准库中的 VecString),以便用户可以安全地使用它们。

9. Smart Pointers (智能指针)

智能指针是一种数据结构,它的行为类似于指针,但额外拥有元数据和功能(如引用计数、自定义清理逻辑)。Rust 标准库提供了几种常用的智能指针:

  • Box<T>: 在堆上分配值。用于处理未知大小的类型或拥有大数据,以及递归类型。
    rust
    enum List {
    Cons(i32, Box<List>), // 使用 Box 来拥有堆上的 List,打破无限递归大小
    Nil,
    }
    use List::{Cons, Nil};
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
  • Rc<T> (Reference Counting): 引用计数智能指针。允许一个值有多个不可变所有者。当引用计数归零时,值会被丢弃。用于单线程内的多所有权场景。
    “`rust
    use std::rc::Rc;

    enum ListRc {
    Cons(i32, Rc),
    Nil,
    }
    use ListRc::{Cons, Nil};

    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!(“count after creating a = {}”, Rc::strong_count(&a)); // 计数为 1
    let b = Cons(3, Rc::clone(&a)); // 克隆 Rc 智能指针,引用计数增加
    println!(“count after creating b = {}”, Rc::strong_count(&a)); // 计数为 2
    {
    let c = Cons(4, Rc::clone(&a)); // 再次克隆
    println!(“count after creating c = {}”, Rc::strong_count(&a)); // 计数为 3
    } // c 超出作用域,引用计数减少
    println!(“count after c goes out of scope = {}”, Rc::strong_count(&a)); // 计数为 2
    * **`Arc<T>` (Atomic Reference Counting)**: 原子引用计数智能指针。与 `Rc<T>` 类似,但其引用计数操作是原子性的,可以在**多线程**间安全使用。这是 `Rc<T>` 的线程安全版本。rust
    use std::sync::{Arc, Mutex};
    use std::thread;

    let counter = Arc::new(Mutex::new(0));
    // (如上面并发部分所示,Arc 与 Mutex 结合用于线程间共享可变状态)
    * **`RefCell<T>`**: 提供了**内部可变性**,即当您拥有一个不可变引用时,仍然可以修改其内部的值。它在**运行时**检查借用规则(而不是编译时),因此过度使用可能导致 `panic!`。`RefCell<T>` 仅用于**单线程**场景。rust
    use std::cell::RefCell;
    use std::rc::Rc;

    // 模拟一个可以被多个消费者借用但仍然可以报告值的消息发送者
    pub trait Messenger {
    fn send(&self, msg: &str);
    }

    pub struct LimitTracker<‘a, T: Messenger> {
    messenger: &’a T,
    value: usize,
    max: usize,
    }

    impl<‘a, T: Messenger> LimitTracker<‘a, T> {
    pub fn set_value(&mut self, value: usize) {
    self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;
    
        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage_of_max >= 0.9 {
            self.messenger.send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger.send("Warning: You've used up over 75% of your quota!");
        }
    }
    

    }

    [cfg(test)]

    mod tests {
    use super::*;

    struct MockMessenger {
        // 使用 RefCell 包裹 vec,即使 MockMessenger 是不可变的引用,也可以修改 sent_messages
        sent_messages: RefCell<Vec<String>>,
    }
    
    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: RefCell::new(vec![]),
            }
        }
    }
    
    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            // 在不可变引用上调用可变方法,通过 RefCell 实现
            self.sent_messages.borrow_mut().push(String::from(message));
        }
    }
    
    #[test]
    fn it_sends_an_urgent_warning_when_75_percent_of_limit_is_used() {
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
        limit_tracker.set_value(76);
        // 在不可变引用上获取 RefCell 的不可变借用
        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
    }
    

    }
    ``Rc>` 是一个常见的组合,用于在单线程内实现多个所有者对数据的可变访问。

10. Macros (宏)

宏是 Rust 中元编程的一种形式,它允许您编写可以生成其他代码的代码。宏在解析器阶段运行,处理的是抽象语法树(AST),而不是运行时值。

  • 声明式宏 (Declarative Macros – macro_rules!): 这是最常见的宏形式,通过模式匹配语法来替换代码。例如 println!, vec!, assert!
  • 过程式宏 (Procedural Macros): 更强大的宏,允许您编写 Rust 代码来操作其他 Rust 代码的 AST。常用于生成代码,如派生宏 (#[derive(Debug)])、属性宏 (#[route("/")])、函数式宏 (json!({...}))。

“`rust
// 简单的声明式宏示例
macro_rules! say_hello {
() => { // () 是匹配模式,{} 是要生成的代码
println!(“Hello!”);
};
}

fn main() {
say_hello!(); // 调用宏
}

// 更复杂的宏示例
macro_rules! create_function {
($func_name:ident) => { // 匹配一个标识符,并捕获为 $func_name
fn $func_name() { // 生成一个函数,函数名为捕获的标识符
println!(“You called {}“, stringify!($func_name)); // stringify! 将标识符转为字符串
}
};
}

create_function!(foo); // 生成 fn foo() { … }
create_function!(bar); // 生成 fn bar() { … }

fn main() {
foo();
bar();
}
“`
宏是 Rust 表达能力和零成本抽象的重要组成部分,但编写和理解复杂宏可能比较困难。

11. Asynchronous Programming (异步编程)

Rust 的异步编程模型建立在 async/await 语法和 Futures 之上。它允许您编写非阻塞、事件驱动的代码,而无需传统的线程或回调地狱。

  • Future 特征: 代表一个可能尚未完成的计算。当 Future 准备好执行下一步时,会由执行器 (Executor) 唤醒。
  • async fn: 定义一个返回 Future 的异步函数。
  • .await: 暂停当前 Future 的执行,等待另一个 Future 完成,然后继续执行。它只能在 async fnasync 块中使用。
  • 执行器 (Executor): 负责轮询 (polling) Futures,驱动它们向前执行。标准库只提供了 Future 的定义,没有内置执行器。需要依赖第三方库,如 tokioasync-std

“`rust
// 假设使用 tokio 执行器
use tokio::time::{sleep, Duration};

// 定义一个异步函数
async fn my_async_function() {
println!(“Starting async operation…”);
// 等待一个 Future (这里的 Future 是 sleep 返回的)
sleep(Duration::from_secs(1)).await;
println!(“Async operation finished.”);
}

// main 函数需要标记为 #[tokio::main] 或 #[async_std::main] 来作为异步入口点

[tokio::main]

async fn main() {
println!(“Hello from main before async!”);
my_async_function().await; // 等待异步函数完成
println!(“Hello from main after async!”);

// 运行多个并发的 Futures
let future1 = my_async_function();
let future2 = my_async_function();

// tokio::join! 可以并行执行多个 Future
tokio::join!(future1, future2);

}
“`
异步编程是 Rust 用于构建高性能网络服务、I/O密集型应用的关键能力。

第四部分:生态系统与应用领域

Rust 的生态系统正在快速发展。Cargo 简化了依赖管理和 crate(Rust 包)的发布/使用。crates.io 是官方的 crate 注册中心,拥有大量高质量的库。

一些常见的 Rust 应用领域:

  • 系统编程: 操作系统、嵌入式系统、驱动程序。
  • Web 后端: 使用 Actix-Web, Rocket, Axum 等框架构建高性能服务。
  • 命令行工具: Cargo 的便捷性加上 Rust 的性能使其成为编写 CLI 工具的理想选择。
  • 网络编程: 高性能 TCP/UDP 服务器、代理、协议实现。
  • WebAssembly (Wasm): Rust 是编译到 Wasm 的主力语言之一,用于在浏览器或 Node.js 环境中运行高性能代码。
  • 游戏开发: 游戏引擎、工具、库。
  • 区块链: Solana, Polkadot 等知名区块链项目大量使用 Rust。
  • 数据库: 构建高性能数据库和存储引擎。

学习 Rust 的资源

  • The Rust Programming Language (简称 “The Book”): 官方提供的免费在线书籍,是学习 Rust 的最佳起点。
  • Rustlings: 一个通过解决小型练习来学习 Rust 的交互式课程。
  • Rust by Example: 通过代码示例来学习 Rust。
  • crates.io: 探索 Rust 生态系统中的库。
  • Rust 官方文档: 深入了解标准库和语言细节。

结论

Rust 是一门充满挑战但回报丰厚的语言。它的所有权系统和借用检查器在初期可能需要一些时间来适应,但一旦掌握,它将赋予您编写高性能、可靠且内存安全代码的能力,并且不会引入垃圾回收的性能不确定性。

从基础的变量、类型、控制流,到核心的所有权、借用、生命周期,再到进阶的结构体、枚举、特征、错误处理、并发和异步编程,Rust 提供了一套强大且一致的工具集。其现代化的构建系统 Cargo 和日益繁荣的生态系统也极大地提升了开发体验。

随着越来越多的项目采用 Rust,无论是为了其性能优势,还是为了其无与伦比的安全性承诺,Rust 都在软件开发的未来中占据着越来越重要的位置。如果您正在寻找一门能让您更接近硬件、编写高性能代码、同时又能保证高级语言的安全性和抽象能力的语言,那么 Rust 绝对值得您投入时间去学习和探索。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部