Rust 是一门注重内存安全、性能和并发性的系统编程语言。以下是其核心语法的扩展介绍,涵盖更多细节和实用特性,适合初学者快速了解并深入掌握。

1. 变量与常量

  • 变量:使用 let 声明,默认不可变。使用 mut 允许修改。变量可以被遮蔽(重新声明)。

    let x = 5;
    let x = x + 1; // 遮蔽,创建新变量
    let mut y = 10;
    y = 15;

    常见场景与注意事项:

    • 配置常量与全局值:不可变配置信息用 conststatic,但如果需要延迟初始化或线程安全的可变全局,优先使用 once_cell::sync::Lazylazy_static + Mutex
    • 遮蔽(shadowing)用于按步骤转换值(例如 let s = s.trim();),可以减少可变性并限定作用域。
    • 谨慎使用 static mut —— 这是不安全的;如需全局可变状态请使用 Atomic* 类型或加锁封装。
    • 类型推断与转换:Rust 编译器能自动推断大多数类型,但在复杂情况下需要显式声明。
      let x = 5;          // 推断为 i32
      let y: f64 = 3.14;  // 显式声明类型
      let z = x as f64;   // 类型转换
  • 常量:使用 const 声明,需指定类型,全局作用域,值在编译时确定。

    const MAX_VALUE: u32 = 100_000; // 下划线提高数字可读性

    常见场景与注意事项:

    • const 适用于编译期已知、不依赖运行时环境的常量(如默认缓冲大小、协议常量)。
    • 如果需要运行时初始化的全局不可变值,使用 once_cell::sync::Lazy 初始化 static
  • 静态变量:使用 static 声明,生命周期贯穿程序运行。

    static GREETING: &str = "Hello, Rust!";

    常见场景与注意事项:

    • static 保存静态字符串、表驱动数据或与 FFI 交互时的固定地址数据。
    • 若需要可变静态数据,请使用 static 搭配线程安全原语(如 Atomic*Mutex)或 Lazy 延迟初始化。
    • 类型别名:使用 type 关键字创建类型别名,提高代码可读性。
      type Kilometers = i32;
      type Result<T> = std::result::Result<T, String>;
    • 字面量后缀:使用后缀明确指定数值类型。
      let x = 42u8;   // u8 类型
      let y = 3.14f32; // f32 类型
      let z = 100_000i64; // i64 类型,使用下划线提高可读性

2. 数据类型

  • 标量类型
    • 整数:有符号 (i8, i16, i32, i64, i128),无符号 (u8, u16, u32, u64, u128),以及 isize/usize(与系统架构相关)。
    • 浮点数:f32, f64
    • 布尔:bool (true, false)。
    • 字符:char (Unicode,单引号,如 '😊')。
  • 复合类型
    • 元组:固定长度,异构类型,可通过 .0, .1 访问或解构。
      let tup: (i32, f64, char) = (500, 6.4, 'z');
      let (x, y, z) = tup; // 解构
    • 数组:固定长度,同构类型,栈分配。
      let arr: [i32; 5] = [1, 2, 3, 4, 5];
      let first = arr[0];
    • 向量:动态数组,使用标准库 Vec<T>,堆分配。
      let mut v = vec![1, 2, 3];
      v.push(4);
    • 切片(Slice):对连续序列的“视图”,不拥有数据,长度在运行时确定。常用于字符串与数组的局部访问,避免拷贝。
      let arr = [1, 2, 3, 4, 5];
      let slice: &[i32] = &arr[1..4];   // 包含 1..3,即 [2, 3, 4]
      println!("{:?}", slice);          // [2, 3, 4]
        
      let s = String::from("hello rust");
      let hello: &str = &s[0..5];       // 字符串切片
      println!("{}", hello);            // hello
      • 切片类型写作 &[T]&str,自带长度信息,可与泛型配合写出通用算法。
      • 空切片 &[] 合法,可用于表示“无数据”而无需 Option
      • 切片与借用规则一致:同一时刻只能有一个可变切片或多个不可变切片。

常见场景与注意事项:

  • 字符串边界:处理 UTF-8 字符串时不要盲目按字节切片(&s[0..n]),这可能截断多字节字符;使用 char 迭代或 s.get(..n) 做安全检查。
  • 数组 vs Vec:如果长度固定且小且无需堆分配,优先用数组;若涉及动态增长或可变长度,使用 Vec<T> 并考虑 with_capacity 以减少重分配。
  • API 设计:函数接受 &[T]&str 更通用,可以接收数组、切片和向量的引用,减少拷贝。
  • 字符串类型
    • String:可增长的 UTF-8 字符串,拥有数据。
    • &str:字符串切片,不拥有数据。
    let s1 = String::from("hello");
    let s2 = "world";           // &str 类型
    let s3 = s1 + " " + s2;     // String 拼接
    let len = s3.len();         // 获取字节长度(非字符数)
  • 类型转换
    let num_str = "42";
    let num: i32 = num_str.parse().unwrap();  // 字符串转数字
    let str_from_num = num.to_string();       // 数字转字符串

