为什么默认(结构值)数组初始化需要 Copy 特征?

Why is the Copy trait needed for default (struct valued) array initialization?

当我像这样定义一个结构时,我可以按值将它传递给一个函数而无需添加任何特定内容:

#[derive(Debug)]
struct MyType {
    member: u16,
}

fn my_function(param: MyType) {
    println!("param.member: {}", param.member);
}

当我想创建一个包含 MyType 个具有默认值

实例的数组时
fn main() {
    let array = [MyType { member: 1234 }; 100];
    println!("array[42].member: ", array[42].member);
}

Rust 编译器告诉我:

error[E0277]: the trait bound `MyType: std::marker::Copy` is not satisfied
  --> src/main.rs:11:17
   |
11 |     let array = [MyType { member: 1234 }; 100];
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::marker::Copy` is not implemented for `MyType`
   |
   = note: the `Copy` trait is required because the repeated element will be copied

当我实施 CopyClone 时,一切正常:

impl Copy for MyType {}
impl Clone for MyType {
    fn clone(&self) -> Self {
        MyType {
            member: self.member.clone(),
        }
    }
}
  1. 为什么我需要指定一个空的 Copy trait 实现?

  2. 有没有更简单的方法来做到这一点,还是我必须重新考虑一些事情?

  3. 为什么将 MyType 的实例按值传递给函数时会起作用?我的猜测是它正在移动,所以一开始就没有副本。

与C/C++相反,Rust 对被复制和被移动的类型有非常明确的区分。请注意,这只是语义上的区别;在实现层面上,移动 浅字节复制,但是,编译器对您可以对从中移动的变量执行的操作施加了某些限制。

默认情况下每个 类型只能移动(不可复制)。这意味着此类类型的值会四处移动:

let x = SomeNonCopyableType::new();
let y = x;
x.do_something();      // error!
do_something_else(x);  // error!

你看,存储在 x 中的值已移至 y,因此您无法对 x 执行任何操作。

移动语义是 Rust 中所有权概念的一个非常重要的部分。您可以阅读更多内容 in the official guide.

然而,有些类型非常简单,因此它们的字节复制也是语义复制:如果你逐字节复制一个值,你将得到一个新的完全独立的值。例如,原始数就是这样的类型。这样的 属性 由 Rust 中的 Copy 特征指定,即如果一个类型实现了 Copy,那么这个类型的值是隐式可复制的。 Copy不包含方法;它的存在仅仅是为了标记实现类型具有某些 属性,因此它通常被称为标记特征(以及其他一些做类似事情的特征)。

但是,它并不适用于所有类型。例如,像动态分配的向量这样的结构不能自动复制:如果它们是这样,它们中包含的分配地址也会被字节复制,然后这种向量的析构函数将是 运行 两倍分配,导致这个指针被释放两次,这是一个内存错误。

所以默认情况下,Rust 中的自定义类型是不可复制的。但是您可以使用 #[derive(Copy, Clone)] 选择加入(或者,如您所见,直接使用 impl;它们是等效的,但 derive 通常读起来更好):

#[derive(Copy, Clone)]
struct MyType {
    member: u16
}

(派生 Clone 是必要的,因为 Copy 继承了 Clone,所以 Copy 的所有内容也必须是 Clone

如果你的类型原则上可以自动复制,也就是说,它没有关联的析构函数并且它的所有成员都是Copy,那么derive你的类型也将是Copy.

您可以在数组初始化器中使用 Copy 类型,因为数组将使用此初始化器中使用的值的按字节副本进行初始化,因此您的类型必须实现 Copy 来指定它确实可以自动复制

以上是对1和2的回答。至于3,是的,你完全正确。它确实有效,因为值已移入函数中。如果您在将 MyType 类型的变量传递给函数后尝试使用它,您会很快注意到有关使用移动值的错误。

Why do I need to specify an empty Copy trait implementation?

Copy 是一个特殊的内置特征,因此 T 实现 Copy 表示可以安全地复制具有浅字节的 T 类型的值复制.

这个简单的定义意味着只需要告诉编译器这些语义是正确的,因为 运行 时间行为没有根本改变:移动(非 Copy 类型) 和 "copy" 是浅字节副本,这只是源代码以后是否可用的问题。参见 an older answer for more details

(如果MyType的内容不是Copy本身,编译器会报错;以前会自动执行,但都改变了with opt-in built-in traits。)

创建数组是通过浅拷贝复制值,如果TCopy,这保证是安全的。在更一般的情况下它是安全的,#5244 涵盖了其中的一些,但在核心,非 Copy 结构将无法用于自动创建固定长度的数组,因为编译器无法判断重复是 safe/correct.

Is there a simpler way to do this or do I have to re-think something (I'm coming from C)?

#[derive(Copy)]
struct MyType {
    member: u16
}

将插入适当的空实现(#[derive] 与其他几个特征一起使用,例如经常看到 #[derive(Copy, Clone, PartialEq, Eq)]。)

Why does it work when passing an instance of MyType to the function by value? My guess is that it is being moved, so there is no copy in the first place.

好吧,如果不调用该函数,就看不到移动与复制行为(如果您调用它两次相同的非 Copy 值,编译器会发出有关移动值的错误).但是,"move" 和 "copy" 在机器上本质上是一样的。值的所有按值使用在 Rust 中都是语义上的浅拷贝,就像在 C 中一样。