Trait core::ops::Drop

1.0.0 · source ·
pub trait Drop {
    // Required method
    fn drop(&mut self);
}
Expand description

析构函数中的自定义代码。

当不再需要某个值时,Rust 将对该值运行 “析构函数”。 不再需要值的最常见方法是离开作用域。析构函数可能仍在其他情况下运行,但是在这里的示例中,我们将重点关注作用域。 要了解其他一些情况,请参见析构函数的 参考 部分。

此析构函数由两个组件组成:

  • 如果为此类型实现了特殊的 Drop trait,则对该值调用 Drop::drop
  • 自动生成的 “drop glue” 递归调用该值的所有字段的析构函数。

由于 Rust 自动调用所有包含字段的析构函数,因此在大多数情况下,您无需实现 Drop。但是在某些情况下它很有用,例如对于直接管理资源的类型。 该资源可能是内存,可能是文件描述符,可能是网络套接字。 一旦不再使用该类型的值,则应通过释放内存或关闭文件或套接字 “clean up” 资源。这是析构函数的工作,因此也是 Drop::drop 的工作。

Examples

要查看析构函数的作用,让我们看一下以下程序:

struct HasDrop;

impl Drop for HasDrop {
    fn drop(&mut self) {
        println!("Dropping HasDrop!");
    }
}

struct HasTwoDrops {
    one: HasDrop,
    two: HasDrop,
}

impl Drop for HasTwoDrops {
    fn drop(&mut self) {
        println!("Dropping HasTwoDrops!");
    }
}

fn main() {
    let _x = HasTwoDrops { one: HasDrop, two: HasDrop };
    println!("Running!");
}
Run

Rust 将首先为 _x 调用 Drop::drop,然后为 _x.one_x.two 调用,这意味着运行此命令将打印

Running!
Dropping HasTwoDrops!
Dropping HasDrop!
Dropping HasDrop!

即使我们删除了针对 HasTwoDropDrop 的实现,其字段的析构函数仍然会被调用。 这将导致

Running!
Dropping HasDrop!
Dropping HasDrop!

您不能自己调用 Drop::drop

因为 Drop::drop 是用来清理一个值的,所以在调用方法后使用该值可能很危险。 由于 Drop::drop 不拥有其输入的所有权,因此 Rust 通过不允许您直接调用 Drop::drop 来防止误用。

换句话说,如果您在上面的示例中尝试显式调用 Drop::drop,则会出现编译器错误。

如果您想显式调用一个值的析构函数,可以使用 mem::drop 代替。

Drop 指令

但是,我们的两个 HasDrop 哪个先丢弃掉? 对于结构体,其声明顺序相同:首先是 one,然后是 two。 如果您想自己尝试一下,可以修改上面的 HasDrop 以包含一些数据 (例如整数),然后在 Drop 内部的 println! 中使用它。 此行为由语言保证。

与结构体不同,局部变量以相反的顺序丢弃:

struct Foo;

impl Drop for Foo {
    fn drop(&mut self) {
        println!("Dropping Foo!")
    }
}

struct Bar;

impl Drop for Bar {
    fn drop(&mut self) {
        println!("Dropping Bar!")
    }
}

fn main() {
    let _foo = Foo;
    let _bar = Bar;
}
Run

这将打印

Dropping Bar!
Dropping Foo!

有关完整规则,请参见 the reference

CopyDrop 是排他的

您不能在同一类型上同时实现 CopyDropCopy 类型被编译器隐式复制,这使得很难预测何时以及将执行析构函数的频率。 因此,这些类型不能有析构函数。

丢弃检查

丢弃以微妙的方式与借用检查器交互: 当类型 T 被隐式丢弃为这种类型的某个变量时离开作用域,借用检查器需要确保此时调用 T 的析构函数是安全的。

特别是,还需要安全地递归丢弃 T 的所有字段。 例如,拒绝像下面这样的代码是至关重要的:

use std::cell::Cell;

struct S<'a>(Cell<Option<&'a S<'a>>>, Box<i32>);
impl Drop for S<'_> {
    fn drop(&mut self) {
        if let Some(r) = self.0.get() {
            // 在 `r` 中打印 `Box` 的内容。
            println!("{}", r.1);
        }
    }
}

fn main() {
    // 设置两个相互指向的 `S`。
    let s1 = S(Cell::new(None), Box::new(42));
    let s2 = S(Cell::new(Some(&s1)), Box::new(42));
    s1.0.set(Some(&s2));
    // 现在他们都丢弃了。
    // 但是第 2 个丢掉的那个会在第一个中访问 `Box`,这是一个 use-after-free!
}
Run

Nomicon 讨论了对 drop check in more detail 的需求。

为了拒绝这样的代码,“drop check” 分析确定当 T 得到弃弃时哪些类型和生命周期需要仍然存在。此分析的确切细节尚未得到稳定保证,并且可能会发生变化。 目前,分析工作如下:

  • 如果 T 没有抛弃胶水,那么什么都不需要活着。如果 T 及其任何 (recursive) 字段都没有析构函数 (impl Drop),就会出现这种情况。 PhantomDataManuallyDrop 被认为永远没有析构函数,无论它们的字段类型如何。
  • 如果 T 有丢弃胶水,那么,对于 T 的任何字段拥有的所有类型 U,递归添加 U 获得丢弃时需要存活的类型和生命周期。拥有类型的集合是通过递归遍历 T 来确定的:
    • 通过 PhantomDataBox、元组和数组 (包括长度为 0 的数组) 递归下降。
    • 停在引用和裸指针类型以及函数指针和函数项上; 他们不拥有任何东西。
    • 停在非复合类型 (当前上下文中保留泛型的类型参数和整数、bool 等基类型) ; 这些类型是拥有所有权的。
    • 当用 impl Drop 击中 ADT 时,停在那里; 这种类型是拥有所有权的。
    • 在没有 impl Drop 的情况下命中 ADT 时,递归下降到它的字段。 (对于 enum,考虑所有变体的所有字段。)
  • 此外,如果 T 实现 Drop,则 T 的所有泛型 (生命周期和类型) 参数都必须有效。

在上面的例子中,最后一个子句暗示当 S<'a> 是丢弃时 'a 必须是 live,因此该例子被拒绝。如果我们删除 impl Drop,则活性要求消失并且该示例被接受。

存在一种类型选择退出最后一个子句的不稳定方式; 这称为 “drop check eyepatch” 或 may_dangle。有关此仅限夜间,的更多详细信息,请参见 discussion in the Nomicon

Required Methods§

source

fn drop(&mut self)

执行此类型的析构函数。

当值离开作用域时隐式调用此方法,并且不能显式调用此方法 (会得到编译器 E0040 错误)。 但是,prelude 中的 mem::drop 函数可用于调用参数的 Drop 实现。

当这个方法被调用时,self 还没有被释放。 只有在方法结束后才会发生这种情况。 如果不是这种情况,那么 self 将是悬垂引用。

Panics

考虑到 panic! 将在展开时调用 drop,因此 drop 实现中的任何 panic! 都可能会中止。

请注意,即使此 panics,该值也被视为已丢弃; 您不得再次调用 drop。 这通常由编译器自动处理,但是在使用不安全的代码时,有时可能会无意间发生,尤其是在使用 ptr::drop_in_place 时。

Implementors§

1.36.0 · source§

impl Drop for Waker

source§

impl<'f> Drop for VaListImpl<'f>

1.40.0 · source§

impl<T, const N: usize> Drop for IntoIter<T, N>