C++ 与 Rust:对比与实践

一篇由浅入深、涵盖概念、原理、源码与实战的对比与学习指南。


目录

  1. 基本概念
  2. 核心原理对比
  3. 语法与源码示例
  4. 内存与并发模型
  5. 实际项目应用案例
  6. 选型与迁移建议

一、基本概念

1.1 C++ 是什么

C++ 是一种多范式、编译型系统编程语言,由 Bjarne Stroustrup 在 1979 年起在 C 语言基础上扩展而来。特点包括:

  • 零成本抽象:高级抽象(类、模板、智能指针)在合理使用下不增加运行时开销。
  • 手动内存管理:可显式 new/delete,也可借助 RAII 与智能指针自动化。
  • 多范式:面向过程、面向对象、泛型、函数式、元编程均可。
  • 标准与生态:ISO 标准驱动,STL 与第三方库丰富,广泛应用于操作系统、游戏、嵌入式、高性能服务等。

1.2 Rust 是什么

Rust 是一种系统级、内存安全、无数据竞争的编程语言,由 Mozilla 发起,2015 年 1.0 稳定。特点包括:

  • 所有权系统:通过所有权、借用、生命周期在编译期消除大部分内存错误与数据竞争。
  • 无垃圾回收:不依赖 GC,通过类型系统与借用检查实现安全与性能兼得。
  • 无畏并发:类型系统保证线程安全,避免数据竞争在编译期被发现。
  • 现代工具链cargo 包管理、rustfmt/clippy 格式化与静态检查,体验统一。

1.3 定位对比(一句话)

维度 C++ Rust
设计目标 性能 + 灵活 + 与 C 兼容 安全 + 性能 + 并发 + 现代工具链
内存安全 需开发者自律与规范 编译器强制保证(所有权/借用)
学习曲线 陡峭(历史包袱、未定义行为) 陡峭(所有权/生命周期概念)
典型场景 遗留系统、游戏、内核、HPC 新系统服务、WebAssembly、CLI、基础设施

二、核心原理对比

2.1 内存管理模型

C++:RAII + 可选智能指针

  • 资源获取即初始化(RAII):构造时获取资源,析构时释放。
  • 可裸指针 + 手动 new/delete,也可用 std::unique_ptrstd::shared_ptr 等减少错误。
  • 编译器不阻止悬垂指针、双重释放、use-after-free,需靠规范与静态分析。

Rust:所有权 + 借用

  • 所有权:每个值有唯一所有者,离开作用域自动 drop,无 GC。
  • 借用:通过引用(&T/&mut T)临时借用,编译期检查引用合法性与可变性。
  • 生命周期:标注引用与谁同寿,避免悬垂引用在编译期报错。

概念对照:

C++ 概念 Rust 近似概念
std::unique_ptr 所有权(默认 move)
std::shared_ptr Rc<T> / Arc<T>
引用 T& 借用 &T / &mut T
析构函数 Drop trait

2.2 类型系统与泛型

C++

  • 模板(Template):编译期多态,可特化、偏特化、SFINAE、C++20 Concept。
  • 类型擦除:std::function、虚函数、void* 等。

Rust

  • 泛型 + Trait:类似「接口」,可默认实现、泛型约束、trait object(动态分发)。
  • 无继承,通过组合与 trait 实现多态。

2.3 错误处理

C++

  • 异常(throw/catch):灵活但可能影响性能与 ABI,部分项目禁用。
  • 错误码、std::optionalstd::expected(C++23)等。

Rust

  • Result<T, E>:强制显式处理错误,? 运算符传播错误。
  • panic! 用于不可恢复错误,类似「断言失败」。

三、语法与源码示例

3.1 你好世界

C++

1
2
3
4
5
6
#include <iostream>

int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}

Rust

1
2
3
fn main() {
println!("Hello, World!");
}

3.2 变量与不可变/可变

C++

1
2
3
const int x = 42;       // 不可变
int y = 10; // 可变
y = 20;

Rust

1
2
3
let x = 42;             // 默认不可变
let mut y = 10; // 可变需显式 mut
y = 20;

3.3 结构体与实现

C++

1
2
3
4
5
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {}
int norm_sq() const { return x * x + y * y; }
};

Rust

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Point {
x: i32,
y: i32,
}

impl Point {
fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
fn norm_sq(&self) -> i32 {
self.x * self.x + self.y * self.y
}
}