3. 函数

  • 函数使用 fn 定义,参数和返回值需显式指定类型。无返回值时省略 ->
    fn add(a: i32, b: i32) -> i32 {
        a + b // 表达式返回值,无需 return
    }
  • 发散函数:永不返回,使用 ! 作为返回类型(如 panic!)。
    fn diverge() -> ! {
        panic!("This function never returns!");
    }

常见场景与注意事项:

  • 小函数优先:把复杂逻辑拆成小函数,提高可测试性与可读性,便于单元测试。
  • 错误传播与返回类型:库函数应尽量返回 Result<T, E>,避免在库内部 panic!,将不可恢复错误留在应用层或测试。
  • 函数签名设计:如果函数需要灵活接受所有权或引用,提供多种签名(例如接收 &str 而非 String),或写成泛型以适配更多输入。
  • 函数指针:可以将函数作为参数传递或存储在变量中。
    fn add(a: i32, b: i32) -> i32 { a + b }
    let operation: fn(i32, i32) -> i32 = add;
    let result = operation(5, 3);
  • 高阶函数:接受函数作为参数或返回函数的函数。
    fn apply_twice<F>(f: F, x: i32) -> i32
    where F: Fn(i32) -> i32 {
        f(f(x))
    }
    let result = apply_twice(|x| x + 1, 5); // 7
  • 返回语句:虽然 Rust 是表达式语言,但也可以使用显式返回。
    fn early_return(x: i32) -> i32 {
        if x > 10 {
            return x * 2;  // 提前返回
        }
        x + 1  // 隐式返回
    }

4. 控制流

  • 条件语句
    • if 支持表达式风格返回值,无需三元运算符。
      let number = 7;
      let result = if number > 0 { "positive" } else { "non-positive" };
    • 条件必须是 bool 类型。
  • 循环
    • loop:无限循环,可用 break 返回值。
      let mut counter = 0;
      let result = loop {
          counter += 1;
          if counter == 10 {
              break counter * 2;
          }
      };
    • while:条件循环。
      while counter < 15 {
          counter += 1;
      }
    • for:常用于迭代器或范围。
      for i in 0..5 { // 包含 0,不包含 5
          println!("i: {}", i);
      }

常见场景与注意事项:

  • 使用 if 表达式构造值可以避免可变变量,比如 let status = if cond { a } else { b };
  • 选择合适的迭代方式:iter()(借用)、iter_mut()(可变借用)、into_iter()(消费并取得所有权),以避免所有权相关编译错误。
  • loop + break 值在需要复杂退出条件且最终返回计算结果时更清晰。
  • 模式匹配在控制流中的应用
    let pair = (0, -2);
    match pair {
        (0, y) => println!("x=0, y={}", y),
        (x, 0) => println!("x={}, y=0", x),
        _ => println!("其他情况"),
    }
  • 标签循环:可以给循环添加标签,用于明确指定 break/continue 的目标。
    'outer: for i in 0..3 {
        'inner: for j in 0..3 {
            if i == 1 && j == 1 {
                break 'outer;
            }
            println!("({}, {})", i, j);
        }
    }

5. 所有权与借用

  • 所有权:每个值有唯一所有者,离开作用域时释放。移动后原变量失效。
    let s1 = String::from("hello");
    let s2 = s1; // s1 失效
    // println!("{}", s1); // 错误:s1 已移动
  • 借用:使用 & 创建不可变引用,&mut 创建可变引用。同一时间只能有一个可变引用或多个不可变引用。
    let mut s = String::from("hello");
    let r1 = &s; // 不可变借用
    // let r2 = &mut s; // 错误:不可同时有可变和不可变借用
  • 复制:实现了 Copy trait 的类型(如基本类型)不会移动。
    let x = 5;
    let y = x; // 复制,x 仍有效

常见场景与注意事项:

  • 函数参数设计:若函数不需要修改数据,使用 &T;若需要修改则 &mut T;若函数需要获得所有权(例如构建返回值),传 T
  • Clone 的成本:对大型结构频繁 clone() 会有性能开销,优先考虑借用或使用共享引用(Rc/Arc)。
  • 临时值借用:不要返回对局部 String&str —— 这会导致借用指向超出作用域的值(编译器错误)。
  • 常见陷阱:在同一作用域内同时持有不可变借用与可变借用(例如在遍历同时修改容器)会被编译器拒绝。
  • 借用检查器规则总结
    • 规则1:任何借用必须在其引用的值的生命周期内有效。
    • 规则2:可以同时存在多个不可变借用 (&T)。
    • 规则3:不能同时存在可变借用 (&mut T) 和任何其他借用。
    let mut s = String::from("hello");
    let r1 = &s;        // 不可变借用
    let r2 = &s;        // 另一个不可变借用 - 允许
    // let r3 = &mut s; // 错误:不能同时有可变借用
    println!("{} {}", r1, r2);
    // r1 和 r2 在这里不再使用
    let r3 = &mut s;    // 现在允许 - 之前的借用已结束

