Rust中的move语义是什么?

x33g5p2x  于 2023-11-19  发布在  其他
关注(0)|答案(6)|浏览(136)

在Rust中,有两种引用的可能性
1.Borrow,即获取引用,但不允许改变引用目的地。&操作符从值中借用所有权。
1.可变地借用,即取一个引用来改变目标。&mut操作符可变地从一个值借用所有权。
Rust关于借用规则的文档说:
首先,任何借用的持续范围必须不大于所有者的范围。其次,您可以拥有这两种借用中的一种,但不能同时拥有两种:

  • 对资源的一个或多个引用(&T),
  • 只有一个可变引用(&mut T)。

我相信引用是创建一个指向值的指针,并通过指针访问值。如果有一个更简单的等效实现,编译器可以优化它。
但是,我不明白move是什么意思,以及它是如何实现的。
对于实现Copy特征的类型,它意味着复制,例如通过从源代码分配结构成员,或memcpy()。对于小结构或原语,这种复制是有效的。
为了搬家
这个问题不是What are move semantics?的重复,因为Rust和C++是不同的语言,两者之间的move语义也不同。

e3bfsja2

e3bfsja21#

语义

Rust实现了所谓的仿射类型系统:
仿射类型是线性类型的一个版本,施加了较弱的约束,对应于仿射逻辑。仿射资源最多可以使用 * 一次,而线性资源必须使用 * 一次
不是Copy的类型,因此被移动,是仿射类型:你可以使用它们一次或永远,没有别的。
Rust在其以所有权为中心的世界观中将其定义为 * 所有权的转移 )。
(*)一些在Rust上工作的人比我在CS上更有资格,他们有意识地实现了仿射类型系统;然而与Haskell暴露math-y/cs-y概念相反,Rust倾向于暴露更实用的概念。

  • 注意:从我的阅读来看,从标记为#[must_use]的函数返回的仿射类型实际上是线性类型。
    执行情况

这取决于。请记住,Rust是一种为速度而构建的语言,这里有许多优化通道,这取决于所使用的 * 编译器 *(在我们的例子中,rustc + LLVM)。
在函数体(playground)中:

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

字符串
如果你检查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)


在封面下,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是有效的,因为它只复制两个字节,而不管它们占用的已分配缓冲区的大小如何
cedebl8k

cedebl8k2#

当你移动一件物品时,你就在转移物品的所有权。这是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 } }


“等等!“,你会说,“memcpy只在实现Copy的类型中起作用!”这基本上是正确的,但最大的区别是,当一个类型实现Copy时,sourcedestination 在复制后都是有效的!
移动语义的一种思考方式与复制语义相同,但增加了一个限制,即被移动的对象不再是可使用的有效项。
然而,从另一个Angular 考虑通常更容易:你可以做的最基本的事情就是移动/给予所有权,而复制东西的能力是一种额外的特权。这就是Rust建模的方式。
这对我来说是个坚韧!在使用Rust一段时间后,move语义是自然的。让我知道我遗漏了哪些部分或解释得不好。

mctunoxg

mctunoxg3#

Rust的move关键字总是困扰着我,所以我决定写下我的理解,这是我和同事讨论后得到的。
我希望这可能会帮助某人。

let x = 1;

字符串
在上面的语句中,x是一个值为1的变量。现在,

let y = || println!("y is a variable whose value is a closure");


因此,move关键字用于将变量的所有权转移到闭包。
在下面的例子中,没有movex就不属于闭包。因此x不属于y,可以进一步使用。

let x = 1;
let y = || println!("this is a closure that prints x = {}". x);


另一方面,在下面的例子中,x由闭包拥有。xy拥有,不能进一步使用。

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
};

u3r8eeie

u3r8eeie4#

请让我回答我自己的问题。我有麻烦,但通过在这里问一个问题,我做了Rubber Duck Problem Solving。现在我明白了:

移动是价值的所有权**转移。

例如,赋值let x = a;转移所有权:最初a拥有值。在let之后,x拥有值。Rust禁止此后使用a
事实上,如果你在let之后执行println!("a: {:?}", a);,Rust编译器会说:

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);
}


这是什么意思?
这个概念似乎来自C11。A document about C++ move semantics说:
从客户机代码的Angular 来看,选择移动而不是复制意味着您不关心源代码的状态发生了什么变化。
啊哈。C
11不关心源代码发生了什么。因此,在这种情况下,Rust可以自由决定禁止在移动后使用源代码。
如何实施?
我不知道。但我可以想象Rust实际上什么都不做。x只是同一个值的不同名称。名称通常会被编译掉(当然调试符号除外)。因此,无论绑定的名称是a还是x,机器码都是相同的。
看起来C++在复制构造函数省略中做了同样的事情。
什么都不做是最有效的。

mzsu5hc0

mzsu5hc05#

将值传递给函数,也会导致所有权的转移;这与其他示例非常相似:

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`

u5i3ibmn

u5i3ibmn6#

let s1:String= String::from("hello");
let s2:String= s1;

字符串
为了确保内存安全,rust会使s1无效,因此这不是浅拷贝,而是称为Move

fn main() {
  // Each value in rust has a variable that is called its owner
  // There can only be one owner at a time.
  let s=String::from('hello')
  take_ownership(s)
  println!("{}",s)
  // Error: borrow of moved value "s". value borrowed here after move. so s cannot be borrowed after a move
  // when we pass a parameter into a function it is the same as if we were to assign s to another variable. Passing 's' moves s into the 'my_string' variable then `println!("{}",my_string)` executed, "my_string" printed out. After this scope is done, some_string gets dropped. 

  let x:i32 = 2;
  makes_copy(x)
  // instead of being moved, integers are copied. we can still use "x" after the function
  //Primitives types are Copy and they are stored in stack because there size is known at compile time. 
  println("{}",x)
}

fn take_ownership(my_string:String){
  println!('{}',my_string);
}

fn makes_copy(some_integer:i32){
  println!("{}", some_integer)
}

相关问题