C++与Rust对比
语言特性与设计哲学对比
概述
C++ 和 Rust 都是系统级编程语言,都能直接操作内存和硬件,都不需要垃圾回收器。但两者的设计哲学截然不同:C++ 追求零开销抽象和向后兼容,信任程序员的能力;Rust 通过所有权系统和借用检查器在编译期保证内存安全,不信任程序员手动管理内存。理解两者的异同,有助于在不同场景下做出合适的技术选择。
为什么需要了解两者的对比?如果你正在为项目选择技术栈,或者想从 C++ 转向 Rust(或反之),了解它们在内存管理、并发模型、生态系统等方面的差异,能帮助你做出明智的决策。两者不是替代关系,而是各有擅长的互补关系。
基础概念
所有权系统:Rust 的核心特性。每个值有且只有一个所有者,当所有者离开作用域时值被自动释放。编译器通过借用检查器确保引用的有效性。
借用检查器:Rust 编译器的一部分,在编译期检查所有引用是否安全。它确保不会同时存在可变引用和不可变引用,也不会有悬垂引用。
生命周期:Rust 中每个引用都有一个生命周期,描述引用有效的范围。编译器通过生命周期标注来验证引用的安全性。
零开销抽象:C++ 和 Rust 共同的设计原则。使用高级抽象不会带来运行时开销,你不需要为没有使用的功能付费。
RAII:资源获取即初始化。C++ 和 Rust 都使用 RAII 模式管理资源,构造时获取资源,析构时释放资源。
快速上手
变量与可变性
// C++:变量默认可变,用 const 标记不可变
int x = 10; // 可变
x = 20; // 允许
const int y = 30; // 不可变
// y = 40; // 编译错误
// Rust:变量默认不可变,用 mut 标记可变
let x = 10; // 不可变
// x = 20; // 编译错误
let mut y = 30; // 可变
y = 40; // 允许
内存管理
// C++:手动管理或使用智能指针
void cppMemoryDemo() {
// 栈上分配,自动释放
int value = 42;
// 堆上分配,手动释放(危险)
int* raw = new int(42);
delete raw; // 忘记 delete 会导致内存泄漏
// 智能指针(推荐)
auto unique = std::make_unique<int>(42); // 独占所有权
auto shared = std::make_shared<int>(42); // 共享所有权(引用计数)
// unique_ptr 离开作用域时自动释放
// shared_ptr 当引用计数为零时自动释放
}
// Rust:所有权系统自动管理
fn rust_memory_demo() {
// 栈上分配
let value = 42;
// 堆上分配,所有权自动管理
let mut unique = Box::new(42); // 独占所有权(类似 unique_ptr)
let shared1 = Rc::new(42); // 共享所有权(类似 shared_ptr)
let shared2 = Rc::clone(&shared1); // 引用计数增加
// Box 离开作用域时自动释放
// Rc 当引用计数为零时自动释放
}
函数与错误处理
// C++:使用异常或返回值
// 方式一:异常
int divide_exception(int a, int b) {
if (b == 0) throw std::runtime_error("除数不能为零");
return a / b;
}
// 方式二:返回值(C++17 的 std::optional)
std::optional<int> divide_optional(int a, int b) {
if (b == 0) return std::nullopt;
return a / b;
}
// Rust:使用 Result 类型(必须处理错误)
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
return Err("除数不能为零".to_string());
}
Ok(a / b)
}
// 调用时必须处理错误
fn main() {
match divide(10, 2) {
Ok(result) => println!("结果: {}", result),
Err(e) => println!("错误: {}", e),
}
// 或者用 ? 操作符传播错误
let result = divide(10, 2)?; // 如果出错,提前返回
}
详细用法
字符串处理
// C++:std::string 和 std::string_view
#include <string>
#include <string_view>
void cppStringDemo() {
// 可变字符串
std::string name = "张三";
name += "你好"; // 拼接
std::cout << name.size() << std::endl; // 长度(字节数,不是字符数)
// 字符串视图(不拥有数据,零拷贝)
std::string_view view = name;
std::string_view sub = view.substr(0, 6); // 子串
// 格式化(C++20)
std::string msg = std::format("你好, {}!", name);
}
// Rust:String 和 &str
fn rust_string_demo() {
// 可变字符串(堆分配)
let mut name = String::from("张三");
name.push_str("你好"); // 拼接
println!("{}", name.len()); // 长度(字节数,UTF-8)
// 字符串切片(不拥有数据,零拷贝)
let view: &str = &name;
let sub: &str = &view[0..6]; // 子串(按字节索引,需注意 UTF-8 边界)
// 格式化
let msg = format!("你好, {}!", name);
}
并发编程
// C++:使用 std::thread 和互斥量
#include <thread>
#include <mutex>
#include <vector>
void cppConcurrencyDemo() {
std::mutex mtx;
int counter = 0;
// 创建多个线程
std::vector<std::thread> threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back([&]() {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁解锁
counter++;
});
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
// counter == 10
// 数据竞争是未定义行为,编译器不会检查
// 以下代码有数据竞争但能编译通过
// int unsafe_counter = 0;
// std::thread t1([&]() { unsafe_counter++; }); // 危险!
// std::thread t2([&]() { unsafe_counter++; }); // 危险!
}
// Rust:使用 std::thread,编译器保证线程安全
use std::sync::{Arc, Mutex};
use std::thread;
fn rust_concurrency_demo() {
// Arc(原子引用计数)+ Mutex(互斥量)
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();
}
// *counter.lock().unwrap() == 10
// 以下代码编译不通过!Rust 阻止了数据竞争
// let mut unsafe_counter = 0;
// let t1 = thread::spawn(|| { unsafe_counter += 1; }); // 编译错误!
// let t2 = thread::spawn(|| { unsafe_counter += 1; }); // 编译错误!
}
泛型与 trait
// C++:模板和概念
#include <concepts>
#include <vector>
#include <algorithm>
// C++20 概念约束
template<typename T>
requires std::integral<T>
T sum(const std::vector<T>& values) {
T result = 0;
for (const auto& v : values) {
result += v;
}
return result;
}
// 编译期错误信息可能很长
// Rust:泛型和 trait
fn sum<T: std::ops::Add<Output = T> + Default + Copy>(values: &[T]) -> T {
let mut result = T::default();
for v in values {
result = result + *v;
}
result
}
// trait 比 C++ 概念更成熟,错误信息更友好
// 编译错误会明确指出哪个 trait 没有实现
枚举与模式匹配
// C++:枚举和 variant
#include <variant>
#include <string>
// 传统枚举
enum class Color { Red, Green, Blue };
// C++17 的 variant(类似 Rust 的枚举)
using Value = std::variant<int, double, std::string>;
void processValue(const Value& v) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << "整数: " << arg << std::endl;
} else if constexpr (std::is_same_v<T, double>) {
std::cout << "浮点: " << arg << std::endl;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "字符串: " << arg << std::endl;
}
}, v);
}
// Rust:枚举和模式匹配(更优雅)
enum Color {
Red,
Green,
Blue,
}
// 枚举可以携带数据
enum Value {
Integer(i32),
Float(f64),
Text(String),
}
fn process_value(v: &Value) {
match v {
Value::Integer(n) => println!("整数: {}", n),
Value::Float(f) => println!("浮点: {}", f),
Value::Text(s) => println!("字符串: {}", s),
}
// match 必须穷举所有变体,否则编译错误
}
常见场景
构建系统对比
# C++:CMake
cmake_minimum_required(VERSION 3.20)
project(MyApp)
find_package(fmt REQUIRED)
add_executable(myapp main.cpp)
target_link_libraries(myapp fmt::fmt)
# Rust:Cargo
[package]
name = "myapp"
version = "0.1.0"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
项目结构对比
# C++ 项目
MyProject/
├── CMakeLists.txt
├── include/
│ └── myproject/
│ └── utils.h
├── src/
│ ├── main.cpp
│ └── utils.cpp
└── tests/
└── test_utils.cpp
# Rust 项目
myproject/
├── Cargo.toml
├── src/
│ ├── main.rs
│ └── utils.rs
└── tests/
└── test_utils.rs
注意事项
学习曲线:Rust 的学习曲线比 C++ 更陡峭,特别是所有权和生命周期概念。但一旦掌握,编译器能帮你避免大量运行时错误。
生态系统:C++ 的生态更成熟,有大量历史积累的库。Rust 的 Cargo 生态更现代,包管理和构建体验更好。
编译速度:C++ 的编译速度通常比 Rust 快(特别是增量编译)。Rust 的借用检查器增加了编译时间。
互操作:C++ 和 Rust 可以通过 FFI(外部函数接口)互相调用。Rust 提供了 C ABI 兼容的接口,可以从 C++ 中调用 Rust 代码。
适用场景:C++ 适合已有大型代码库、游戏开发、嵌入式系统;Rust 适合新项目、系统工具、WebAssembly、安全敏感场景。
进阶用法
C++ 调用 Rust
// Rust 侧:导出 C 兼容函数
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}
// 编译为静态库
// cargo build --release
// 生成 libmyrustlib.a(Linux)或 myrustlib.lib(Windows)
// C++ 侧:声明并调用 Rust 函数
extern "C" {
int rust_add(int a, int b);
}
int main() {
int result = rust_add(3, 5);
std::cout << "结果: " << result << std::endl; // 8
return 0;
}
Rust 调用 C++
// Rust 侧:使用 bindgen 自动生成绑定
// build.rs
fn main() {
cc::Build::new()
.cpp(true)
.file("src/cpp_code.cpp")
.compile("cpp_code");
// 生成 C++ 头文件的 Rust 绑定
let bindings = bindgen::Builder::default()
.header("src/cpp_code.h")
.clang_arg("-xc++")
.generate()
.expect("无法生成绑定");
bindings
.write_to_file(std::path::PathBuf::from("src/bindings.rs"))
.expect("无法写入绑定");
}