RAII 守卫

说明

RAII是个糟糕的名字,代表“资源获取即初始化”。该模式的本质是,资源的初始化在对象的构造函数中完成,以及确定性析构器。通过使用一个RAII对象作为一些资源的守卫,并且依赖类型系统确保访问始终要通过守卫对象,以此在Rust中扩展这种模式。

代码示例

互斥保护是std库中这种模式的经典示例(这是实际实现中的简化版本):

  1. use std::ops::Deref;
  2. struct Foo {}
  3. struct Mutex<T> {
  4. // We keep a reference to our data: T here.
  5. //..
  6. }
  7. struct MutexGuard<'a, T: 'a> {
  8. data: &'a T,
  9. //..
  10. }
  11. // Locking the mutex is explicit.
  12. impl<T> Mutex<T> {
  13. fn lock(&self) -> MutexGuard<T> {
  14. // Lock the underlying OS mutex.
  15. //..
  16. // MutexGuard keeps a reference to self
  17. MutexGuard {
  18. data: self,
  19. //..
  20. }
  21. }
  22. }
  23. // Destructor for unlocking the mutex.
  24. impl<'a, T> Drop for MutexGuard<'a, T> {
  25. fn drop(&mut self) {
  26. // Unlock the underlying OS mutex.
  27. //..
  28. }
  29. }
  30. // Implementing Deref means we can treat MutexGuard like a pointer to T.
  31. impl<'a, T> Deref for MutexGuard<'a, T> {
  32. type Target = T;
  33. fn deref(&self) -> &T {
  34. self.data
  35. }
  36. }
  37. fn baz(x: Mutex<Foo>) {
  38. let xx = x.lock();
  39. xx.foo(); // foo is a method on Foo.
  40. // The borrow checker ensures we can't store a reference to the underlying
  41. // Foo which will outlive the guard xx.
  42. // x is unlocked when we exit this function and xx's destructor is executed.
  43. }

出发点

当资源被使用后必须被销毁,RAII可以被用来实现确定性析构。如果在销毁后访问该资源是错误的,那么此模式可用于防止此类错误。

优点

防止使用未初始化资源和销毁后资源的错误。

讨论

RAII是确保资源被合适地析构或确定的实用模式。我们可以在Rust中使用借用检查器静态地防止析构后发生使用资源的错误。

借用检查器的核心目标是确保对数据的引用不能超过数据的生命周期。RAII守卫模式之所以有效,是因为守卫对象包含对底层资源的引用并且只暴露这样的引用。Rust确保了守卫不能比底层资源活的更长,并且由守卫控制的对资源的引用不能比守卫获得更长。要了解这是如何工作的,最好检查deref的签名不进行生命周期省略。

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

返回的资源引用有与self相同的生命周期('a')。借用检查器因此确保T的引用比self的声明周期要短。

注意实现Deref不是这个模式的核心部分,这只是为了在用守卫时更加符合人体工程学。对守卫实现一个get方法也一样可以。

参阅

Finalisation in destructors idiom

RAII is a common pattern in C++: cppreference.com, wikipedia.

Style guide entry (currently just a placeholder).