nal*_*ply 21 ownership move-semantics rust
在Rust中,有两种可能性来引用
借用,即参考,但不允许改变参考目的地.该&运营商借用值所有权.
可变地借用,即参考改变目的地.该&mut运营商性情不定地借用一个值所有权.
首先,任何借入必须持续不超过所有者的范围.其次,您可能拥有这两种借款中的一种或另一种,但不能同时使用这两种:
- 一个或多个
&T资源的引用(),- 一个可变的引用(
&mut T).
我相信引用一个引用是创建一个指向值的指针并通过指针访问该值.如果存在更简单的等效实现,则编译器可以优化它.
但是,我不明白什么是移动的意思以及它是如何实现的.
对于实现Copy特征的类型,它意味着复制,例如通过从源分配结构成员,或者a memcpy().对于小结构或原始数据,此副本是有效的.
而对于招?
Mat*_* M. 21
语义
Rust实现了所谓的仿射类型系统:
仿射类型是强加较弱约束的线性类型的一种形式,对应于仿射逻辑.仿射资源只能使用一次,而线性资源只能使用一次.
没有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);
}
Run Code Online (Sandbox Code Playgroud)
如果检查LLVM IR(在调试中),您将看到:
%_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)
Run Code Online (Sandbox Code Playgroud)
下面的盖子,rustc调用memcpy从结果"Hello, World!".to_string()来s,然后到t.虽然看起来效率低下,但在发布模式下检查相同的IR,您会发现LLVM已经完全省略了副本(意识到s未使用).
调用函数时会出现同样的情况:理论上你将对象"移动"到函数堆栈框架中,但实际上如果对象很大,则rustc编译器可能会切换到传递指针.
另一种情况是从函数返回,但即使这样,编译器也可以应用"返回值优化"并直接在调用者的堆栈帧中构建 - 也就是说,调用者传递一个指针,在该指针中写入返回值,中间存储.
Rust的所有权/借用限制允许在C++中难以实现的优化(也有RVO,但在很多情况下不能应用它).
那么,摘要版本:
memcpy的std::mem::size_of::<T>()字节,因此移动大String是有效的,因为它只有一对夫妇字节不管他们守住分配的缓冲区的大小She*_*ter 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);
}
Run Code Online (Sandbox Code Playgroud)
它是如何实现的.
从概念上讲,移动某些东西不需要做任何事情.在上面的示例中,没有理由在某处实际分配空间,然后在分配给不同变量时移动分配的数据.我实际上并不知道编译器的作用,它可能会根据优化级别而改变.
但是出于实际目的,您可以认为当您移动某些内容时,表示该项目的位将被复制,就像通过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 } }
Run Code Online (Sandbox Code Playgroud)
"但是等等!",你说," memcpy只有实现类型才能发挥作用Copy!".这大部分都是正确的,但最大的区别在于,当一个类型实现时Copy,源和目标都可以在复制后使用!
考虑移动语义的一种方法与复制语义相同,但附加的限制是移动的东西不再是要使用的有效项.
但是,通常更容易从另一个角度来考虑它:你可以做的最基本的事情是移动/放弃所有权,复制某些东西的能力是一个额外的特权.这就是Rust模仿它的方式.
这对我来说是一个棘手的问题!使用Rust一段时间后,移动语义是很自然的.让我知道我遗漏或解释不好的部分.