Rust中的移动语义是什么? [英] What are move semantics in Rust?

查看:158
本文介绍了Rust中的移动语义是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Rust中,有两种可能的参考方式

In Rust, there are two possibilities to take a reference

  1. 借阅,即获取参考,但不允许更改参考目标. &运算符从值中借用所有权.

  1. Borrow, i.e., take a reference but don't allow mutating the reference destination. The & operator borrows ownership from a value.

可变借阅,即引用以更改目标. &mut运算符从值中可变地借用所有权.

Borrow mutably, i.e., take a reference to mutate the destination. The &mut operator mutably borrows ownership from a value.

有关借阅规则的锈迹斑斑的文档说:

首先,任何借贷必须持续的范围不超过借款的范围. 所有者.其次,您可能同时拥有这两种 借,但不能同时借:

First, any borrow must last for a scope no greater than that of the owner. Second, you may have one or the other of these two kinds of borrows, but not both at the same time:

  • 一个或多个对资源的引用(&T)
  • 恰好是一个可变引用(&mut T).
  • one or more references (&T) to a resource,
  • exactly one mutable reference (&mut T).

我认为引用是创建指向该值的指针并通过该指针访问该值.如果存在更简单的等效实现,则编译器可以优化这一点.

I believe that taking a reference is creating a pointer to the value and accessing the value by the pointer. This could be optimized away by the compiler if there is a simpler equivalent implementation.

但是,我不理解移动的含义以及其实现方式.

However, I don't understand what move means and how it is implemented.

对于实现Copy特征的类型,它意味着复制例如通过从源代码或memcpy()成员方式分配struct成员.对于小型结构或基元而言,此副本非常有效.

For types implementing the Copy trait it means copying e.g. by assigning the struct member-wise from the source, or a memcpy(). For small structs or for primitives this copy is efficient.

移动吗?

这个问题不是什么是移动语义?,因为Rust和C ++是不同的语言,而移动语义是两者之间是不同的.

This question is not a duplicate of What are move semantics? because Rust and C++ are different languages and move semantics are different between the two.

推荐答案

语义

Rust实现了所谓的仿射类型系统:

Rust implements what is known as an Affine Type System:

仿射类型是施加较弱约束的线性类型的一种形式,对应于仿射逻辑. 仿射资源只能使用一次,而线性资源只能使用一次.

Affine types are a version of linear types imposing weaker constraints, corresponding to affine logic. An affine resource can only be used once, while a linear one must be used once.

不是Copy且因此被移动的类型是仿射类型:您可以使用一次,也可以永远不使用它们.

Types that are not Copy, and are thus moved, are Affine Types: you may use them either once or never, nothing else.

铁锈将其视为所有权的转移..

Rust qualifies this as a transfer of ownership in its Ownership-centric view of the world (*).

(*)一些Rust的工作人员比我在CS方面的能力要强得多,他们有意识地实施了仿射类型系统;但是与Haskell揭露了数学y/cs-y概念相反,Rust倾向于揭露更多实用的概念.

(*) Some of the people working on Rust are much more qualified than I am in CS, and they knowingly implemented an Affine Type System; however contrary to Haskell which exposes the math-y/cs-y concepts, Rust tends to expose more pragmatic concepts.

注意:可以说,根据我的阅读,从带有#[must_use]标签的函数返回的仿射类型实际上是线性类型.

Note: it could be argued that Affine Types returned from a function tagged with #[must_use] are actually Linear Types from my reading.

实施

这取决于.请记住,Rust是一种为速度而设计的语言,此处有许多优化途径在起作用,这取决于所使用的编译器(在我们的例子中为Rustc + LLVM).

It depends. Please keep in mind than Rust is a language built for speed, and there are numerous optimizations passes at play here which will depend on the compiler used (rustc + LLVM, in our case).

在函数体内(游乐场 ):

fn main() {
    let s = "Hello, World!".to_string();
    let t = s;
    println!("{}", t);
}

如果检查LLVM IR(在Debug中),则会看到:

If you check the LLVM IR (in Debug), you'll see:

%_5 = alloca %"alloc::string::String", align 8
%t = alloca %"alloc::string::String", align 8
%s = alloca %"alloc::string::String", align 8

%0 = bitcast %"alloc::string::String"* %s to i8*
%1 = bitcast %"alloc::string::String"* %_5 to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %1, i8* %0, i64 24, i32 8, i1 false)
%2 = bitcast %"alloc::string::String"* %_5 to i8*
%3 = bitcast %"alloc::string::String"* %t to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %3, i8* %2, i64 24, i32 8, i1 false)

在封底下,rustc从"Hello, World!".to_string()的结果调用memcpys,然后到t.尽管这似乎效率低下,但在发布"模式下检查相同的IR时,您将意识到LLVM已完全删除了副本(意识到s未被使用).

Underneath the covers, rustc invokes a memcpy from the result of "Hello, World!".to_string() to s and then to t. While it might seem inefficient, checking the same IR in Release mode you will realize that LLVM has completely elided the copies (realizing that s was unused).

调用函数时也会发生相同的情况:理论上,您将对象移动"到函数堆栈框架中,但是实际上,如果对象较大,则rustc编译器可能会改为传递指针.

The same situation occurs when calling a function: in theory you "move" the object into the function stack frame, however in practice if the object is large the rustc compiler might switch to passing a pointer instead.

另一种情况是从函数 returning ,但是即使这样,编译器仍可能会应用返回值优化"并直接在调用者的堆栈框架中进行构建-也就是说,调用者将指针传递给编写返回值,该返回值无需中间存储即可使用.

Another situation is returning from a function, but even then the compiler might apply "return value optimization" and build directly in the caller's stack frame -- that is, the caller passes a pointer into which to write the return value, which is used without intermediary storage.

Rust的所有权/借用约束使得优化无法在C ++中实现(C ++也具有RVO,但在很多情况下无法应用).

The ownership/borrowing constraints of Rust enable optimizations that are difficult to reach in C++ (which also has RVO but cannot apply it in as many cases).

摘要版本:

  • 移动大型物体效率不高,但是有许多优化措施可能会完全抵消这一移动
  • 移动涉及到std::mem::size_of::<T>()个字节的memcpy个字节,因此移动一个大的String块是很有效的,因为它仅复制几个字节,无论它们持有的已分配缓冲区的大小如何
  • moving large objects is inefficient, but there are a number of optimizations at play that might elide the move altogether
  • moving involves a memcpy of std::mem::size_of::<T>() bytes, so moving a large String is efficient because it only copies a couple bytes whatever the size of the allocated buffer they hold onto

这篇关于Rust中的移动语义是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