为什么 Rust 不允许在一种类型上复制和删除特征?

Why does Rust not allow the copy and drop traits on one type?

来自 the book

Rust won’t let us annotate a type with the Copy trait if the type, or any of its parts, has implemented the Drop trait. If the type needs something special to happen when the value goes out of scope and we add the Copy annotation to that type, we’ll get a compile time error.

为什么设计决定不允许在同一类型上使用 CopyDrop

  • Drop 特征用在 RAII 上下文中,通常是在对象被销毁时某些资源需要 released/closed 时。
  • 另一方面,Copy 类型是一种普通类型,只能用 memcpy 复制。

有了这两个描述,它们的排他性就更清楚了:memcpy 重要数据没有意义:如果我们复制数据,然后删除其中一个副本怎么办?其他副本的内部资源将不再可靠。

事实上,Copy 甚至不是 "real" 特征,因为它没有定义任何函数。它是一个特殊的 标记 ,它告诉编译器:"you can duplicate myself with a simple bytes copy"。所以你不能提供Copy的自定义实现,因为根本没有实现。但是,您可以将类型标记为可复制:

impl Copy for Foo {}

或更好,带有派生:

#[derive(Clone, Copy)]
struct Foo { /* ... */ }

仅当所有字段都实现 Copy 时才会构建。否则,编译器拒绝编译,因为这是不安全的。


为了举例,我们假设 File 结构实现了 Copy。当然,不是这种情况,而且这个例子是错误的,无法编译:

fn drop_copy_type<T>(T x)
where
    T: Copy + Drop,
{
    // The inner file descriptor is closed there:
    std::mem::drop(x);
}

fn main() {
    let mut file = File::open("foo.txt").unwrap();
    drop_copy_type(file);
    let mut contents = String::new();

    // Oops, this is unsafe!
    // We try to read an already closed file descriptor:
    file.read_to_string(&mut contents).unwrap();
}

引用 documentation.

[...] [A]ny type implementing Drop can't be Copy, because it's managing some resource besides its own size_of::<T> bytes.

这里的其他答案是在谈论为什么我们通常 不想 为同一类型实现 CopyDrop,但那是与解释为什么 禁止 不同。看起来像这样的玩具示例应该可以正常工作:

#[derive(Copy, Clone)]
struct Foo {
    i: i32,
}

impl Drop for Foo {
    fn drop(&mut self) {
        // No problematic memory management here. Just print.
        println!("{}", self.i);
    }
}

fn main() {
    let foo1 = Foo { i: 42 };
    let foo2 = foo1;
    // Shouldn't this just print 42 twice?
}

但实际上,如果我们尝试编译它(使用 Rust 1.52),它会按预期失败:

error[E0184]: the trait `Copy` may not be implemented for this type; the type has a destructor
 --> src/main.rs:1:10
  |
1 | #[derive(Copy, Clone)]
  |          ^^^^ Copy not allowed on types with destructors
  |
  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0184`.

看到底部的“了解更多信息”注释了吗?这些通常很有帮助。让我们 运行 rustc --explain E0184:

The `Copy` trait was implemented on a type with a `Drop` implementation.

Erroneous code example:

```
#[derive(Copy)]
struct Foo; // error!

impl Drop for Foo {
    fn drop(&mut self) {
    }
}
```

Explicitly implementing both `Drop` and `Copy` trait on a type is currently
disallowed. This feature can make some sense in theory, but the current
implementation is incorrect and can lead to memory unsafety (see
[issue #20126][iss20126]), so it has been disabled for now.

[iss20126]: https://github.com/rust-lang/rust/issues/20126

紧接着这个问题 link 引发了关于“掉落时归零”的讨论。现在的 Rust 不再这样做了,但是 up until around 2016 Rust implemented "dynamic drop" by zeroing all the bits of a value when dropping it. But of course that isn't a valid implementation if a type can be both Copy and Drop -- Rust can't zero out a value that you're allowed to keep using -- so implementing both of those traits on the same type was disallowed. The discussion ends with this interesting comment:

Anyhow, it's easiest to forbid it for now. We can always make it legal later if someone comes up with a persuasive use case. Idempotent destructors seem like a bit of an odd thing.


以上是对 Rust 当前行为的解释,据我所知。但我认为还有另一个原因让事情保持原样,我还没有看到讨论:Copy 目前意味着一个值可以按位复制 也可以按位覆盖.考虑这段代码:

#[derive(Copy, Clone)]
struct Foo {
    i: i32,
}

fn main() {
    let mut ten_foos = [Foo { i: 42 }; 10];
    let ten_more_foos = [Foo { i: 99 }; 10];
    // Overwrite all the bytes of the first array with those of the second.
    unsafe {
        std::ptr::copy_nonoverlapping(&ten_more_foos, &mut ten_foos, 1);
    }
}

这个不安全的代码今天完全没问题。事实上,[T]::copy_from_slice 会对任何 T: Copy 做完全相同的事情。但是,如果允许 Foo(或任何其他 Copy 类型)为 Drop,它仍然可以吗?我们这里的代码和 copy_from_slice 中的标准库代码将销毁对象而不丢弃它们!

现在,技术上,调用对象的析构函数失败是允许的。在 Rust 1.0 之前不久,有一个 very interesting discussion 导致 std::mem::forgetunsafe 变为安全。因此,尽管有这个问题,Rust 可能允许 Copy + Drop 而不会导致任何未定义的行为。但令人惊讶的是,某些(标准!)函数无法调用您期望的析构函数。 属性“Copy 对象可以按位复制和按位覆盖”似乎是一个很好的保留。