概述

Rust是一种追求内存安全的系统编程语言,无需垃圾回收器或运行时开销。它通过所有权系统(Ownership)、**借用检查器(Borrow Checker)生命周期(Lifetime)**在编译时消除数据竞争、空指针解引用等常见错误。1

Rust的设计目标:

  • 内存安全:编译时消除悬空指针、缓冲区溢出
  • 并发安全:无数据竞争的线程通信
  • 零成本抽象:高性能,可直接替代C++
  • 实用主义:成熟的工具链(cargo)和生态系统

所有权系统

所有权是Rust最核心的概念,每个值有且只有一个所有者,当所有者离开作用域时,值被自动释放。

三条核心规则

  1. Rust中每个值都有一个所有者
  2. 同一时刻只能有一个所有者
  3. 当所有者离开作用域时,值被Dropped(释放)

移动语义

fn main() {
    // String在堆上分配,所有权从s1转移到s2
    let s1 = String::from("hello");
    let s2 = s1;  // s1的所有权移动到s2
    
    // println!("{}", s1);  // 编译错误:s1已无效
    println!("{}", s2);  // 正确:s2拥有所有权
}

对比C++的移动语义:

// C++移动语义(类似但不完全相同)
std::string s1 = "hello";
std::string s2 = std::move(s1);  // s1变为空壳
// std::cout << s1 << std::endl; // 未定义行为!
std::cout << s2 << std::endl;    // "hello"

Copy trait

对于栈上分配的简单类型(如i32),默认实现Copytrait,赋值时直接复制:

fn main() {
    let x = 42;
    let y = x;  // i32实现了Copy,x和y都有效
    
    println!("x = {}, y = {}", x, y);  // 正确
}

实现了Copy的类型

  • 所有整数类型(i32、u64等)
  • 浮点数类型(f32、f64)
  • 布尔类型(bool)
  • 字符类型(char)
  • 元组(仅当所有元素都实现Copy)

克隆(Clone)

对于需要深拷贝的数据,手动调用clone()

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // 显式深拷贝
    
    println!("s1 = {}, s2 = {}", s1, s2);  // 两者都有效
}

借用与引用

借用允许临时使用值而不获取所有权,类似C++的引用,但有更严格的规则。

不可变借用(Immutable Borrow)

fn calculate_length(s: &String) -> usize {
    s.len()
}  // s离开作用域,但不释放String,因为只是借用
 
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);  // &表示借用
    
    println!("'{}'的长度是{}", s1, len);  // s1仍然有效
}

可变借用(Mutable Borrow)

当需要修改数据时,使用可变引用:

fn append_world(s: &mut String) {
    s.push_str(", world");
}
 
fn main() {
    let mut s = String::from("hello");
    append_world(&mut s);
    println!("{}", s);  // "hello, world"
}

可变借用规则

  • 同一作用域内,只能有一个可变引用,或多个不可变引用,不能同时存在
  • 避免数据竞争(data race)
fn main() {
    let mut s = String::from("hello");
    
    // ❌ 编译错误:同时存在可变和不可变引用
    let r1 = &s;
    let r2 = &mut s;  // 错误!
    println!("{} and {}", r1, r2);
    
    // ✅ 正确:按顺序使用
    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);  // r1、r2使用完毕
    
    let r3 = &mut s;  // 现在可以了
    r3.push('!');
}

与C++引用对比

C++Rust区别
T&&T不可变借用
T&(const)&mut TRust可精确区分
T*不推荐使用Rust的借用更安全

生命周期

生命周期是Rust防止悬空引用的机制,编译器确保所有引用始终有效。

生命周期标注

// 编译器无法推断返回引用的生命周期,需要标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

'a是一个生命周期参数,表示:

  • 返回值引用的生命周期与输入引用中较短的那个相同
  • 防止返回指向已释放内存的引用

生命周期省略规则

以下情况Rust自动推断生命周期,无需手动标注:

  1. 每个输入引用获得自己的生命周期
  2. 如果只有一个输入引用,且有输出引用,其生命周期赋给输出引用
  3. 如果有&self&mut self方法,所有生命周期赋给输出引用

结构体中的生命周期

包含引用的结构体必须标注生命周期:

struct ImportantExcerpt<'a> {
    part: &'a str,  // 必须标注
}
 
