在Rust中,有两种可能性来引用
-
Borrow ,即参考但不允许改变参考目的地 .
&
运算符从值中借用所有权 . -
Borrow mutably ,即参考改变目的地 .
&mut
运营商可能会从一个值中借用所有权 .
Rust documentation about borrowing rules说:
首先,任何借款必须持续不超过业主的范围 . 其次,您可能拥有这两种借用中的一种或另一种,但不能同时具有这两种:一个或多个引用(&T)到资源,恰好是一个可变引用(&mut T) .
我相信引用一个引用是创建一个指向值的指针并通过指针访问该值 . 如果有更简单的等效实现,编译器可以优化它 .
但是,我不明白 move 的含义以及它是如何实现的 .
对于实现 Copy
特征的类型,它意味着复制,例如通过从源分配结构成员,或 memcpy()
. 对于小结构或原始数据,此副本是有效的 .
并为 move ?
这个问题不是What are move semantics?的重复,因为Rust和C是不同的语言,并且两者之间的移动语义不同 .
4 回答
Semantics
Rust实现了所谓的Affine Type System:
非
Copy
并因此被移动的类型是仿射类型:您可以使用它们一次或从不使用它们 .Rust认为这是以其以所有权为中心的世界观(*)的所有权转移 .
(*)一些在Rust工作的人比我在CS中更有资格,他们故意实施仿射型系统;然而,与公开math-y / cs-y概念的Haskell相反,Rust倾向于暴露出更实用的概念 .
注意:可以认为从标记为
#[must_use]
的函数返回的仿射类型实际上是我阅读中的线性类型 .Implementation
这取决于 . 请记住,Rust是一种为速度而构建的语言,这里有许多优化过程,这取决于所使用的编译器(在我们的例子中是rustc LLVM) .
在函数体(playground)中:
如果检查LLVM IR(在Debug中),您将看到:
在封面下方,rustc从
"Hello, World!".to_string()
到s
的结果调用memcpy
,然后调用t
. 虽然它看起来效率低下,但在发布模式下检查相同的IR,您会发现LLVM已经完全省略了副本(意识到s
未被使用) .调用函数时会出现同样的情况:理论上你将对象“移动”到函数堆栈框架中,但实际上如果对象很大,则rustc编译器可能会切换到传递指针 .
另一种情况是从函数返回,但即使这样,编译器也可以应用"return value optimization"并直接在调用者的堆栈帧中构建 - 也就是说,调用者传递一个指针来写入返回值,该指针在没有中间存储的情况下使用 .
Rust的所有权/借用限制允许在C中难以达到的优化(其中也有RVO,但在很多情况下不能应用它) .
那么,摘要版本:
移动大型物体效率低下,但有许多优化措施可能会完全忽略此举
移动涉及
std::mem::size_of::<T>()
的std::mem::size_of::<T>()
字节,因此移动大String
是有效的,因为它只有几个字节,无论它们保持的分配缓冲区的大小如何移动项目时,您正在转移该项目的所有权 . 这是Rust的一个关键组成部分 .
假设我有一个结构,然后我将结构从一个变量分配给另一个变量 . 默认情况下,这将是一个举动,我已转让所有权 . 编译器将跟踪此所有权更改并阻止我再使用旧变量:
从概念上讲,移动某些东西不需要做任何事情 . 在上面的例子中,实际上并不知道编译器的作用,它可能会根据优化级别而改变 .
但是,出于实际目的,您可以认为当您移动某些内容时,表示该项目的位将被复制,就像通过
memcpy
一样 . 这有助于解释当您将变量传递给使用它的函数时,或者从函数返回值时(再次,优化器可以执行其他操作以使其有效,这只是概念上)时会发生什么:"But wait!",你说,“
memcpy
只在实现Copy
的类型中发挥作用!” . 这大部分都是正确的,但最大的区别在于当一个类型实现Copy
时,源和目标都可以在之后使用副本!一种思考移动语义的方法与复制语义相同,但附加的限制是移动的东西不再是要使用的有效项 .
但是,通常更容易从另一个角度来考虑它:您可以做的最基本的事情是移动/放弃所有权,复制某些东西的能力是另一种特权 . 这就是Rust模仿它的方式 .
这对我来说是一个棘手的问题!使用Rust一段时间后,移动语义是很自然的 . 让我知道我遗漏或解释不好的部分 .
请让我回答我自己的问题 . 我遇到了麻烦,但在这里问了一个问题我做了Rubber Duck Problem Solving . 现在我明白了:
move 是该值的 transfer of ownership .
例如,赋值
let x = a;
转移所有权:首先a
拥有该值 . 在let
之后,拥有该值的是x
. Rust禁止此后使用a
.事实上,如果你在
let
之后做println!("a: {:?}", a);
,Rust编译器说:完整的例子:
那 move 是什么意思?
似乎这个概念来自C 11. A document about C++ move semantics说:
啊哈 . C 11并不关心源码发生了什么 . 所以在这种情况下,Rust可以自由决定禁止在移动后使用源 .
它是如何实现的?
我不知道 . 但是我可以想象Rust确实没什么 .
x
只是相同值的不同名称 . 名称通常被编译掉(当然除了调试符号) . 因此,绑定的名称是a
还是x
是相同的机器代码 .似乎C在复制构造函数elision中也是如此 .
什么都不做是最有效的 .
将 Value 转化为功能,也会导致所有权转移;它与其他例子非常相似:
因此预期的错误: