为什么要学 Rust

C++ 写多了,总会遇到这些问题:

  • 悬空指针、野指针
  • 内存泄漏
  • 数据竞争(data race)
  • 未定义行为(UB)

Rust 从语言层面就帮你杜绝了这些。它的所有权系统(Ownership)是核心——编译期检查,零运行时开销。

本文用 C++ 对比着讲 Rust,帮你快速上手。

基础类型:差不多

// C++
int x = 42;
double pi = 3.14;
bool flag = true;
char c = 'A';
auto* ptr = new int(42);
// Rust
let x: i32 = 42;
let pi: f64 = 3.14;
let flag: bool = true;
let c: char = 'A';
let mut ptr = Box::new(42);  // 堆分配,Box 就是智能指针

关键区别:

  • Rust 变量默认不可变let mut x 才是可变的
  • i32f64bool 这些名字和 C++ 相似
  • 没有裸指针,但有 *const T*mut T(安全受限)

所有权:核心概念

这是 Rust 和 C++ 最核心的区别。

C++ 的问题

int* create_buffer() {
    int* buffer = new int[100];
    // ... 处理 ...
    return buffer;  // 谁负责释放?
}

void process() {
    int* data = create_buffer();
    // 用完了
    delete[] data;  // 忘了 delete 就内存泄漏
}

Rust 的解决方案:所有权转移

fn create_buffer() -> Vec<i32> {
    let buffer = vec![0; 100];  // Vec 是堆数组,类似 std::vector
    buffer  // 所有权转移给调用方,函数结束后 buffer 不被 drop
}

fn process() {
    let data = create_buffer();
    // 用完了,data 离开作用域,自动释放(drop)
}  // data 在这里被 drop,无需手动管理

Rust 三条规则:

  1. 每个值有且只有一个所有者(Owner)
  2. 值离开作用域时,自动调用 drop(类似析构函数)
  3. 所有权可以转移(Move),转移后原变量失效

Move vs Copy

// 栈上类型:Copy,自动复制
let x: i32 = 42;
let y = x;  // 复制,x 仍然有效
println!("{} {}", x, y);

// 堆上类型:Move,转移所有权
let v1 = vec![1, 2, 3];  // Vec 在堆上
let v2 = v1;              // Move,v1 失效
// println!("{:?}", v1);  // 编译错误!v1 已无效
// C++ 对比:vector 也是 Move 语义
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1);  // v1 失效
// std::cout << v1.size();  // UB 或空

借用(Borrowing):不用所有权的引用

每次都转移所有权太麻烦了,用引用借用一下:

fn print_length(s: &String) {  // & 表示借用,不获取所有权
    println!("Length: {}", s.len());
}  // s 离开作用域,但值不被 drop,因为只是借用

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

借用规则(编译器强制):

  • 要么多个不可变引用 &T
  • 要么一个可变引用 &mut T
  • 不能同时有可变引用和不可变引用

这从根本上避免了数据竞争!

let mut v = vec![1, 2, 3];

let r1 = &v;      // OK
let r2 = &v;      // OK,多个不可变引用可以共存
// let r3 = &mut v; // 错误!有不可变引用时不能可变借用

println!("{:?} {:?}", r1, r2);

结构和枚举:C++ 的 struct/class + enum

Struct

// C++
struct Point {
    int x;
    int y;
};

Point p = {10, 20};
p.x = 30;
struct Point {
    x: i32,
    y: i32,
}

let mut p = Point { x: 10, y: 20 };
p.x = 30;  // 如果没有 mut,这里会报错

Enum:C++ 的 enum 升级版

C++ 的 enum 很弱,Rust 的 enum 强大得多:

// 枚举可以带数据
enum Message {
    Quit,                        // 无数据
    Move { x: i32, y: i32 },    // 匿名结构体
    Write(String),               // 带一个 String
    ChangeColor(i32, i32, i32), // 多个值
}

fn process(msg: Message) {
    match msg {
        Message::Quit => println!("Quit"),
        Message::Move { x, y } => println!("Move to {} {}", x, y),
        Message::Write(text) => println!("Write: {}", text),
        Message::ChangeColor(r, g, b) => println!("Color {} {} {}", r, g, b),
    }
}

这其实就是代数数据类型(ADT),C++ 得用继承 + 虚函数 + visitor 模式才能实现类似效果。

Option:替代 null

C++ 的 null 是万恶之源:

// C++:不知道返回的是不是 null
User* find_user(int id) {
    if (found) return &user;
    else return nullptr;  // 调用方可能忘记检查
}

