写点什么

Rust 中值销毁前的清理动作

作者:Shine
  • 2022 年 4 月 03 日
  • 本文字数:884 字

    阅读完需:约 3 分钟

IO 操作类型的值在被销毁前通常需要执行一些清理工作,如把缓存数据刷到磁盘, 关闭文件,优雅地关闭远程连接等等. 很自然地想到的方法是在 Drop 实现中添加这些清理操作. 但是, 若值被 dropped 时发生了错误, 我们无法把错误处理交给调用方. 为此,我们通常提供一个显示的析构方法---这个方法需要获取值的所有权 self, 返回 Result<_, _>. 务必在文档中强调这个显示的析构器, 这样调用者必须调用该方法优雅地释放资源. 尽管如此, 添加一个显示的析构方法会有两个问题:

  1. 不能在这个析构方法中移走这个类型的字段, 因为 Drop::drop 方法在调用这个析构方法之后仍然会调用, 而 drop 方法负责移走各个字段, 它要求该类型的各个字段没有被移走;

  2. drop 方法用的是 self 的可变引用,即 &mut self, 不是 self. 因此 drop 方法内部不能调用这个显示的析构方法.

有三种方法可以解决上面的问题,:

  1. 第一种方法是用 Option 包装这个(内部)类型, 创建新的顶级类型。自定义析构方法和 drop 方法都可以使用Option::take,仅当内部类型尚未被拿走(take)时,才调用内部类型的显式析构方法。由于内部类型不实现 Drop,因此您可以拥有其中所有字段的所有权。这种方法的缺点是,顶级类型上提供的所有方法现都必须通过 Option 访问内部类型上的字段(由于尚未调用 drop,所以这个 Option 总是 Some)。

  2. 第二种方法是使每个字段都是可以被取走的(takable). 可以使用 Option 的 take 方法替换值为 None, 也可以用 std::mem::take 方法用默认值替换集合(如 Vec, MashMap) 中的元素. 这种方法特别适合于你的类型字段都有合理空值的情况. 缺点是:访问每个字段都必须判断 Option 是否有值,这比较乏味.

  3. 第三种方法是用 ManuallyDrop<T>类型封装内部类型 T. 使用 ManuallyDrop 不需要 unwrap, 直接解引用为内部值. 也可以在 drop 中使用Manually::take获取所有权. 缺点是ManuallyDrop::take是不安全的, 因为你有可能调用多次 take, 或者调用 take 后还继续使用 ManuallyDrop 的其他方法使用内部值.


最终,你应该选择其中最适合你应用程序的方法。我一般开始偏向选择第二种方法, 当发现我的类型中太多 Option 字段后会转向另外两种方法. 如果你能够很容易保证你的代码是安全的, ManuallyDrop 方案是最佳的.


发布于: 刚刚阅读数: 4
用户头像

Shine

关注

万物负阴而抱阳,冲气以为和 2017.11.23 加入

一名修身养性的程序员

评论

发布
暂无评论
Rust中值销毁前的清理动作_rust_Shine_InfoQ写作平台