use*_*335 37 move-semantics rust
该锈语言网站索赔移动语义的语言的特征之一.但我无法看到Rust中如何实现移动语义.
Rust box是唯一使用移动语义的地方.
let x = Box::new(5);
let y: Box<i32> = x; // x is 'moved'
Run Code Online (Sandbox Code Playgroud)
上面的Rust代码可以用C++编写
auto x = std::make_unique<int>();
auto y = std::move(x); // Note the explicit move
Run Code Online (Sandbox Code Playgroud)
据我所知(如果我错了,请纠正我),
Rust如何提供移动语义?
oli*_*obk 44
我认为这是来自C++的一个非常普遍的问题.在C++中,当涉及到复制和移动时,你正在做一切事情.该语言是围绕复制和引用而设计的.使用C++ 11,"移动"内容的能力被粘在该系统上.另一方面,Rust重新开始了.
Rust根本没有构造函数,更不用说移动构造函数了.
您不需要移动构造函数.Rust移动"没有复制构造函数"的所有内容,即"不实现Copy
特征".
struct A;
fn test() {
let a = A;
let b = a;
let c = a; // error, a is moved
}
Run Code Online (Sandbox Code Playgroud)
Rust的默认构造函数(按照惯例)只是一个名为的关联函数new
:
struct A(i32);
impl A {
fn new() -> A {
A(5)
}
}
Run Code Online (Sandbox Code Playgroud)
更复杂的构造函数应该具有更具表现力的名称.这是C++中命名的构造函数习语
不支持右值参考.
它一直是一个请求的功能,请参阅RFC问题998,但很可能您要求一个不同的功能:将内容移动到函数:
struct A;
fn move_to(a: A) {
// a is moved into here, you own it now.
}
fn test() {
let a = A;
move_to(a);
let c = a; // error, a is moved
}
Run Code Online (Sandbox Code Playgroud)
无法使用rvalue参数创建函数重载.
你可以用特质做到这一点.
trait Ref {
fn test(&self);
}
trait Move {
fn test(self);
}
struct A;
impl Ref for A {
fn test(&self) {
println!("by ref");
}
}
impl Move for A {
fn test(self) {
println!("by value");
}
}
fn main() {
let a = A;
(&a).test(); // prints "by ref"
a.test(); // prints "by value"
}
Run Code Online (Sandbox Code Playgroud)
Jas*_*rff 12
Rust 支持具有以下功能的移动语义:
所有类型都是可移动的。
默认情况下,在整个语言中向某处发送一个值是一种移动。对于非Copy
类型,如Vec
,以下都是 Rust 中的动作:按值传递参数、返回值、赋值、按值模式匹配。
你std::move
在 Rust 中没有,因为它是默认的。你真的一直在使用动作。
Rust 知道不能使用移动的值。如果您有一个值x: String
并且 dochannel.send(x)
将值发送到另一个线程,编译器就会知道它x
已被移动。在移动后尝试使用它是一个编译时错误,“使用移动的值”。如果有人引用某个值(悬空指针),则您无法移动该值。
Rust 知道不要在移动的值上调用析构函数。移动值会转移所有权,包括清理责任。类型不必能够表示特殊的“值已移动”状态。
移动成本低,性能可预测。它基本上是 memcpy。返回一个巨大Vec
的总是很快——你只是在复制三个词。
Rust 标准库在任何地方使用并支持移动。我已经提到了通道,它使用移动语义来安全地跨线程传输值的所有权。其他优点:所有类型都支持std::mem::swap()
Rust 中的无复制;的Into
和From
标准转换性状是由值; Vec
和其他集合具有.drain()
和.into_iter()
方法,因此您可以粉碎一个数据结构,将所有值移出其中,并使用这些值构建一个新的数据结构。
Rust 没有移动引用,但移动是 Rust 中一个强大且核心的概念,它提供了许多与 C++ 中相同的性能优势,以及一些其他优势。
Seb*_*edl 11
Rust的移动和复制语义与C++非常不同.我将采用不同的方法来解释它们而不是现有的答案.
在C++中,由于自定义复制构造函数,复制是一种可以任意复杂的操作.Rust不希望简单赋值或参数传递的自定义语义,因此采用不同的方法.
首先,在Rust中传递的赋值或参数始终只是一个简单的内存副本.
let foo = bar; // copies the bytes of bar to the location of foo (might be elided)
function(foo); // copies the bytes of foo to the parameter location (might be elided)
Run Code Online (Sandbox Code Playgroud)
但是,如果对象控制一些资源怎么办?假设我们正在处理一个简单的智能指针Box
.
let b1 = Box::new(42);
let b2 = b1;
Run Code Online (Sandbox Code Playgroud)
此时,如果仅复制字节,是否不会drop
为每个对象调用析构函数(在Rust中),从而将相同的指针释放两次并导致未定义的行为?
答案是Rust 默认移动.这意味着它将字节复制到新位置,然后旧对象消失.b1
在上面的第二行之后访问是一个编译错误.析构函数不是为它而调用的.该值已移至b2
,并且b1
可能不再存在.
这就是移动语义在Rust中的工作方式.将复制字节,旧对象消失.
在一些关于C++移动语义的讨论中,Rust的方式被称为"破坏性移动".有人建议添加"移动析构函数"或类似于C++的东西,以便它可以具有相同的语义.但是移动语义,因为它们是用C++实现的,不会这样做.旧对象被遗忘,其析构函数仍然被调用.因此,您需要一个移动构造函数来处理移动操作所需的自定义逻辑.移动只是一个专门的构造函数/赋值运算符,预计会以某种方式运行.
因此,默认情况下,Rust的赋值会移动对象,使旧位置无效.但是许多类型(整数,浮点,共享引用)具有语义,其中复制字节是创建实际副本的完全有效的方式,而不需要忽略旧对象.这些类型应该实现Copy
trait,它可以由编译器自动派生.
#[derive(Copy)]
struct JustTwoInts {
one: i32,
two: i32,
}
Run Code Online (Sandbox Code Playgroud)
这表示编译器分配和参数传递不会使旧对象无效:
let j1 = JustTwoInts { one: 1, two: 2 };
let j2 = j1;
println!("Still allowed: {}", j1.one);
Run Code Online (Sandbox Code Playgroud)
请注意,琐碎的复制和破坏的需要是相互排斥的; 一类是Copy
不能也Drop
.
那么当你想复制一些只是复制字节的东西时,例如一个向量呢?这没有语言功能; 从技术上讲,类型只需要一个返回以正确方式创建的新对象的函数.但按照惯例,这是通过实现Clone
特征及其clone
功能来实现的.实际上,编译器也支持自动派生Clone
,它只是克隆每个字段.
#[Derive(Clone)]
struct JustTwoVecs {
one: Vec<i32>,
two: Vec<i32>,
}
let j1 = JustTwoVecs { one: vec![1], two: vec![2, 2] };
let j2 = j1.clone();
Run Code Online (Sandbox Code Playgroud)
每当你派生出来时Copy
,你也应该派生出来Clone
,因为容器就像Vec
在内部克隆时一样使用它.
#[derive(Copy, Clone)]
struct JustTwoInts { /* as before */ }
Run Code Online (Sandbox Code Playgroud)
现在,这有什么缺点吗?是的,事实上有一个相当大的缺点:由于移动对象到另一个存储位置只是通过复制字节完成的,没有自定义逻辑,类型不能引用到自身.实际上,Rust的生命周期系统使得无法安全地构建这样的类型.
但在我看来,权衡是值得的.
归档时间: |
|
查看次数: |
8225 次 |
最近记录: |