在Rust中,有两种可能性来引用
借用,即参考但不允许改变参考目的地。 &
运算符从值中借用所有权。
可以借用,即参考改变目的地。 &mut
运算符可以从值中可变地借用所有权。
Rust documentation about borrowing rules说:
首先,任何借用必须持续不超过的范围 所有者。其次,你可能有这两种中的一种或另一种 借用,但不能同时借两个:
- 对资源的一个或多个引用(
&T
),- 恰好是一个可变引用(
&mut T
)。
我认为引用引用是创建指向值的指针并通过指针访问值。如果存在更简单的等效实现,则编译器可以对此进行优化。
但是,我不明白移动的含义及其实现方式。
对于实现Copy
特征的类型,它意味着复制,例如通过从源分配结构成员,或memcpy()
。对于小结构或基元,这个副本是有效的。
移动?
这个问题不是What are move semantics?的重复,因为Rust和C ++是不同的语言,两者之间的移动语义不同。
答案 0 :(得分:18)
<强>语义强>
Rust实现了所谓的Affine Type System:
仿射类型是强加较弱约束的线性类型的一种形式,对应于仿射逻辑。 仿射资源只能使用一次,而线性资源只能使用一次。
非Copy
并且因此被移动的类型是仿射类型:您可以使用它们一次或从不使用它们。
Rust在其以所有权为中心的世界观(*)中将其视为所有权转移。
(*)一些在Rust工作的人比在CS中更有资格,他们故意实施仿射型系统;然而,与公开math-y / cs-y概念的Haskell相反,Rust倾向于暴露出更实用的概念。
注意:可以认为从#[must_use]
标记的函数返回的仿射类型实际上是我阅读时的线性类型。
<强>实施强>
这取决于。请记住,Rust是一种为速度而构建的语言,这里有许多优化过程,这取决于所使用的编译器(在我们的例子中是rustc + LLVM)。
在函数体(playground)中:
fn main() {
let s = "Hello, World!".to_string();
let t = s;
println!("{}", t);
}
如果你检查LLVM IR(在Debug中),你会看到:
%_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从memcpy
到"Hello, World!".to_string()
的结果调用s
,然后调用t
。虽然看起来效率低下,但在发布模式下检查相同的IR,您会发现LLVM已经完全省略了副本(意识到s
未被使用)。
调用函数时会出现同样的情况:理论上你将对象“移动”到函数堆栈框架中,但实际上如果对象很大,则rustc编译器可能会切换到传递指针。
另一种情况是从函数返回,但即使这样,编译器也可以应用“返回值优化”并直接在调用者的堆栈帧中构建 - 也就是说,调用者传递一个指针写入返回值,在没有中间存储的情况下使用。
Rust的所有权/借用限制使得在C ++中很难实现优化(也有RVO,但在很多情况下都不能应用它)。
所以,摘要版本:
memcpy
个std::mem::size_of::<T>()
字节,因此移动大String
是有效的,因为它只有几个字节,无论它们保留的分配缓冲区的大小是什么答案 1 :(得分:10)
当您移动某个项目时,您转移该项目的所有权。这是Rust的一个关键组成部分。
让我们说我有一个结构,然后我将结构从一个变量分配给另一个变量。默认情况下,这将是一个举动,我已转让所有权。编译器将跟踪此所有权更改并阻止我再使用旧变量:
pub struct Foo {
value: u8,
}
fn main() {
let foo = Foo { value: 42 };
let bar = foo;
println!("{}", foo.value); // error: use of moved value: `foo.value`
println!("{}", bar.value);
}
如何实施。
从概念上讲,移动某些东西并不需要来做任何事情。在上面的例子中,当我分配给不同的变量时,实际上在某处实际分配空间然后移动分配的数据是没有理由的。我实际上并不知道编译器的作用,它可能会根据优化级别而改变。
但是出于实际目的,您可以认为当您移动某些内容时,表示该项目的位将被复制,就像通过memcpy
一样。这有助于解释当您将变量传递给使用它的函数时,或者当您从函数返回值时(再次,优化器可以执行其他操作以使其高效)时会发生什么,这只是概念上):
// Ownership is transferred from the caller to the callee
fn do_something_with_foo(foo: Foo) {}
// Ownership is transferred from the callee to the caller
fn make_a_foo() -> Foo { Foo { value: 42 } }
&#34;但是等等!&#34;,你说,&#34; memcpy
只对实现Copy
的类型发挥作用!&#34;。这大部分都是正确的,但最大的区别在于,当类型实现Copy
时, source 和 destination 都可以在复制后使用!< / p>
一种思考移动语义的方法与复制语义相同,但附加的限制是移动的东西不再是要使用的有效项目。
但是,从另一个角度来看,通常更容易想到它:你可以做的最基本的事情是移动/放弃所有权,复制某些东西的能力是另一种特权。这就是Rust模仿它的方式。
这对我来说是一个棘手的问题!使用Rust一段时间后,移动语义是很自然的。让我知道我遗漏或解释不好的部分。
答案 2 :(得分:1)
请让我回答我自己的问题。我遇到了麻烦,但在这里问了一个问题我做了Rubber Duck Problem Solving。现在我明白了:
移动是值的所有权转移。
例如,作业let x = a;
转让所有权:首先a
拥有该值。拥有该值的let
之后x
。 Rust禁止此后使用a
。
事实上,如果你在Rust编译器println!("a: {:?}", a);
之后执行let
:
error: use of moved value: `a`
println!("a: {:?}", a);
^
完整示例:
#[derive(Debug)]
struct Example { member: i32 }
fn main() {
let a = Example { member: 42 }; // A struct is moved
let x = a;
println!("a: {:?}", a);
println!("x: {:?}", x);
}
移动意味着什么?
似乎这个概念来自C ++ 11。 document about C++ move semantics说:
从客户端代码的角度来看,选择move而不是copy意味着你不关心源的状态会发生什么。
啊哈。 C ++ 11并不关心源代码会发生什么。因此,在这种情况下,Rust可以自由决定在移动后禁止使用源。
它是如何实现的?
我不知道。但是我可以想象Rust确实没什么。 x
只是同一个值的不同名称。名称通常被编译掉(当然除了调试符号)。因此绑定的名称为a
或x
是相同的机器代码。
似乎C ++在复制构造函数elision中也是如此。
什么都不做是最有效的。
答案 3 :(得分:1)
将值传递给函数,也会导致所有权转移;它与其他例子非常相似:
struct Example { member: i32 }
fn take(ex: Example) {
// 2) Now ex is pointing to the data a was pointing to in main
println!("a.member: {}", ex.member)
// 3) When ex goes of of scope so as the access to the data it
// was pointing to. So Rust frees that memory.
}
fn main() {
let a = Example { member: 42 };
take(a); // 1) The ownership is transfered to the function take
// 4) We can no longer use a to access the data it pointed to
println!("a.member: {}", a.member);
}
因此预期错误:
post_test_7.rs:12:30: 12:38 error: use of moved value: `a.member`
答案 4 :(得分:0)
Rust 的 move
关键字一直困扰着我,所以我决定写下我在与同事讨论后获得的理解。
我希望这可以帮助某人。
let x = 1;
在上面的语句中,x 是一个值为 1 的变量。现在,
let y = || println!("y is a variable whose value is a closure");
因此,move
关键字用于将变量的所有权转移给闭包。
在下面的示例中,如果没有 move
,则 x
不属于闭包。因此,x
不归 y
所有,可供进一步使用。
let x = 1;
let y = || println!("this is a closure that prints x = {}". x);
另一方面,在下面这个例子中,x
归闭包所有。 x
归 y
所有,不可进一步使用。
let x = 1;
let y = move || println!("this is a closure that prints x = {}". x);
owning
我的意思是 containing as a member variable
。上面的示例案例与以下两种情况处于相同的情况。我们也可以假设下面的解释是关于 Rust 编译器如何扩展上述情况的。
形式(没有move
;即没有所有权转让),
struct ClosureObject {
x: &u32
}
let x = 1;
let y = ClosureObject {
x: &x
};
后者(带move
;即所有权转让),
struct ClosureObject {
x: u32
}
let x = 1;
let y = ClosureObject {
x: x
};