04-RAII 守卫

RAII 守卫 — Rust Design Patterns

译文 · 基于 Rust Design Patterns

原文链接: https://rust-unofficial.github.io/patterns/patterns/behavioural/RAII.html

RAII 守卫

描述

RAII 代表 “Resource Acquisition is Initialisation”(资源获取即初始化),这是个糟糕的名字。该模式的本质是:资源初始化在对象的构造器中完成,收尾在析构器中完成。在 Rust 中,该模式被扩展为:用 RAII 对象作为某种资源的守卫,并依赖类型系统确保访问始终经由守卫对象中介。

示例

互斥锁守卫是标准库中该模式的经典例子(这是真实实现的简化版):

use std::ops::Deref;

struct Foo {}

struct Mutex<T> {
    // 此处保存对数据 T 的引用。
    //..
}

struct MutexGuard<'a, T: 'a> {
    data: &'a T,
    //..
}

// 加锁是显式的。
impl<T> Mutex<T> {
    fn lock(&self) -> MutexGuard<T> {
        // 锁定底层操作系统互斥锁。
        //..

        // MutexGuard 保存对 self 的引用
        MutexGuard {
            data: self,
            //..
        }
    }
}

// 用于解锁互斥锁的析构器。
impl<'a, T> Drop for MutexGuard<'a, T> {
    fn drop(&mut self) {
        // 解锁底层操作系统互斥锁。
        //..
    }
}

// 实现 Deref 意味着我们可以把 MutexGuard 当作指向 T 的指针来用。
impl<'a, T> Deref for MutexGuard<'a, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.data
    }
}

fn baz(x: Mutex<Foo>) {
    let xx = x.lock();
    xx.foo(); // foo 是 Foo 上的方法。
              // 借用检查器确保我们无法保存对底层
              // Foo 的引用,使其生命周期超过守卫 xx。

    // 退出此函数并执行 xx 的析构器时,x 会被解锁。
}

动机

当资源必须在使用后收尾时,可以用 RAII 来完成收尾。如果在收尾之后再访问该资源是错误的,那么可以用此模式防止这类错误。

优点

防止资源未收尾,以及资源在收尾之后仍被使用的错误。

讨论

RAII 是确保资源被正确释放或收尾的有用模式。我们可以利用 Rust 的借用检查器,在静态层面防止收尾发生后仍使用资源所导致的错误。

借用检查器的核心目标是确保对数据的引用不会比该数据活得更久。RAII 守卫模式之所以有效,是因为守卫对象包含对底层资源的引用,并且只暴露这类引用。Rust 确保守卫不能比底层资源活得更久,经由守卫中介的资源引用也不能比守卫活得更久。要看清这一点,查看不做生命周期省略的 deref 签名会有帮助:

fn deref<'a>(&'a self) -> &'a T {
    //..
}

返回的资源引用与 self 具有相同的生命周期('a)。因此借用检查器会确保对 T 的引用的生命周期短于 self 的生命周期。

注意,实现 Deref 并不是该模式的核心部分,它只是让守卫对象用起来更符合人体工学。在守卫上实现 get 方法同样可行。

参见

析构器中的收尾

RAII 是 C++ 中的常见模式: cppreference.com, wikipedia。

风格指南条目 (目前只是占位)。

最后修改 August 21, 2026: 更新 (76fc81a2e)