需要手动掉落的 Rust 类型

Rust type that requires manual drop

如果要自动删除结构,有没有办法让 Rust 编译器出错?

示例:我正在实现一个内存池,并希望将分配手动返回到内存池以防止泄漏。下面的例子中有类似 RequireManualDrop 的东西吗?

impl MemoryPool {
   pub fn allocate(&mut self) -> Option<Allocation> { /* ... */ }
   pub fn free(&mut self, alloc: Allocation) { let (ptr, size) = alloc.inner.into_inner(); /* ... */ }
}

pub struct Allocation {
   inner: RequireManualDrop<(*mut u8, usize)>,
}

fn valid_usage(mem: &mut MemoryPool) {
   let chunk = mem.allocate();
   /* ... */
   mem.free(chunk);
}

/* compile error: Allocation.inner needs to be manully dropped */
fn will_have_compile_error(mem: &mut MemoryPool) {
   let chunk = mem.allocate();
   /* ... */
}

Rust 对此没有直接的解决方案。但是,您可以为此使用一个 nit 技巧(这个技巧取自 https://github.com/Kixunil/dont_panic)。

如果调用一个已声明但未定义的函数,链接器会报错。我们可以用它来解决掉落时的错误。通过在析构函数中调用未定义的函数,除非不调用析构函数,否则代码不会编译。

#[derive(Debug)]
pub struct Undroppable(pub i32);

impl Drop for Undroppable {
    fn drop(&mut self) {
        extern "C" {
            // This will show (somewhat) useful error message instead of complete gibberish
            #[link_name = "\n\nERROR: `Undroppable` implicitly dropped\n\n"]
            fn trigger() -> !;
        }
        unsafe { trigger() }
    }
}

pub fn manually_drop(v: Undroppable) {
    let v = std::mem::ManuallyDrop::new(v);
    println!("Manually dropping {v:?}...");
}

Playground.

但是,请注意,虽然在这种情况下它甚至可以在调试版本中运行,但在其他情况下可能需要优化才能消除调用。展开可能会使过程更加复杂,因为它可能导致意外的隐式丢弃。例如,如果我将 manually_drop() 更改为以下看似相同的版本:

pub fn manually_drop(v: Undroppable) {
    println!("Manually dropping {v:?}...");
    std::mem::forget(v);
}

不行,因为println!()可能会平仓,然后std::mem::forget(v)就达不到了。如果一个按值采用 Undroppable 的函数必须展开,您可能别无选择,只能用 ManuallyDrop 包装它并手动确保它被正确删除(尽管您可以在函数末尾删除它, 并让 unwind case 泄漏它,因为在恐慌中泄漏大多是好的)。