为什么说 Rust 是 C++...

为什么说 Rust 是 C++ 的终结者?

前言:一个 C++ 老兵的觉醒

在 C++ 的世界里摸爬滚打十年,我见过太多因为一个野指针导致的线上崩溃,也调试过无数个因为 double free 引发的诡异 bug。每次在凌晨三点盯着 Valgrind 的输出时,我都在想:为什么在 2025 年,我们还在用一门需要程序员手动管理内存的语言?

直到我遇到了 Rust。

C++ 的原罪:运行时才暴露的内存灾难

经典场景:Use-After-Free

cpp 复制代码
// C++: 编译通过,运行时爆炸
#include <iostream>
#include <vector>

class User {
public:
    std::string name;
    User(std::string n) : name(n) {}
};

int main() {
    std::vector<User*> users;
    
    {
        User* user = new User("Alice");
        users.push_back(user);
        delete user;  // 手动释放内存
    }
    
    // 灾难:访问已释放的内存
    std::cout << users[0]->name << std::endl;  // UB: 未定义行为
    
    return 0;
}

这段代码能编译通过,但运行时会触发 Use-After-Free。更可怕的是,在某些情况下它可能"正常运行",直到某天在生产环境突然崩溃。

数据竞争:多线程的噩梦

cpp 复制代码
// C++: 编译通过,运行时数据竞争
#include <thread>
#include <vector>

int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        counter++;  // 非原子操作,存在数据竞争
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(increment);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << counter << std::endl;  // 结果不确定
    return 0;
}

C++ 编译器对此无能为力,只能依赖程序员的"自觉"加锁。

Rust 的革命:编译期的铁幕防线

所有权系统:一个值,一个主人

Rust 的核心哲学:每个值在任意时刻只能有一个所有者。当所有者离开作用域,值自动销毁,内存自动释放。

rust 复制代码
// Rust: 编译器直接拒绝
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // 所有权转移给 s2
    
    println!("{}", s1);  // ❌ 编译错误:value borrowed here after move
}

编译器输出:

rust 复制代码
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:20
  |