6. 结构体与枚举

  • 结构体
    • 命名结构体:
      struct Rectangle {
          width: u32,
          height: u32,
      }
      let rect = Rectangle { width: 30, height: 50 };
    • 元组结构体:
      struct Color(u8, u8, u8);
      let black = Color(0, 0, 0);
    • 方法:使用 impl 定义。
      impl Rectangle {
          fn area(&self) -> u32 {
              self.width * self.height
          }
      }
  • 枚举:支持复杂数据结构,常与 match 结合。
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
    }

常见场景与注意事项:

  • 枚举用于状态机建模(例如连接状态、任务状态),结合 match 可保证覆盖所有分支。
  • DTO 与业务实体:接口层返回轻量 DTO,避免将内部可变状态或锁直接暴露给调用者。
  • impl 方法签名:不可变操作使用 &self,需要修改使用 &mut self,并根据可能的错误返回 Result
  • 关联函数(不取 self 参数):
    impl Rectangle {
        fn square(size: u32) -> Self {
            Self { width: size, height: size }
        }
    }
    let sq = Rectangle::square(10);
  • 枚举的常见模式
    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));

7. 模式匹配

  • match:穷尽匹配,必须覆盖所有情况。
    let num = Some(4);
    match num {
        Some(x) if x % 2 == 0 => println!("Even: {}", x),
        Some(x) => println!("Odd: {}", x),
        None => println!("None"),
    }
  • if let:简化单一模式匹配。
    if let Some(x) = num {
        println!("Value: {}", x);
    }

常见场景与注意事项:

  • 使用 if letwhile let 处理单一分支或逐项解析,代码更简洁。
  • 在匹配复杂结构时使用解构({ x, y, .. }(_, _, tail)),并用 _/.. 忽略不必要字段。
  • 当有默认分支时尽量显式写出 _ => {},以便未来扩展时不遗漏处理。
  • 守卫条件(match guards):
    let num = Some(4);
    match num {
        Some(x) if x < 5 => println!("小于5: {}", x),
        Some(x) => println!("大于等于5: {}", x),
        None => println!("没有值"),
    }
  • 绑定模式
    let value = Some(10);
    if let Some(x) = value {
        println!("值是: {}", x);
    } else {
        println!("没有值");
    }
  • while let 循环
    let mut stack = vec![1, 2, 3];
    while let Some(top) = stack.pop() {
        println!("弹出: {}", top);
    }

8. 错误处理

  • Option:处理可能为空的值 (Some(T)None)。
    let opt: Option<i32> = Some(5);
    let value = opt.unwrap_or(0); // 解包或提供默认值
  • Result:处理可能出错的操作 (Ok(T)Err(E))。
    fn divide(a: i32, b: i32) -> Result<i32, String> {
        if b == 0 {
            Err(String::from("Divide by zero"))
        } else {
            Ok(a / b)
        }
    }
    let result = divide(10, 2).expect("Division failed"); // 成功返回 5
  • ? 运算符:简化错误传播。
    fn try_divide() -> Result<i32, String> {
        let result = divide(10, 0)?;
        Ok(result)
    }

常见场景与注意事项:

  • 传播错误:使用 ? 链式传播并在上层提供上下文(结合 thiserror / anyhow / eyre 等库)。
  • 错误类型设计:库内使用精确错误类型(enum Error),应用层可以用 anyhow::Error 简化处理。
  • Option vs Result:仅”可能不存在”用 Option,若需错误信息使用 Result
  • 使用 map_err, with_context 来附带额外上下文,便于排查问题。
  • 组合子方法
    let opt1: Option<i32> = Some(5);
    let opt2: Option<i32> = Some(10);
    let sum = opt1.and_then(|x| opt2.map(|y| x + y)); // Some(15)
    
    let result: Result<i32, &str> = Ok(5);
    let doubled = result.map(|x| x * 2); // Ok(10)
  • 错误类型转换
    fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
        s.parse::<i32>()
    }
    
    fn double_number(s: &str) -> Result<i32, String> {
        parse_number(s)
            .map(|n| n * 2)
            .map_err(|e| format!("解析失败: {}", e))
    }

9. 模块系统

  • 使用 mod 定义模块,pub 控制可见性,use 引入路径。
    mod front_of_house {
        pub mod hosting {
            pub fn add_to_waitlist() {}
        }
    }
    use crate::front_of_house::hosting;
    hosting::add_to_waitlist();
  • 文件系统模块:mod 声明会加载同名文件或目录中的代码。

