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

时间:2015-01-10 13:12:19

标签: struct rust traits

当我定义这样的结构时,我可以通过值将它传递给函数而不添加任何特定的结构:

#[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特征实现?

  2. 是否有更简单的方法可以做到这一点,还是我必须重新思考一下?

  3. 为什么通过值将MyType的实例传递给函数时它是否有效?我的猜测是它被移动了,所以首先没有副本。

2 个答案:

答案 0 :(得分:20)

与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 trait指定,即如果类型实现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以指定它确实< em>可以自动复制。

以上是对1和2的回答。至于3,是的,你是绝对正确的。它确实正常工作,因为值被移入函数中。如果在将函数传递给函数后尝试使用MyType类型的变量,则会很快发现有关使用移动值的错误。

答案 1 :(得分:5)

  

为什么我需要指定一个空的Copy trait实现?

Copy是一种特殊的内置特征,T实现Copy表示使用浅字节副本复制类型T的值是安全的。

这个简单的定义意味着只需告诉编译器那些语义是正确的,因为运行时行为没有根本改变:移动(非Copy类型)和“复制”是浅字节副本,这只是一个问题,即源是否可以使用。请参阅an older answer for more details

(如果MyType的内容本身不是Copy,编译器会抱怨;之前会自动实施,但所有内容都已更改with opt-in built-in traits。)

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

  

有没有更简单的方法可以做到这一点,还是我必须重新思考一下(我来自C)?

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

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

  

为什么通过值将MyType的实例传递给函数时它是否有效?我的猜测是它被移动了,所以首先没有副本。

好吧,在没有调用函数的情况下,没有看到移动与复制行为(如果你将其称为两次相同的非Copy值,编译器将发出有关移动值的错误)。但是,“移动”和“复制”在机器上基本相同。值的所有按值使用都是Rust中语义上的浅拷贝,就像在C中一样。