为什么 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.
为什么设计决定不允许在同一类型上使用 Copy
和 Drop
?
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.
这里的其他答案是在谈论为什么我们通常 不想 为同一类型实现 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?
}
但实际上,如果我们尝试编译它(使用 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::forget
从 unsafe
变为安全。因此,尽管有这个问题,Rust 可能允许 Copy
+ Drop
而不会导致任何未定义的行为。但令人惊讶的是,某些(标准!)函数无法调用您期望的析构函数。 属性“Copy
对象可以按位复制和按位覆盖”似乎是一个很好的保留。
来自 the book:
Rust won’t let us annotate a type with the
Copy
trait if the type, or any of its parts, has implemented theDrop
trait. If the type needs something special to happen when the value goes out of scope and we add theCopy
annotation to that type, we’ll get a compile time error.
为什么设计决定不允许在同一类型上使用 Copy
和 Drop
?
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 beCopy
, because it's managing some resource besides its ownsize_of::<T>
bytes.
这里的其他答案是在谈论为什么我们通常 不想 为同一类型实现 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?
}
但实际上,如果我们尝试编译它(使用 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::forget
从 unsafe
变为安全。因此,尽管有这个问题,Rust 可能允许 Copy
+ Drop
而不会导致任何未定义的行为。但令人惊讶的是,某些(标准!)函数无法调用您期望的析构函数。 属性“Copy
对象可以按位复制和按位覆盖”似乎是一个很好的保留。