常见场景与注意事项:

  • 目录布局:在库 crate 中把公共 API 暴露在 lib.rs 顶层,内部实现放子模块并只 pub 出必要接口。
  • re-export:使用 pub use 聚合导出,简化外部调用路径(例如 pub use crate::module::Type)。
  • 避免循环依赖:通过拆分模块、提取 trait 或把共享接口移动到顶层模块来解决。
  • 模块路径系统
    // 绝对路径
    crate::front_of_house::hosting::add_to_waitlist();
    // 相对路径
    front_of_house::hosting::add_to_waitlist();
    // 使用 super 访问父模块
    super::some_function();
  • 使用 as 关键字重命名
    use std::collections::HashMap as Map;
    let mut map = Map::new();
  • 通配符导入(谨慎使用):
    use std::collections::*;

10. 集合类型

  • Vec:动态数组。
    let mut v = Vec::new();
    v.push(1);
  • HashMap:键值对集合。
    use std::collections::HashMap;
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);

常见场景与注意事项:

  • Vec 性能:当已知大小时使用 Vec::with_capacity(n) 预分配以避免重复分配。
  • HashMap 常用技巧:使用 entry API(or_insert, or_insert_with)减少查找与初始化重复。
  • 并发集合:在多线程共享时可用 Arc<Mutex<HashMap<...>>> 或使用 dashmap 等并发数据结构减少锁竞争。
  • 其他常用集合
    use std::collections::{HashSet, BTreeMap, BTreeSet, VecDeque, BinaryHeap};
    
    // HashSet - 无序集合
    let mut set = HashSet::new();
    set.insert(1);
    set.insert(2);
    
    // VecDeque - 双端队列
    let mut deque = VecDeque::new();
    deque.push_front(1);
    deque.push_back(2);
    
    // BinaryHeap - 二叉堆(优先队列)
    let mut heap = BinaryHeap::new();
    heap.push(3);
    heap.push(1);
    heap.push(2);
    println!("{}", heap.peek().unwrap()); // 3
  • 迭代器适配器
    let v = vec![1, 2, 3, 4, 5];
    let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
    let evens: Vec<&i32> = v.iter().filter(|x| *x % 2 == 0).collect();

11. 泛型与 trait

  • 泛型:提高代码复用性。
    fn largest<T: PartialOrd>(list: &[T]) -> &T {
        &list[0]
    }
  • trait:定义共享行为,类似接口。
    trait Summary {
        fn summarize(&self) -> String;
    }
    struct Article {
        content: String,
    }
    impl Summary for Article {
        fn summarize(&self) -> String {
            format!("Article: {}", self.content)
        }
    }

常见场景与注意事项:

  • 泛型约束:当约束复杂时使用 where 语句提高可读性。
  • Trait 对象:需要在运行时动态分派时使用 Box<dyn Trait>,注意 object-safe 规则(某些 trait 无法作为 trait 对象)。
  • 为通用库设计泛型接口时小心二次编译膨胀(code bloat),并考虑为常见类型提供专门实现。
  • 关联类型
    trait Container {
        type Item;
        fn get(&self, index: usize) -> Self::Item;
    }
    
    struct VecContainer<T> {
        items: Vec<T>,
    }
    
    impl<T: Clone> Container for VecContainer<T> {
        type Item = T;
        fn get(&self, index: usize) -> Self::Item {
            self.items[index].clone()
        }
    }
  • 默认实现
    trait Summary {
        fn summarize(&self) -> String {
            String::from("(Read more...)")
        }
    }

12. 生命周期

  • 确保引用有效,生命周期注解用 'a 表示。
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
        if x.len() > y.len() { x } else { y }
    }

常见场景与注意事项:

  • 不要返回对局部变量的引用(会导致编译错误)。
  • 当生命周期签名复杂时,考虑返回拥有所有权的类型(如 String)或使用 Arc/Cow 来简化 API。
  • 在接口设计中尽量让调用方持有所有权或明确借用关系,减少不必要的生命周期注解暴露。
  • 生命周期省略规则(Lifetime Elision):
    Rust 编译器会自动应用三条规则来省略生命周期注解:
    1. 每个引用参数都有自己的生命周期参数
    2. 如果只有一个输入生命周期参数,它被赋予所有输出生命周期参数
    3. 如果有多个输入生命周期参数,但其中一个时 &self&mut self,那么 self 的生命周期被赋予所有输出生命周期参数
    // 编译器可以自动推断,无需显式生命周期注解
    fn first_word(s: &str) -> &str {
        let bytes = s.as_bytes();
        for (i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
                return &s[0..i];
            }
        }
        &s[..]
    }
  • 静态生命周期
    let s: &'static str = "I have a static lifetime.";

13. 并发

  • 线程:使用 std::thread
    use std::thread;
    let handle = thread::spawn(|| {
        println!("Running in a thread!");
    });
    handle.join().unwrap();
  • 消息传递:通过通道 (mpsc)。
    use std::sync::mpsc;
    let (tx, rx) = mpsc::channel();
    tx.send(42).unwrap();
    println!("Received: {}", rx.recv().unwrap());
  • 共享状态:使用 MutexArc.
    use std::sync::{Arc, Mutex};
    let counter = Arc::new(Mutex::new(0));

