当我定义这样的结构时,我可以通过值将它传递给函数而不添加任何特定的结构:
#[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
当我实施Copy
和Clone
时,一切正常:
impl Copy for MyType {}
impl Clone for MyType {
fn clone(&self) -> Self {
MyType {
member: self.member.clone(),
}
}
}
为什么我需要指定一个空的Copy
特征实现?
是否有更简单的方法可以做到这一点,还是我必须重新思考一下?
为什么通过值将MyType
的实例传递给函数时它是否有效?我的猜测是它被移动了,所以首先没有副本。
答案 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。)
创建数组是通过浅拷贝复制值,如果T
为Copy
,则保证这是安全的。在更一般的情况下它是安全的,#5244涵盖了其中一些,但在核心,非Copy
结构将无法用于自动创建固定长度的数组,因为编译器无法判断复制是否安全/正确。
有没有更简单的方法可以做到这一点,还是我必须重新思考一下(我来自C)?
#[derive(Copy)]
struct MyType {
member: u16
}
将插入适当的空实现(#[derive]
与其他几个特征一起使用,例如经常看到#[derive(Copy, Clone, PartialEq, Eq)]
。)
为什么通过值将
MyType
的实例传递给函数时它是否有效?我的猜测是它被移动了,所以首先没有副本。
好吧,在没有调用函数的情况下,没有看到移动与复制行为(如果你将其称为两次相同的非Copy
值,编译器将发出有关移动值的错误)。但是,“移动”和“复制”在机器上基本相同。值的所有按值使用都是Rust中语义上的浅拷贝,就像在C中一样。