3.4 枚举与模式匹配

C++(std::variant + std::visit,C++17)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <variant>
#include <string>

using Number = std::variant<int, double>;
using Message = std::variant<int, double, std::string>;

void process(Message m) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) { /* ... */ }
else if constexpr (std::is_same_v<T, double>) { /* ... */ }
else if constexpr (std::is_same_v<T, std::string>) { /* ... */ }
}, m);
}

Rust

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

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

3.5 错误处理

C++(错误码 + optional)

1
2
3
4
5
6
7
8
9
10
#include <optional>
#include <string>

std::optional<int> parse_int(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
}

Rust

1
2
3
4
5
6
7
8
9
10
fn parse_int(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse()
}

// 使用 ? 传播错误
fn read_and_parse() -> Result<i32, Box<dyn std::error::Error>> {
let s = std::fs::read_to_string("num.txt")?;
let n: i32 = s.trim().parse()?;
Ok(n)
}

3.6 所有权与移动

Rust(核心概念,C++ 无直接对应)

1
2
3
4
5
6
7
8
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权移动到 s2,s1 不再有效
// println!("{}", s1); // 编译错误:value used after move

let s3 = s2.clone(); // 显式克隆,s2 仍有效
println!("{} {}", s2, s3);
}

C++(拷贝/移动语义)

1
2
3
4
5
6
7
#include <string>

int main() {
std::string s1 = "hello";
std::string s2 = std::move(s1); // 移动后 s1 处于有效但未指定状态
std::string s3 = s2; // 拷贝(或拷贝省略)
}

3.7 借用与引用

Rust

1
2
3
4
5
6
7
8
9
fn len(s: &String) -> usize {
s.len()
} // s 的借用结束,不拥有 s,不 drop

fn main() {
let s = String::from("hello");
println!("length: {}", len(&s));
println!("{}", s); // s 仍然有效
}

C++

1
2
3
4
5
6
7
8
9
10
11
#include <string>

size_t len(const std::string& s) {
return s.size();
}

int main() {
std::string s = "hello";
std::cout << "length: " << len(s) << std::endl;
std::cout << s << std::endl;
}

3.8 泛型与约束

C++(模板 + Concept,C++20)

1
2
3
4
5
6
7
8
9
#include <concepts>

template<typename T>
concept Addable = requires(T a, T b) { a + b; };

template<Addable T>
T add(T a, T b) {
return a + b;
}

Rust

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::ops::Add;

fn add<T: Add<Output = T>>(a: T, b: T) -> T {
a + b
}

// 或使用 where
fn add2<T>(a: T, b: T) -> T
where
T: Add<Output = T>,
{
a + b
}

3.9 并发:线程与同步

C++(std::thread + mutex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <thread>
#include <mutex>
#include <iostream>

std::mutex mtx;
int counter = 0;

void increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}

int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "counter = " << counter << std::endl;
}

Rust(Arc + Mutex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc::channel;

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

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

for h in handles {
h.join().unwrap();
}
println!("counter = {}", *counter.lock().unwrap());
}

四、内存与并发模型

4.1 C++ 内存模型简述

  • 堆:new/delete 或智能指针管理。
  • 栈:局部变量,自动管理。
  • 未定义行为(UB):如 use-after-free、双重释放、数据竞争等,标准不保证行为,需避免。

4.2 Rust 内存模型与安全保证

  • 栈上值默认,堆上用 Box<T>Vec<T> 等,由所有权与 drop 管理。
  • 引用规则(借用检查):
    • 任意时刻,要么多个不可变引用,要么一个可变引用。
    • 引用不能比被引用者活得更久(生命周期检查)。
  • 满足规则则无数据竞争、无悬垂引用,在编译期保证。

4.3 生命周期示例(Rust 独有)

1
2
3
4
5
6
7
8
9
10
11
12
13
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

fn main() {
let s1 = String::from("short");
let result;
{
let s2 = String::from("longer");
result = longest(s1.as_str(), s2.as_str());
}
// println!("{}", result); // 错误:result 可能引用已 drop 的 s2
}

五、实际项目应用案例