常见场景与注意事项:

  • 任务划分:IO 密集型使用异步模型(tokio),CPU 密集型使用线程池或 spawn_blocking
  • 共享计数器:若只是计数,优先使用原子类型(AtomicUsize)以减少锁开销。
  • 通道模式:mpsc 适合生产者-消费者;对性能敏感场景可用 crossbeam 提供更高性能的通道。
  • 避免死锁:尽量缩小锁的持有范围,并避免在持锁期间调用外部代码或再加锁。
  • 读写锁(RwLock):
    use std::sync::RwLock;
    
    let lock = RwLock::new(5);
    
    // 多个读者
    {
        let r1 = lock.read().unwrap();
        let r2 = lock.read().unwrap();
        println!("{}", *r1);
    } // 读锁在这里释放
    
    // 单个写者
    {
        let mut w = lock.write().unwrap();
        *w += 1;
    }
  • 条件变量(Condvar):
    use std::sync::{Arc, Mutex, Condvar};
    
    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pair2 = Arc::clone(&pair);
    
    std::thread::spawn(move || {
        let (lock, cvar) = &*pair2;
        let mut started = lock.lock().unwrap();
        *started = true;
        cvar.notify_one();
    });
    
    let (lock, cvar) = &*pair;
    let mut started = lock.lock().unwrap();
    while !*started {
        started = cvar.wait(started).unwrap();
    }

14. 常用宏

  • println!, format!:格式化输出。
  • vec!:快速创建向量。
  • panic!:触发程序崩溃。
  • todo!:标记未实现代码。
    let v = vec![1, 2, 3];
    println!("{:?}", v);

常见场景与注意事项:

  • 调试:使用 dbg!(expr) 快速打印表达式及其位置,便于排查。
  • 宏滥用:宏能生成重复代码,但会增加调试难度;如果函数或 trait 已能满足需求,优先使用函数/trait。
  • 自定义宏:在需要元编程或减少重复模板化代码(如注册、绑定)时使用,但注意错误消息友好性。
  • 格式化宏详解
    let name = "Rust";
    let version = 1.70;
    
    println!("语言: {}, 版本: {}", name, version);  // 基本格式化
    println!("二进制: {:b}", 10);                  // 1010
    println!("十六进制: {:x}", 255);               // ff
    println!("指针: {:p}", &name);                 // 内存地址
    println!("调试: {:?}", (1, 2, 3));             // 调试格式
  • 断言宏
    assert!(true);
    assert_eq!(2 + 2, 4);
    assert_ne!(1, 2);
    debug_assert!(some_condition);  // 仅在 debug 模式下检查

15. 智能指针