fn main() {
    let novel = String::from("Call me Ishmael...");
    let first_sentence = novel.split('.').next().unwrap();
    
    let excerpt = ImportantExcerpt {
        part: first_sentence,
    };
}

生命周期推导示例

// C++ 版本(危险!)
struct Person {
    std::string* address;  // 指针可能指向已释放的内存
};
 
Person* findYounger(Person* p1, Person* p2) {
    return p1->age < p2->age ? p1 : p2;  // 可能返回悬空指针
}
 
// Rust 版本(安全)
struct Person {
    name: String,
    age: u32,
}
 
fn find_younger<'a>(p1: &'a Person, p2: &'a Person) -> &'a Person {
    if p1.age < p2.age { p1 } else { p2 }
}

所有权与C++ RAII对比

Rust的所有权系统可以理解为无处不在的RAII2

C++Rust行为
new T / delete TBox::new(T)手动管理(不推荐)
std::unique_ptr<T>Box<T>独占所有权
std::shared_ptr<T>Rc<T> / Arc<T>引用计数(单线程/多线程)
std::weak_ptr<T>Weak<T>弱引用避免循环
裸指针T*&T / &mut T安全借用
use std::sync::Arc;
use std::thread;
 
// 多线程共享数据
fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    
    let data_clone = Arc::clone(&data);
    let handle = thread::spawn(move || {
        println!("线程中: {:?}", data_clone);
    });
    
    println!("主线程: {:?}", data);
    handle.join().unwrap();
}

常见所有权模式

1. 转移所有权

fn consume(s: String) {
    println!("消费: {}", s);
}
 
fn main() {
    let s = String::from("hello");
    consume(s);  // 所有权转移
    // println!("{}", s);  // 编译错误
}

2. 借用使用

fn print_length(s: &String) {
    println!("长度: {}", s.len());
}
 
fn main() {
    let s = String::from("hello");
    print_length(&s);  // 借用,不转移所有权
    println!("{}", s);  // 仍然有效
}

3. 复合类型的所有权

struct Config {
    timeout: u32,
    debug: bool,
}
 
fn update_config(cfg: &mut Config) {
    cfg.timeout = 300;
}
 
fn main() {
    let mut cfg = Config { timeout: 100, debug: false };
    update_config(&mut cfg);
    println!("timeout: {}", cfg.timeout);
}

借用检查器实战技巧

避免常见错误

fn main() {
    let mut v = vec![1, 2, 3];
    
    // ❌ 迭代过程中不能修改集合
    for i in &v {
        v.push(*i + 1);  // 编译错误!
    }
    
    // ✅ 方案1:先收集要添加的元素
    let mut to_add = Vec::new();
    for i in &v {
        to_add.push(*i + 1);
    }
    v.extend(to_add);
    
    // ✅ 方案2:使用索引
    let len = v.len();
    for i in 0..len {
        v.push(v[i] + 1);
    }
}

结构体字段的独立借用

struct Container {
    a: i32,
    b: i32,
}
 
fn main() {
    let mut c = Container { a: 1, b: 2 };
    
    // ✅ 可以同时借用不同字段
    let r_a = &mut c.a;
    let r_b = &mut c.b;
    *r_a += 10;
    *r_b += 20;
    println!("a={}, b={}", c.a, c.b);
}

错误处理与Option

Rust没有null,使用Option<T>表示可能不存在的值:

fn find_user(id: u32) -> Option<&'static str> {
    if id == 1 {
        Some("Alice")
    } else {
        None
    }
}
 
fn main() {
    match find_user(1) {
        Some(name) => println!("找到用户: {}", name),
        None => println!("用户不存在"),
    }
    
    // 使用if let简化
    if let Some(name) = find_user(2) {
        println!("{}", name);
    } else {
        println!("不存在");
    }
}

总结

Rust的所有权系统是革命性的设计:

  • 编译时安全:消除数据竞争、悬空指针等
  • 零运行时开销:无需GC,无需引用计数(除非显式使用)
  • 学习曲线陡峭:但一旦掌握,代码质量显著提升

对于C++开发者,关键是理解:

  • 所有权 ≈ 严格的RAII
  • 借用 ≈ 更安全的引用
  • 生命周期 ≈ 编译时保证的引用有效性

Footnotes

  1. 本段参考Rust for C++ Developers: Complete Migration Guide

  2. 本段参考Rust ownership and C++ RAII comparison