5.1 C++ 典型应用

  • LLVM/Clang:编译器基础设施,大量模板与手写内存管理。
  • Chromium:浏览器引擎,C++ 主导,复杂多进程与沙箱。
  • Unreal Engine:游戏引擎,深度使用 C++ 与反射/蓝图。
  • TensorFlow/PyTorch 底层:高性能算子与运行时多为 C++。
  • MySQL/PostgreSQL:数据库核心用 C/C++ 实现。

5.2 Rust 典型应用

  • Rust 编译器(rustc):自举,展示大型 Rust 项目与复杂类型系统。
  • Firefox(Servo 组件):浏览器引擎实验,部分已合入 Firefox。
  • Deno:TypeScript/JavaScript 运行时,核心用 Rust 重写。
  • Discord:从 Go 迁移部分服务到 Rust,降低延迟与资源占用。
  • Cloudflare:边缘逻辑、代理、零拷贝解析等用 Rust。
  • AWS Firecracker:轻量级 VMM,用于 Lambda 等,安全与性能关键。
  • 1Password:部分核心组件用 Rust 编写。

5.3 案例:从 C++ 到 Rust 的迁移(概念示例)

某团队将一高并发 TCP 代理从 C++ 迁移到 Rust 的动机与收益(概念性总结):

  • 动机:偶发 use-after-free 与竞态,难以在测试中稳定复现。
  • 做法:用 Rust 重写核心路径,保持协议与配置兼容;通过 FFI 或重写逐步替换。
  • 收益
    • 编译通过即大幅排除内存与数据竞争类 bug。
    • 无 GC 停顿,延迟与 C++ 版本相当或更稳定。
    • cargoclippy 提升协作与代码质量。

5.4 混合使用:Rust 调用 C++ / C++ 调用 Rust

Rust 调用 C 库(C++ 可 extern “C” 暴露)

1
2
3
4
5
6
7
8
9
// 在 Rust 中声明 C 函数
#[link(name = "mylib")]
extern "C" {
fn c_hello();
}

fn main() {
unsafe { c_hello(); }
}

C++ 调用 Rust(Rust 导出 C ABI)

1
2
3
4
5
// lib.rs
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}
1
2
3
4
5
6
// main.cpp
extern "C" int rust_add(int a, int b);

int main() {
return rust_add(1, 2);
}

实际项目常用 bindgen(C/C++ → Rust 绑定)与 cbindgen(Rust → C 头文件)做接口生成。


六、选型与迁移建议

6.1 何时优先 C++

  • 已有大型 C++ 代码库,团队熟悉 C++。
  • 强依赖现有 C++ 生态(如特定游戏引擎、HPC 库)。
  • 需要与 C ABI 或 C++ ABI 深度耦合、对 ABI 稳定性有要求。
  • 对语言标准与编译器行为有历史兼容需求。

6.2 何时优先 Rust

  • 新项目,尤其重视内存安全与并发正确性。
  • 系统软件、网络服务、命令行工具、WebAssembly 等。
  • 希望减少内存与并发类 bug,愿意接受所有权与生命周期的学习成本。
  • 希望统一构建、测试、格式化(cargo)与静态检查(clippy)。

6.3 迁移策略(C++ → Rust)

  1. 新模块用 Rust:在现有 C++ 系统中用 Rust 写新组件,通过 C ABI 或 gRPC/消息队列与 C++ 交互。
  2. 逐层替换:从边界服务或库开始,用 Rust 重写并保持接口一致。
  3. 双轨维护:关键路径先保留 C++,Rust 实现做并行验证与灰度。
  4. 工具:用 bindgen/cbindgencxx 等减少手写 FFI,并补充测试与模糊测试。

小结

主题 C++ 要点 Rust 要点
内存 RAII、智能指针、可手写任意指针 所有权、借用、生命周期、无 GC
安全 依赖规范与工具 类型系统与借用检查强制保证
并发 需谨慎使用锁与原子 类型系统辅助避免数据竞争
抽象 模板、虚函数、Concept 泛型、Trait、trait object
错误处理 异常、错误码、optional Result + ?,显式错误路径
生态与工具 标准委员会、多编译器、多构建系统 Cargo、clippy、rustfmt 统一体验

两者都是系统级语言,C++ 更偏灵活与历史兼容,Rust 更偏安全与可维护。掌握两者有助于在不同场景下做出合适的技术选型,并在需要时进行混合编程或渐进式迁移。


文档中的代码示例均在 C++17/20 与 Rust 2021 下可编译运行,实际项目请以各自代码库与规范为准。