Rust 中的“智能指针”不仅仅是指针,它们还可以提供额外的所有权语义与运行时行为。常见的智能指针包括 Box<T>Rc<T>Arc<T>Cell<T>RefCell<T>。下面分别介绍它们的用途、约束与示例。

  • Box:将值放到堆上,最简单的智能指针。常用于:

    • 在编译时大小不确定但需要在堆上存储(如递归数据结构)。
    • 明确将大对象移到堆以减小栈开销。
    let b = Box::new(5);
    let boxed_array: Box<[i32]> = Box::new([1, 2, 3]);
  • 常见场景与注意事项:

    • 用于实现递归数据结构(例如自定义链表和树),把子节点放在 Box 中以满足大小已知要求。
    • 在 API 需要明确传递堆上数据的所有权时使用 Box<T>
  • Rc(Reference Counted):引用计数智能指针,适用于单线程场景,允许多个所有者共享同一块数据。Rc<T> 在克隆时会增加引用计数,直到所有者都被丢弃,数据才会被释放。注意:Rc<T> 不能用于跨线程共享(不是 Send/Sync)。

    • 使用场景:树结构、图结构或需要多个所有者但只在单线程中的共享数据。
    use std::rc::Rc;
    
    let a = Rc::new(String::from("hello"));
    let b = Rc::clone(&a); // 增加引用计数
    println!("count = {}", Rc::strong_count(&a)); // 2
  • 常见场景与注意事项:

    • RefCell<T> 搭配使用:Rc<RefCell<T>> 常用于单线程内既需多所有者又需内部可变性的结构(如可变树)。
    • 注意循环引用会导致内存泄漏;若需要父指针请使用 Weak 断开循环。
  • Arc(Atomic Reference Counted):与 Rc<T> 类似,但使用原子操作维护引用计数,因此可以安全地在多线程间共享(实现了 SendSync,前提是 T 本身满足相应约束)。相比 RcArc 的计数开销更高,但可用于并发场景。

    use std::sync::Arc;
    use std::thread;
    
    let data = Arc::new(vec![1, 2, 3]);
    let mut handles = Vec::new();
    for _ in 0..3 {
        let d = Arc::clone(&data);
        handles.push(thread::spawn(move || {
            println!("{:?}", d);
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
  • 常见场景与注意事项:

    • 在多线程共享读/写场景中通常与 MutexRwLock 结合(Arc<Mutex<T>>Arc<RwLock<T>>)。
    • 当仅需要只读共享时,优先使用 Arc<T>(且 T 为不可变或只读)以减少锁开销。
  • Cell与 RefCell(内部可变性 / Interior Mutability):它们允许在外部不可变绑定下对内部数据进行修改(绕开编译时的借用规则,但在运行时仍保证安全性)。

    • Cell<T>:提供按值存取(Copy 或可移动类型),常用方法有 get / set / replace。不提供引用借用;适合小型标量或实现缓存的场景。
      use std::cell::Cell;
      
      let c = Cell::new(5);
      c.set(10);
      let v = c.get();
    • RefCell<T>:在单线程中用于在运行时进行借用检查。通过 borrow()(不可变借用,返回 Ref<T>)和 borrow_mut()(可变借用,返回 RefMut<T>)实现。当出现违反借用规则的情况(例如同时存在一个 borrow() 和一个 borrow_mut()),会在运行时 panic。
      • 常见搭配:Rc<RefCell<T>>,用于在单线程中既需要多个所有者(Rc)又需要在任意时间点可变访问(RefCell)的场景,例如构建可变的树/图。
      use std::rc::Rc;
      use std::cell::RefCell;
      
      #[derive(Debug)]
      struct Node {
          value: i32,
          children: Vec<Rc<RefCell<Node>>>,
      }
      
      let leaf = Rc::new(RefCell::new(Node { value: 3, children: vec![] }));
      let branch = Rc::new(RefCell::new(Node { value: 5, children: vec![Rc::clone(&leaf)] }));
      // 修改 leaf 的值,即使它被多个 Rc 所拥有
      leaf.borrow_mut().value = 10;
      println!("{:?}", branch);
  • 设计和使用要点(实践建议):

    • 优先使用编译时借用规则(& / &mut)。只有在确实需要多个所有者或内部可变性时,才使用智能指针。
    • 使用 Rc<T> 时注意循环引用(reference cycle)会导致内存泄漏,因为引用计数永远无法降为 0。若确实需要父子双向引用,可用 Weak<T>(弱引用)来打破循环:
      use std::rc::{Rc, Weak};
      use std::cell::RefCell;
      
      struct Node { parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>> }
    • RefCell<T> 在运行时检查借用,错误会导致 panic。若需要在运行时避免 panic,应在逻辑上保证借用规则或使用更合适的并发原语(如 Mutex)。
    • 在多线程场景下,使用 Arc<T> + Mutex<T>Arc<RwLock<T>> 来实现线程安全的可变共享数据,而不是 Rc / RefCell
    • Cow(写时克隆)
      use std::borrow::Cow;
      
      fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> {
          if input.contains(' ') {
              Cow::Owned(input.replace(' ', ""))
          } else {
              Cow::Borrowed(input)
          }
      }
    • Pin 和 Unpin(高级特性):
      用于确保某些类型在内存中的位置不会被移动,主要用于异步编程。
      use std::pin::Pin;
      
      let mut val = 5;
      let pinned = Pin::new(&mut val);

小结:

  • Box:将值放到堆上(所有权明确)。
  • Rc:单线程引用计数,多所有者(只读或配合内部可变性)。
  • Arc:跨线程的原子引用计数。
  • Cell / RefCell:在单线程下提供“内部可变性”,在运行时检查借用规则(RefCell 会 panic 当规则被违反)。
  • Weak:与 Rc/Arc 配合使用,避免循环引用,不能增加引用计数但可以升级为强引用(若仍存活)。

16. 迭代器与闭包

  • 迭代器(Iterator):Rust 的 for 循环本质就是迭代器语法糖。任何实现 Iterator 的类型都可以用 for 遍历。
    let v = vec![1, 2, 3];
    let mut iter = v.iter();   // 产生 &i32
    assert_eq!(iter.next(), Some(&1));
    • 常用适配器:mapfiltercollectfoldenumeratezip 等,均返回新迭代器,实现“零成本抽象”。
    let sum: i32 = (1..=10).filter(|&x| x % 2 == 0).sum();
    • 自定义迭代器:实现 Iterator 关联类型 Itemnext 方法即可。
  • 闭包(Closure):匿名函数,可捕获环境变量,语法 |param| body
    let factor = 2;
    let multiply = |x| x * factor;
    assert_eq!(multiply(5), 10);
    • 闭包会根据捕获方式自动实现 FnFnMutFnOnce 三种 trait,决定可被调用多次/可变/一次性。
    • 常与迭代器搭配,实现链式数据处理:
    let v: Vec<i32> = vec![1, 2, 3]
        .into_iter()
        .map(|x| x * x)
        .collect();

常见场景与注意事项:

  • 流式处理:用迭代器链实现数据转换流水线,既简洁又高效(惰性求值)。
  • 惰性计算:迭代器直到被消费(如 collectsumfor)才执行,适合处理大数据/流式数据。
  • 捕获语义:闭包默认按需借用或移动捕获变量;遇到所有权冲突时可使用 move 强制移动捕获。
  • 迭代器消费者
    let v = vec![1, 2, 3, 4, 5];
    
    // 消费迭代器并产生结果
    let sum: i32 = v.iter().sum();
    let product: i32 = v.iter().product();
    let max: Option<&i32> = v.iter().max();
    let min: Option<&i32> = v.iter().min();
    
    // 检查条件
    let all_positive = v.iter().all(|&x| x > 0);
    let any_large = v.iter().any(|&x| x > 3);
  • 闭包 trait 详解
    • Fn:可以多次调用,不获取所有权
    • FnMut:可以多次调用,可能修改环境
    • FnOnce:只能调用一次,获取所有权
    fn apply<F>(f: F) -> i32
    where F: Fn(i32) -> i32 {
        f(5)
    }
    
    let x = 10;
    let add_x = |y| y + x;  // 实现 Fn
    println!("{}", apply(add_x)); // 15

17. 属性与条件编译

  • 属性(Attribute):以 #[...] 形式给编译器提供元数据。
    #[derive(Debug, Clone)]
    struct Point { x: f64, y: f64 }
    
    #[inline(always)]
    fn fast_add(a: i32, b: i32) -> i32 { a + b }
  • 条件编译:使用 #[cfg(...)] 按平台/特性包含或排除代码。
    #[cfg(target_os = "linux")]
    fn on_linux() { println!("Running on Linux"); }
    
    #[cfg(feature = "serde")]
    impl serde::Serialize for Config { /* ... */ }
    • Cargo.toml 中通过 [features] 定义可选特性,实现“按需编译”。

常见场景与注意事项:

  • 平台相关分支:用 #[cfg(...)] 为不同平台实现差异化逻辑,保持同一 API。
  • 可选依赖:在 Cargo.toml 使用 feature 将可选功能隔离,减少默认依赖体积(例如 serde)。
  • 测试特定代码:用 #[cfg(test)] 隔离测试辅助代码,避免污染生产可执行文件。
  • 自定义属性
    // 自定义条件编译属性
    #[cfg_attr(feature = "nightly", feature(specialization))]
    fn specialized_function() {}
    
    // 标记废弃
    #[deprecated(since = "1.0.0", note = "请使用 new_function 代替")]
    fn old_function() {}
    
    // 允许特定警告
    #[allow(dead_code)]
    fn unused_function() {}
  • 内联属性详解
    #[inline]          // 建议内联
    #[inline(always)]  // 总是内联
    #[inline(never)]   // 从不内联
    fn example() {}

18. 异步编程

  • async/.await:Rust 使用零成本抽象的异步模型,基于 Future trait。
    use tokio; // 常用运行时
    
    #[tokio::main]
    async fn main() {
        let task = async { 42 };
        let val = task.await;
        println!("{}", val);
    }
    • async fn 返回实现 Future 的匿名类型;.await 挂起当前任务,让出线程。
    • 并发原语:join!(并发等待多个 Future)、select!(多路复用)、spawn(任务调度)。
    • 注意:异步块默认单线程调度,CPU 密集任务应使用 spawn_blocking 或专用线程池。

常见场景与注意事项:

  • 运行时选择:在应用层使用 tokio 或 async-std;库层尽量不直接依赖特定运行时以提高可组合性(使用 futures 兼容 trait)。
  • IO 密集型服务:HTTP、数据库访问、网络爬虫等使用 async 可以获得高并发吞吐。
  • 阻塞操作:避免在 async 任务中直接执行阻塞 IO 或 CPU 密集任务,使用 spawn_blocking 或单独线程池。
  • Future trait 基础
    use std::future::Future;
    use std::pin::Pin;
    use std::task::{Context, Poll};
    
    struct MyFuture {
        count: u32,
    }
    
    impl Future for MyFuture {
        type Output = u32;
        
        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
            if self.count > 0 {
                self.count -= 1;
                Poll::Pending
            } else {
                Poll::Ready(42)
            }
        }
    }
  • 异步 trait(实验性):
    #[async_trait::async_trait]  // 需要 async-trait crate
    trait AsyncService {
        async fn process(&self, data: &str) -> String;
    }

19. 宏系统

  • 声明宏(macro_rules!):基于模式匹配生成代码。
    macro_rules! say_hello {
        () => { println!("Hello!"); };
        ($name:expr) => { println!("Hello, {}!", $name); };
    }
    say_hello!();
    say_hello!("Rust");
  • 过程宏(Procedural Macro):分为 #[derive(...)]#[attribute]#[function_like] 三种,通过 proc_macro crate 实现,用于代码生成、DSL 等高级场景。
    • 示例:自定义 #[derive(MyTrait)] 为结构体自动生成实现。

常见场景与注意事项:

  • 使用 derive 为常见 trait(Debug, Clone, Serialize, Deserialize)自动生成实现,减少模板代码。
  • 过程宏适合生成重复模板代码或实现 DSL,但编写与调试更复杂,务必提供清晰的错误信息与文档。
  • 宏会使错误信息与展开代码更难调试,原则上优先用函数、trait、泛型实现可行的逻辑。
  • 过程宏示例
    // derive 宏
    #[derive(Debug, Clone, PartialEq)]
    struct Point { x: i32, y: i32 }
    
    // attribute 宏
    #[tokio::main]
    async fn main() {}
    
    // function-like 宏
    let v = vec![1, 2, 3];  // vec! 是声明宏
  • 宏的卫生性(Hygiene):
    Rust 宏是卫生的,意味着它们不会意外捕获外部作用域的标识符。
    macro_rules! make_function {
        () => {
            fn helper() -> i32 { 42 }
        };
    }
    
    make_function!();
    // helper(); // 错误:helper 不在作用域中

20. 工具链与生态

  • Cargo:Rust 的构建系统与包管理器。
    • cargo newbuildruntestcheckclippyfmt 等常用命令。
    • Cargo.toml 声明依赖、特性、工作区;Cargo.lock 锁定版本,确保可重复构建。
  • crates.io:官方包仓库,发布与获取开源库。
  • rustup:工具链管理器,支持安装/切换 stable/beta/nightly 版本,以及交叉编译目标。
  • 常用开发工具
    • rustfmt:自动格式化代码。
    • clippy:Lint 工具,提供 500+ 规则检查。
    • rust-analyzer:IDE 语言服务器,支持 VS Code、Vim、Emacs 等。
  • 测试体系
    • 单元测试:与源码同文件,置于 #[cfg(test)] mod tests
    • 集成测试:放在 tests/ 目录,独立可执行。
    • 文档测试:在 /// 注释中写 ``` 代码块,cargo test 自动运行。
  • 文档生成cargo doc --open 生成并打开 HTML 文档,支持 Markdown 与交叉链接。
  • 性能调优
    • cargo bench:使用 #[bench] 进行基准测试(需 nightly)。
    • perf/flamegraph:采样分析;cargo profiler 集成火焰图。
    • #[inline]#[cold]std::hint::unreachable_unchecked() 等微优化提示。
  • 安全与审计
    • cargo audit:检查依赖已知漏洞。
    • cargo geiger:统计 unsafe 代码占比。
    • cargo deny:强制许可证/漏洞/重复依赖策略。

常见场景与注意事项:

  • CI 集成:在 CI 中强制运行 cargo fmt -- --checkcargo clippy -- -D warningscargo test 保持代码质量。
  • 依赖审计:使用 cargo audit 定期扫描依赖漏洞,尤其在生产部署前。
  • 本地开发:使用 rustup 管理工具链并在项目中记录所需的 toolchain(rust-toolchain.toml),保证团队一致性。
  • 性能剖析:在性能热点使用采样工具(perf + flamegraph)而非盲目微优化;仅在确认热点后才用 #[inline] 等属性。
  • 工作空间(Workspace)
    # Cargo.toml
    [workspace]
    members = [
        "crate-a",
        "crate-b",
    ]
  • 发布配置优化
    # Cargo.toml
    [profile.release]
    lto = true          # 启用链接时优化
    codegen-units = 1   # 单代码生成单元
    panic = "abort"     # 移除 panic 处理代码
  • 常用开发命令
    cargo tree          # 查看依赖树
    cargo expand        # 查看宏展开结果
    cargo metadata      # 获取项目元数据
    cargo fix           # 自动修复编译警告
    cargo miri          # 运行 Miri 检测未定义行为
  • 交叉编译
    # 安装目标平台
    rustup target add x86_64-unknown-linux-musl
    
    # 为特定目标构建
    cargo build --target x86_64-unknown-linux-musl
  • 自定义构建脚本
    // build.rs
    fn main() {
        // 告诉 Cargo 在重新运行构建脚本时,如果 src/hello.c 改变则重新运行
        println!("cargo:rerun-if-changed=src/hello.c");
        
        // 使用 cc crate 编译 C 代码
        cc::Build::new()
            .file("src/hello.c")
            .compile("hello");
    }
  • 环境变量
    // 在编译时获取环境变量
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    const NAME: &str = env!("CARGO_PKG_NAME");
    
    // 在运行时获取
    if let Ok(path) = std::env::var("PATH") {
        println!("PATH: {}", path);
    }