在Rust中,有两种引用的可能性
1.Borrow,即获取引用,但不允许改变引用目的地。&
操作符从值中借用所有权。
1.可变地借用,即取一个引用来改变目标。&mut
操作符可变地从一个值借用所有权。
Rust关于借用规则的文档说:
首先,任何借用的持续范围必须不大于所有者的范围。其次,您可以拥有这两种借用中的一种,但不能同时拥有两种:
- 对资源的一个或多个引用(
&T
), - 只有一个可变引用(
&mut T
)。
我相信引用是创建一个指向值的指针,并通过指针访问值。如果有一个更简单的等效实现,编译器可以优化它。
但是,我不明白move是什么意思,以及它是如何实现的。
对于实现Copy
特征的类型,它意味着复制,例如通过从源代码分配结构成员,或memcpy()
。对于小结构或原语,这种复制是有效的。
为了搬家?
这个问题不是What are move semantics?的重复,因为Rust和C++是不同的语言,两者之间的move语义也不同。
6条答案
按热度按时间e3bfsja21#
语义
Rust实现了所谓的仿射类型系统:
仿射类型是线性类型的一个版本,施加了较弱的约束,对应于仿射逻辑。仿射资源最多可以使用 * 一次,而线性资源必须使用 * 一次。
不是
Copy
的类型,因此被移动,是仿射类型:你可以使用它们一次或永远,没有别的。Rust在其以所有权为中心的世界观中将其定义为 * 所有权的转移 ()。
(*)一些在Rust上工作的人比我在CS上更有资格,他们有意识地实现了仿射类型系统;然而与Haskell暴露math-y/cs-y概念相反,Rust倾向于暴露更实用的概念。
#[must_use]
的函数返回的仿射类型实际上是线性类型。执行情况
这取决于。请记住,Rust是一种为速度而构建的语言,这里有许多优化通道,这取决于所使用的 * 编译器 *(在我们的例子中,rustc + LLVM)。
在函数体(playground)中:
字符串
如果你检查LLVM IR(在图中),你会看到:
型
在封面下,rustc从
"Hello, World!".to_string()
到s
,然后到t
的结果调用memcpy
。虽然它可能看起来效率低下,但在Release模式下检查相同的IR,您会意识到LLVM已经完全省略了副本(意识到s
未使用)。当调用函数时也会发生同样的情况:理论上你将对象“移动”到函数堆栈框架中,但实际上如果对象很大,rustc编译器可能会切换到传递指针。
另一种情况是从函数中返回,但即使这样,编译器也可能应用“返回值优化”并直接在调用者的堆栈框架中构建-也就是说,调用者传递一个指针,将返回值写入其中,而无需中间存储。
Rust的所有权/借用约束实现了C中难以实现的优化(C也有RVO,但不能在很多情况下应用它)。
所以,Digest版本:
std::mem::size_of::<T>()
字节的memcpy
,因此移动较大的String
是有效的,因为它只复制两个字节,而不管它们占用的已分配缓冲区的大小如何cedebl8k2#
当你移动一件物品时,你就在转移物品的所有权。这是Rust的一个关键组件。
假设我有一个结构体,然后我把这个结构体从一个变量赋值给另一个变量。默认情况下,这将是一个移动,我已经转移了所有权。编译器将跟踪所有权的变化,并阻止我再使用旧的变量:
字符串
如何实施。
从概念上讲,移动某些东西并不需要做任何事情。在上面的例子中,没有理由在某个地方分配空间,然后在分配给不同变量时移动分配的数据。我实际上不知道编译器做什么,它可能会根据优化级别而改变。
实际上,你可以认为当你移动某个东西的时候,代表那个项目的位就像通过
memcpy
一样被复制了。这有助于解释当你把一个变量传递给一个函数来消耗它时会发生什么,或者当你从一个函数返回一个值时会发生什么(同样,优化器可以做其他事情来提高效率,这只是概念上的):型
“等等!“,你会说,“
memcpy
只在实现Copy
的类型中起作用!”这基本上是正确的,但最大的区别是,当一个类型实现Copy
时,source 和 destination 在复制后都是有效的!移动语义的一种思考方式与复制语义相同,但增加了一个限制,即被移动的对象不再是可使用的有效项。
然而,从另一个Angular 考虑通常更容易:你可以做的最基本的事情就是移动/给予所有权,而复制东西的能力是一种额外的特权。这就是Rust建模的方式。
这对我来说是个坚韧!在使用Rust一段时间后,move语义是自然的。让我知道我遗漏了哪些部分或解释得不好。
mctunoxg3#
Rust的
move
关键字总是困扰着我,所以我决定写下我的理解,这是我和同事讨论后得到的。我希望这可能会帮助某人。
字符串
在上面的语句中,x是一个值为1的变量。现在,
型
因此,
move
关键字用于将变量的所有权转移到闭包。在下面的例子中,没有
move
,x
就不属于闭包。因此x
不属于y
,可以进一步使用。型
另一方面,在下面的例子中,
x
由闭包拥有。x
由y
拥有,不能进一步使用。型
owning
的意思是containing as a member variable
。上面的例子与下面的两种情况相同。我们也可以假设下面的解释,关于Rust编译器如何扩展上面的情况。格式(无
move
;即所有权不转移),型
后者(带有
move
;即所有权转移),型
u3r8eeie4#
请让我回答我自己的问题。我有麻烦,但通过在这里问一个问题,我做了Rubber Duck Problem Solving。现在我明白了:
移动是价值的所有权**转移。
例如,赋值
let x = a;
转移所有权:最初a
拥有值。在let
之后,x
拥有值。Rust禁止此后使用a
。事实上,如果你在
let
之后执行println!("a: {:?}", a);
,Rust编译器会说:字符串
完整示例:
型
这是什么意思?
这个概念似乎来自C11。A document about C++ move semantics说:
从客户机代码的Angular 来看,选择移动而不是复制意味着您不关心源代码的状态发生了什么变化。
啊哈。C11不关心源代码发生了什么。因此,在这种情况下,Rust可以自由决定禁止在移动后使用源代码。
如何实施?
我不知道。但我可以想象Rust实际上什么都不做。
x
只是同一个值的不同名称。名称通常会被编译掉(当然调试符号除外)。因此,无论绑定的名称是a
还是x
,机器码都是相同的。看起来C++在复制构造函数省略中做了同样的事情。
什么都不做是最有效的。
mzsu5hc05#
将值传递给函数,也会导致所有权的转移;这与其他示例非常相似:
字符串
因此,预期误差:
型
u5i3ibmn6#
字符串
为了确保内存安全,rust会使s1无效,因此这不是浅拷贝,而是称为
Move
型