3 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`
4 |     let s2 = s1;
  |              -- value moved here
5 |     println!("{}", s1);
  |                    ^^ value borrowed here after move

这不是警告,是编译错误。你的代码根本无法通过编译。

借用检查器:可变性排他原则

Rust 强制执行两条铁律:

  1. 同一时刻,要么有多个不可变引用,要么只有一个可变引用
  2. 引用的生命周期不能超过被引用值的生命周期
rust 复制代码
// Rust: 借用检查器的严格执法
fn main() {
    let mut data = vec![1, 2, 3];
    
    let r1 = &data;           // 不可变借用
    let r2 = &data;           // 再次不可变借用,OK
    let r3 = &mut data;       // ❌ 编译错误:不能在不可变借用存在时创建可变借用
    
    println!("{:?}", r1);
}

编译器输出:

ini 复制代码
error[E0502]: cannot borrow `data` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &data;
  |              ----- immutable borrow occurs here
5 |     let r2 = &data;
6 |     let r3 = &mut data;
  |              ^^^^^^^^^ mutable borrow occurs here
7 |     
8 |     println!("{:?}", r1);
  |                      -- immutable borrow later used here

实战对比:内存泄漏的终结

C++:手动管理的地狱

cpp 复制代码
// C++: 内存泄漏的经典场景
#include <iostream>

class Resource {
public:
    int* data;
    
    Resource() {
        data = new int[1000];
        std::cout << "Resource allocated" << std::endl;
    }
    
    ~Resource() {
        delete[] data;
        std::cout << "Resource freed" << std::endl;
    }
};

void process() {
    Resource* res = new Resource();
    
    if (some_condition()) {
        return;  // 💣 忘记 delete,内存泄漏!
    }
    
    delete res;
}

即使使用 std::unique_ptr,也需要程序员记得使用它:

cpp 复制代码
// C++: 需要程序员"记得"使用智能指针
void process() {
    auto res = std::make_unique<Resource>();  // 需要主动选择
    
    if (some_condition()) {
        return;  // OK,自动释放
    }
}

Rust:编译器强制的 RAII

rust 复制代码
// Rust: 所有权系统自动管理
struct Resource {
    data: Vec<i32>,
}

impl Resource {
    fn new() -> Self {
        println!("Resource allocated");
        Resource {
            data: vec![0; 1000],
        }
    }
}

impl Drop for Resource {
    fn drop(&mut self) {
        println!("Resource freed");
    }
}

fn process() {
    let res = Resource::new();  // 所有权在这里
    
    if some_condition() {
        return;  // ✅ 编译器自动插入 drop 调用
    }
    
    // res 在这里自动 drop
}

关键区别:Rust 中你无法"忘记"释放资源,因为编译器会在所有退出路径上自动插入清理代码。

数据竞争:编译期的终极防御

C++:运行时的定时炸弹

cpp 复制代码
// C++: 编译通过,运行时炸弹
#include <thread>
#include <vector>

struct Counter {
    int value = 0;
    
    void increment() {
        value++;  // 数据竞争
    }
};

int main() {
    Counter counter;
    std::vector<std::thread> threads;
    
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back([&counter]() {
            for (int j = 0; j < 100000; ++j) {
                counter.increment();
            }
        });
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << counter.value << std::endl;  // 结果不确定
    return 0;
}

Rust:Send 和 Sync trait 的守护

rust 复制代码
// Rust: 编译器直接拒绝不安全的并发
use std::thread;

struct Counter {
    value: i32,
}

impl Counter {
    fn increment(&mut self) {
        self.value += 1;
    }
}

fn main() {
    let mut counter = Counter { value: 0 };
    
    let handles: Vec<_> = (0..10)
        .map(|_| {
            thread::spawn(|| {
                for _ in 0..100000 {
                    counter.increment();  // ❌ 编译错误:cannot move out of captured variable
                }
            })
        })
        .collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
}

编译器输出:

vbnet 复制代码
error[E0373]: closure may outlive the current function, but it borrows `counter`, which is owned by the current function

正确的 Rust 实现 :使用 Arc<Mutex<T>>

rust 复制代码
use std::sync::{Arc, Mutex};
use std::thread;

struct Counter {
    value: i32,
}

impl Counter {
    fn increment(&mut self) {
        self.value += 1;
    }
}

fn main() {
    let counter = Arc::new(Mutex::new(Counter { value: 0 }));
    
    let handles: Vec<_> = (0..10)
        .map(|_| {
            let counter = Arc::clone(&counter);
            thread::spawn(move || {
                for _ in 0..100000 {
                    let mut c = counter.lock().unwrap();
                    c.increment();
                }
            })
        })
        .collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    let final_value = counter.lock().unwrap().value;
    println!("Final value: {}", final_value);  // 确定性结果:1000000
}

核心差异

  • C++ 中,你可以"忘记"加锁,编译器不会阻止你
  • Rust 中,如果不使用 Mutex,代码根本无法编译

性能神话的破灭

很多人担心 Rust 的安全检查会带来性能损失。事实是:零成本抽象

所有权和借用检查发生在编译期,运行时没有任何额外开销。生成的机器码与手写的 C++ 代码性能相当,甚至因为编译器能做出更激进的优化而更快。

rust 复制代码
// Rust: 零成本抽象
fn sum(data: &[i32]) -> i32 {
    data.iter().sum()  // 编译后与手写循环性能相同
}

等价的汇编代码与 C++ 的 std::accumulate 完全一致。

结论:C++ 的时代结束了

Rust 不是 C++ 的"改进版",而是对系统编程范式的重新定义

  1. 内存安全不再是程序员的责任,而是编译器的义务
  2. 并发安全不再依赖文档和约定,而是类型系统的保证
  3. 性能和安全不再是二选一,而是同时拥有

当你的 C++ 代码在生产环境因为野指针崩溃时,Rust 程序员已经在编译期就解决了这个问题。

这不是信仰之争,这是工程现实。

欢迎来到 Rust 的世界,这里没有 Segmentation Fault。


作者:一个从 C++ 苦海脱离出来的 Rust 布道师
如果你还在用 newdelete,是时候重新思考你的技术栈了。

相关推荐
楼兰公子17 小时前
buildroot 在编译rust时裁剪平台类型数量的方法
开发语言·后端·rust
Rust研习社1 天前
开源项目里的 deny.toml 是什么?
后端·rust·编程语言
铭毅天下1 天前
当搜索引擎遇上 Rust——深度解读下一代实时搜索引擎 INFINI Pizza
开发语言·后端·搜索引擎·rust
咸甜适中1 天前
rust语言学习笔记Trait之Default(默认值)
笔记·学习·rust
容智信息2 天前
AI Agent(智能体)的输出格式应该从 Markdown 转向 HTML吗?
前端·人工智能·rust·编辑器·html·prompt
Rust研习社2 天前
Rust Clippy 实用指南:写出更优雅、安全的 Rust 代码
后端·rust·编程语言
yangyongdehao302 天前
两天用AI+rust撸了一款本地批量去水印软件,30MB,效果能打
ai作画·rust
nudt_qxx2 天前
NVIDIA 正式开源cuda-oxide!Rust 编写 CUDA 内核新范式!
rust
小杍随笔3 天前
【Rust桌面革命:Tauri×Dioxus——架构对决、实战拆解与2026选型杀招】
开发语言·架构·rust