如何让 Rust 单例的析构函数运行?



这些是我所知道的在Rust中创建单例的方法:

#[macro_use]
extern crate lazy_static;
use std::sync::{Mutex, Once, ONCE_INIT};
#[derive(Debug)]
struct A(usize);
impl Drop for A {
    fn drop(&mut self) {
        // This is never executed automatically.
        println!(
            "Dropping {:?} - Important stuff such as release file-handles etc.",
            *self
        );
    }
}
// ------------------ METHOD 0 -------------------
static PLAIN_OBJ: A = A(0);
// ------------------ METHOD 1 -------------------
lazy_static! {
    static ref OBJ: Mutex<A> = Mutex::new(A(1));
}
// ------------------ METHOD 2 -------------------
fn get() -> &'static Mutex<A> {
    static mut OBJ: *const Mutex<A> = 0 as *const Mutex<A>;
    static ONCE: Once = ONCE_INIT;
    ONCE.call_once(|| unsafe {
        OBJ = Box::into_raw(Box::new(Mutex::new(A(2))));
    });
    unsafe { &*OBJ }
}
fn main() {
    println!("Obj = {:?}", PLAIN_OBJ); // A(0)
    println!("Obj = {:?}", *OBJ.lock().unwrap()); // A(1)
    println!("Obj = {:?}", *get().lock().unwrap()); // A(2)
}

它们都不会在程序退出时调用A的析构函数(drop())。这是方法2(它是堆分配的)的预期行为,但我没有研究lazy_static!的实现,知道它将是类似的。

这里没有RAII。我可以在c++中实现RAII单例的行为(直到一年前我用c++编写代码,所以我的大多数比较都与它有关——我不知道很多其他语言)使用函数局部静态:

A& get() {
  static A obj; // thread-safe creation with C++11 guarantees
  return obj;
}

这可能是在实现定义的区域(惰性地)分配/创建的,并且在程序的生命周期内有效。当程序终止时,析构函数被确定地运行。我们需要避免从其他静态对象的析构函数中访问它,但我从来没有遇到过这种情况。

我可能需要释放资源,我想要运行drop()。现在,我最终在程序终止之前手工完成它(在所有线程都加入等之后的main结束时)。

我甚至不知道如何使用lazy_static!来做到这一点,所以我避免使用它,只使用方法2,我可以在最后手动销毁它。

我不想这样做;是否有一种方法可以让我在Rust中拥有这样一个RAII行为的单例?

特别是单例,以及全局构造函数/析构函数,都是祸根(特别是在c++这样的语言中)。

我认为它们引起的主要(功能)问题分别称为静态初始化(resp)。破坏)秩序惨败。也就是说,很容易意外地在这些全局变量之间创建一个依赖循环,即使没有这样的循环,编译器也不能立即清楚它们应该以什么顺序构建/销毁。

它们也可能导致其他问题:启动速度较慢,意外共享内存,…

在Rust中,采用的态度是 main之前/之后没有生命。因此,试图获得c++的行为可能不会像预期的那样工作。

你将得到更多的语言支持,如果你:

  • 删除全局方面
  • drop尝试拥有单个实例

(作为奖励,并行测试也会容易得多)

因此,我的建议是坚持使用局部变量。在main中实例化它,通过值/引用在调用堆栈中传递它,不仅可以避免那些棘手的初始化顺序问题,还可以得到销毁。

最新更新