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

sdg*_*sdh 12 traits ownership move-semantics rust

书中:

Copy如果类型或其任何部分已经实现了Drop特征,Rust将不允许我们使用特征注释类型.如果类型需要一些特殊的东西,当值超出范围并且我们将Copy注释添加到该类型时,我们将得到编译时错误.

为什么设计决定不允许CopyDrop使用相同的类型?

Fre*_*ios 15

  • Drop特征用于RAII上下文,通常在对象被销毁时需要释放/关闭某些资源时.
  • 另一方面,Copy类型是一种普通类型,只能用一个复制memcpy.

有了这两个描述,就更清楚它们是独占的:对于memcpy非平凡的数据没有意义:如果我们复制数据,我们放弃其中一个副本会怎样?另一个副本的内部资源将不再可靠.

事实上,Copy甚至不是一个"真正的"特征,因为它没有定义任何功能.它是一个特殊的标记,对编译器说:"你可以用简单的字节副本复制自己".所以你不能提供自定义的实现Copy,因为根本没有实现.但是,您可以将类型标记为可复制:

impl Copy for Foo {}
Run Code Online (Sandbox Code Playgroud)

或者更好,有一个派生:

#[derive(Clone, Copy)]
struct Foo { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

仅在所有字段都实现时才构建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();
}
Run Code Online (Sandbox Code Playgroud)

  • @sdgfsdh实际上生锈允许类似`Copy`的自定义实现.它被称为"克隆".`Clone`总是在探索,可以执行额外的操作(例如增加引用计数),并且可以与`Drop`共存. (6认同)
  • @sdgfsdh复制共享指针时,必须增加内部引用计数器.这不是一个简单的`memcpy`.例如,参见[Rc`的drop实现](https://doc.rust-lang.org/src/alloc/rc.rs.html#830). (2认同)
  • @sdgfsdh不.让我在答案中添加更多相关信息. (2认同)

Jac*_*nor 15

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

#[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?
}
Run Code Online (Sandbox Code Playgroud)

但事实上,如果我们尝试编译它(使用 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`.
Run Code Online (Sandbox Code Playgroud)

看到底部的“了解更多信息”注释了吗?这些通常很有帮助。让我们运行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
Run Code Online (Sandbox Code Playgroud)

通过该问题链接,可以进入有关“drop-on-drop”的讨论。如今的 Rust 不再这样做,但直到 2016 年左右,Rust 才通过在删除值时将值的所有位清零来实现“动态删除”。但当然,如果一个类型可以同时是两者,那么这不是一个有效的实现Copy——RustDrop不能将允许您继续使用的值归零——因此不允许在同一类型上实现这两个特征。讨论以这个有趣的评论结束:

无论如何,现在禁止是最简单的。如果有人提出有说服力的用例,我们以后总是可以使其合法化。幂等析构函数看起来有点奇怪。


据我所知,以上是对 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

现在,从技术上讲,不调用对象的析构函数是允许的。那天有一个非常有趣的讨论,导致在 Rust 1.0 出现之前不久std::mem::forget从安全转向安全。unsafe因此,尽管存在这个问题,Rust 可能允许Copy+Drop而不会导致任何未定义的行为。但令人惊讶的是,某些(标准!)函数无法调用您期望的析构函数。Copy“对象可以按位复制和按位覆盖”这一属性似乎是一个值得保留的属性。


hel*_*low 6

引用文档

[...] [A]ny 类型实现Dropcan't be Copy,因为它管理除了自己的size_of::<T>字节之外的一些资源。


归档时间:

查看次数:

1448 次

最近记录:

7 年,1 月 前