一、深入所有权系统(从C++智能指针到Rust所有权)
内存管理范式对比
C++的抉择:
// 堆分配选择权在程序员
int* raw_ptr = new int(42); // 原始指针
std::unique_ptr uptr = ...; // 独占所有权
std::shared_ptr sptr = ...; // 共享所有权
Rust的强制安全:
let boxed = Box::new(42); // 独占所有权(类似unique_ptr)
let rc = Rc::new(42); // 引用计数(类似shared_ptr)
// 没有原始指针的隐式解引用
关键差异表:
特性 | C++ | Rust |
默认所有权 | 无明确系统 | 编译时强制检查 |
移动语义 | std::move显式声明 | 默认移动 |
空指针 | 允许 | 必须用Option包装 |
线程安全共享 | 需手动同步 | 通过Arc+Mutex保证 |
函数传参的语义差异
C++的参数传递:
void process(std::vector vec) { /* 按值传递 */ }
void process_ref(const std::vector& vec) { /* 常量引用 */ }
std::vector data{1,2,3};
process(std::move(data)); // 移动语义转移所有权
process_ref(data); // 借用观察
Rust的明确所有权:
fn process(vec: Vec) { /* 获得所有权 */ }
fn process_borrow(vec: &[i32]) { /* 只读借用 */ }
let data = vec![1,2,3];
process(data); // 所有权转移,原data失效
// process_borrow(&data); // 错误!data已移动
二、借用检查机制(C++引用的安全进化)
引用规则对比
C++的自由与风险:
int* dangling_ptr() {
int x = 42;
return &x; // 编译器警告但允许
}
const int& dangling_ref() {
return 42; // 返回临时量的引用
}
Rust的编译时守卫:
fn safe_ref() -> &i32 {
let x = 42;
&x // 编译器直接报错:borrowed value does not live long enough
}
借用规则详解
Rust的著名三原则:
- 任意时刻,一个数据要么有:多个不可变引用(&T)或 一个可变引用(&mut T)
- 引用必须始终有效(无悬垂指针)
对应到C++的等效模式:
// Rust的不可变引用 ≈ const &
const std::vector& vec_ref = get_vector();
// Rust的可变引用 ≈ unique_ptr + 独占访问
std::unique_ptr lock;
std::vector& vec_mut = get_mutable_vector();
典型错误场景对比:
// C++允许的危险代码
std::vector data{1,2,3};
auto& first = data[0];
data.push_back(4); // 可能导致迭代器失效
std::cout << first; // 未定义行为
// Rust编译拦截
let mut data = vec![1,2,3];
let first = &data[0];
data.push(4); // 编译错误:不能同时存在可变和不可变借用
println!("{}", first);
三、生命周期标注(C++程序员的理解捷径)
生命周期基础概念
C++中的隐式生命周期管理:
const std::string& longer(
const std::string& a, // 生命周期由调用方保证
const std::string& b
) {
return a.length() > b.length() ? a : b;
}
Rust的显式标注:
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
生命周期标注类比理解:
- 类似C++模板元编程中的类型关系约束
- 相当于给引用添加"有效期"标签,确保返回引用不会超过输入引用的寿命
典型使用场景
结构体包含引用:
// C++等效代码
struct CppWrapper {
const std::string& inner; // 需要确保外部数据存活
};
// Rust安全实现
struct RustWrapper<'a> {
inner: &'a str, // 明确标注生命周期依赖
}
自动生命周期推断:
// 多数情况可省略(类似C++的模板参数推导)
fn first_word(s: &str) -> &str { // 编译器自动添加生命周期
s.split_whitespace().next().unwrap()
}
四、结构体与方法(从C++类到Rust结构体)
类型定义对比
C++类:
class Point {
public:
int x;
int y;
Point(int x, int y) : x(x), y(y) {}
void move(int dx, int dy) {
x += dx;
y += dy;
}
};
Rust结构体+impl:
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Self {
Point { x, y }
}
fn move(&mut self, dx: i32, dy: i32) {
self.x += dx;
self.y += dy;
}
}
关键差异:
- 数据与行为分离(无继承)
- 可变性显式声明(&mut self)
- 多个impl块可分散定义
关联函数与静态方法
C++静态方法:
class MathUtils {
public:
static int add(int a, int b) { return a + b; }
};
Rust关联函数:
impl Point {
fn origin() -> Self {
Point { x: 0, y: 0 }
}
}
// 调用方式
let p = Point::origin();
五、错误处理(从异常到Result)
错误处理范式迁移
C++异常机制:
int parse_number(const std::string& s) {
try {
return std::stoi(s);
} catch (const std::invalid_argument& e) {
// 处理错误
}
}
Rust的Result类型:
fn parse_number(s: &str) -> Result {
s.parse()
}
// 使用match处理
let result = match parse_number("42") {
Ok(n) => n,
Err(e) => /* 处理错误 */
};
Option与空值安全
C++的可空指针问题:
int* find(int* arr, size_t len, int target) {
for (size_t i=0; i<len; ++i) {
if (arr[i] == target) return &arr[i];
}
return nullptr; // 潜在的空指针解引用风险
}
Rust的Option解决方案:
fn find(arr: &[i32], target: i32) -> Option<&i32> {
arr.iter().find(|&&x| x == target)
}
// 强制处理空值情况
match find(&[1,2,3], 4) {
Some(v) => println!("Found {}", v),
None => println!("Not found"),
}
错误处理最佳实践对比:
场景 | C++惯用法 | Rust惯用法 |
不可恢复错误 | throw exception | panic! |
可恢复错误 | 返回错误码/异常 | Result |
可选值 | 返回指针/optional | Option |
错误传播 | try/catch | ?运算符 |
下篇预告:我们将深入探讨Rust的trait系统与C++概念/虚函数的异同、泛型编程的进阶技巧、智能指针的Rust实现方案,以及unsafe代码与C/C++互操作的实际应用场景。