auto* user = find_user(123);
user->do_something();  // 如果返回 nullptr,UB

Rust 没有 null,用 Option<T>

enum Option<T> {
    Some(T),   // 有值
    None,      // 没有值
}

fn find_user(id: i32) -> Option<User> {
    if found {
        Some(user)
    } else {
        None
    }
}

match find_user(123) {
    Some(user) => user.do_something(),
    None => println!("User not found"),
}

// 或者用 if let
if let Some(user) = find_user(123) {
    user.do_something();
}

编译器强制你处理 None 的情况,想忽略?用 unwrap() 但这只是把问题延迟到运行时。

错误处理:Result 替代异常

C++ 用异常处理错误,问题是你不知道会抛什么异常:

try {
    auto result = do_something();
    result.process();
} catch (const std::exception& e) {
    // 捕获所有异常,但不知道具体是哪种
    log_error(e.what());
} catch (...) {
    // 捕获未知异常...
}

Rust 用 Result<T, E>

enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn read_file(path: &str) -> Result<String, std::io::Error> {
    let mut file = File::open(path)?;  // ? 操作符:错误自动向上返回
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    match read_file("config.toml") {
        Ok(contents) => println!("{}", contents),
        Err(e) => eprintln!("Error: {}", e),
    }
}

? 操作符是灵魂:如果是 Err,立即返回;如果是 Ok,取出值继续。

生命周期:Rust 的 borrow checker 加强版

C++ 的引用需要程序员自己保证有效性:

int& bad_reference() {
    int x = 10;
    return x;  // 悬空引用!离开函数 x 就没了
}

Rust 用生命周期标注显式声明引用有效期:

// 告诉编译器:返回值的生命周期和输入参数相同
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}

大多数时候编译器能自动推断,只有多参数、返回值有引用时才需要标注。

线程安全:C++ 的噩梦,Rust 的强项

C++ 数据竞争:

std::thread t1([&counter] {
    for (int i = 0; i < 1000; ++i) {
        ++counter;  // 数据竞争!三个线程同时读改写
    }
});

Rust 的解决:Send + Sync trait 保证线程安全:

use std::sync::{Arc, Mutex};
use std::thread;

let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
    let counter = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut num = counter.lock().unwrap();
        *num += 1;
    });
    handles.push(handle);
}

for handle in handles {
    handle.join().unwrap();
}

Arc<Mutex<T>>

  • Arc:原子引用计数,多线程共享
  • Mutex:互斥锁,保护数据
  • 锁的 lock() 返回 Result,必须处理

想数据竞争?编译不过。

泛型:比 C++ 模板更安全

// C++ 模板
template<typename T>
T max(T a, T b) {
    return a > b ? a : b;  // 编译期实例化,错误信息常常看不懂
}
// Rust 泛型
fn max<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

PartialOrd 是 trait(类似 C++ concept),约束 T 必须支持比较操作符。缺少约束时,编译期报错清楚明确。

实战对比:写一个简单 HTTP Server

C++(用 libcurl)

#include <curl/curl.h>
#include <iostream>

int main() {
    CURL* curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.example.com/data");
    CURLcode res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
    return 0;
}

Rust(用 reqwest)

use reqwest;

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let resp = reqwest::get("https://api.example.com/data").await?;
    println!("Status: {}", resp.status());
    let body = resp.text().await?;
    println!("Body: {}", body);
    Ok(())
}

关键区别:

  • Rust 版本有编译期保证:错误类型清晰,必须处理
  • C++ 版本依赖返回值检查,容易遗漏
  • Rust 用 async/await 并发模型,比 C++ 的回调简洁得多

C++ → Rust 速查表

C++ Rust 说明
T* ptr &T / &mut T 引用,借用
std::unique_ptr<T> Box<T> 独占所有权
std::shared_ptr<T> Arc<T> 引用计数共享
std::lock_guard Mutex<T> 互斥锁
nullptr Option<T> 空值
throw Result<T, E> 错误处理
class struct / enum 类型定义
异常 Result + ? 错误传播
模板 泛型 + trait 多态

总结

Rust 学什么:

  1. 所有权系统:不用 GC,不用手动 free,编译器保证
  2. 借用规则:从根本上避免数据竞争
  3. Result + Option:告别 null 和异常
  4. 编译器是你的朋友:编译错误信息非常详细,照着改就行

入门曲线比 C++ 陡,但:

  • 没有悬空指针
  • 没有数据竞争
  • 没有未定义行为
  • 性能一样好(零成本抽象)

值得投入。


后续计划写 Rust 异步编程(Tokio)和生命周期详解,敬请期待。