是的,而且比「默认」更进一步:在安全 Rust 里它是唯一的资源管理方式,由编译器强制执行。

C++ 里 RAII 是一种你可以选择遵守的惯用法,你仍然可以写 new 和 delete、持有裸指针。Rust 把它做成了语言规则:每个值有且只有一个所有者,所有者离开作用域时值被自动释放。

fn main() {
    let f = std::fs::File::open("a.txt").unwrap();  // 获取资源
    let v = vec![1, 2, 3];                          // 堆内存
    let g = mutex.lock().unwrap();                  // 锁
}   // 作用域结束:g、v、f 按声明的逆序依次释放,不用写任何代码

析构逻辑写在 Drop trait 里,相当于 C++ 的析构函数。上面这段没有在机器上跑过,只是示意。

和 C++ 的 RAII 有什么不同

相同的是思想,不同的是有几处规则更严,也有几处你的 C++ 直觉会出错。

方面 C++ Rust
是否强制 惯用法,可以绕开 语言规则,安全代码里绕不开
赋值和传参 默认拷贝,移动要写 std::move 默认移动,拷贝要显式写 .clone()
移动之后的源对象 仍然存在,处于「有效但未指定」状态,析构函数照常执行 编译器禁止再使用,也不会再对它调用析构
移动的实现 调用移动构造函数,可以自定义 按位拷贝,不可自定义,不会失败
构造 构造函数,失败靠抛异常 没有构造函数,用普通函数返回值,失败返回 Result
手动释放 可以显式调用析构函数 不能直接调 drop 方法,只能用 drop(x) 把值移走
成员的析构顺序 按声明的逆序 按声明的顺序
局部变量的析构顺序 按声明的逆序 按声明的逆序,这一点相同

三个值得记住的点

一,移动后不析构。 这是和 C++ 最大的区别。C++ 的移动构造函数必须把源对象置成一个可以安全析构的空状态,比如把指针置空。Rust 不需要:编译器静态地知道这个值已经被移走了,直接不生成析构调用。所以 Rust 里不存在「被移走的空对象」这个概念,也不需要写移动构造函数。

二,锁守卫比 C++ 的更强。 C++ 的 std::lock_guard 只是约定:你可以忘记加锁就去访问数据。Rust 的 Mutex<T> 把数据包在锁里面,只有通过 lock() 返回的守卫才能拿到数据的引用,守卫析构时自动解锁。不加锁就访问数据,编译不过。

三,析构不保证一定执行。 这是 Rust 比你预期宽松的地方。std::mem::forget 可以让一个值不被析构,Rc 的循环引用也会导致泄漏,而且这些都算安全代码。Rust 保证的是不会出现释放后使用和重复释放,不保证不泄漏。所以不能把内存安全建立在「某个析构函数一定会跑」这个假设上。

面试时一句话

Rust 的所有权就是编译器强制执行的 RAII 加上默认移动语义。C++ 靠程序员自觉遵守的规则,Rust 靠编译器检查。

《Programming Rust》第 4 章和第 13 章讲的就是这部分,会把上面每一条和 C++ 做对比。