在 Rust 中,实际上是什么在移动变量?

6
我从未尝试过像C、C++和Go等语言,我决定从Rust开始学起。我已经有点了解栈和堆是什么了,但是,移动变量到底意味着什么?文档中说这不是浅拷贝:

...听起来可能像是一个浅拷贝。但由于Rust还使第一个变量无效,而不是称其为浅拷贝,因此它被称为移动(move)。在这个例子中,我们会说s1被移动到s2中...

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

 println!("{}, world!", s1);

文档中提到“无效”指什么?这是否意味着Rust将使s1无效,并将原来在s1的值分配给s2,所以...s1不存在?还是说它仍然有任何价值?这是我不理解的主要内容,它真的被移到了吗?还是s1在内存中仍然有任何价值?
据我所了解,这种检查发生在编译时,这让我想到s1在内存中确实不存在,只有s2,因为s1实际上已经移动到s2
显然,此情况发生在大小未知的值(即,在堆中)中。
希望我能帮助你理解。 :)
2个回答

4
对于你提出的每一个yes/no问题,答案既是"是"也是"不是"。我们可以从语言的语义和编译器对代码的处理两个方面来回答这个问题。
从语义上讲,在值被移动到s2之后,s1就不能再被读取了。但是,它可以被初始化为一个新的String值,然后再次使用。在某种意义上,“未初始化”和“移动自”实际上是相同的状态,因为在使用变量之前必须对其赋值。(它们在编译器给出的消息和结构体的子值可以被移动,但不能被未初始化方面略有不同。)
那么编译器会怎么处理呢?这取决于许多因素,包括(但不限于)编译器版本和选择的优化级别。如果编译器希望,它可以完全省略移动操作,因此s2实际上成为与s1相同内存区域的另一个名称。它也可以在堆栈上为s2分配自己的内存空间,并将s1的内容复制到其中。任何关于编译器如何操作的具体答案都必须带有编译环境的完整描述,包括编译器版本和传递给编译器的标志。
显然,这种情况发生在大小不确定的值上,即在堆上。
任何拥有所有权(并且未固定)的值都可以移动,无论它是否在堆上分配内存。通常,堆内存本身并没有被移动,只是指向堆内存的指针被移动到新位置。(例如,移动StringVec不会改变实际内容存储的内存位置。只是指向那些数据的指针换了手。)

嗯...所以,这取决于情况,哈哈哈,但是由于我是初学者,这对我来说有点困难,但考虑到你告诉我的内容(我没有配置任何东西,只是使用cargo run哈哈哈),实际上变量s1没有被初始化,也就是说,在内存中没有给它一个值,因为该值在s2中初始化,也就是说,它“转换”或理解为:let s1; let s2 = String::from("hello"); println!("{}, world!", s2);所以,在s1中没有引用或浅拷贝,我的理解正确吗?哈哈哈。 - Grizzly
1
@Grizzly 从概念上讲,移动操作将一个变量中的值转移所有权到另一个变量中。实际上并没有比这更复杂的东西。数据如何从一个变量传递到另一个变量(以及哪些变量可以被优化掉)是编译器需要解决的细节问题。在这种情况下,s1 拥有一个 String,它是指向堆分配的托管指针。将其移动到 s2String 值(因此也是堆分配)的所有权转移给了 s2,但移动并不要求甚至存在堆分配。 - cdhowie
啊啊啊啊!!!我想我现在明白了,它正在移动分配,它只是移动指针还是所有权?但不一定要重新分配或在堆中分配,但我会听从你的建议,因为我发现有些词汇和概念很难理解,因为我不会说英语(我说西班牙语),许多不同的单词被翻译成相同的意思,我找不到某些事物之间的区别。谢谢你的时间。 - Grizzly
1
@Grizzly:是的。移动语义只有在涉及到其他RAII控制的资源时才有意义(例如堆分配,在这种情况下,资源必须恰好释放一次),或者必须具有唯一所有权(例如可变引用不能复制,否则会存在竞争条件)。不涉及任何形式的资源管理的东西通常实现Copy特质,因此移动变成了复制,因为移动没有任何好处(复制i64的成本与移动它的成本相同)/复制没有额外的成本。 - ShadowRanger
1
@Grizzly:并且要明确的是,它绝对不会在堆上重新分配任何东西;移动语义的一个主要好处是任何这样的堆分配都会从一个对象转移到另一个对象。如果它正在创建新的分配并从旧对象复制到新对象,然后删除旧对象,那么移动语义将提供很少的好处(您仍然需要完成克隆对象的所有工作,您的峰值内存使用量仍将增加一倍,只是由于自动删除而更快地释放其中一半内存,这是一个次要的好处)。为什么要冒分配失败的风险,需要两倍的分配/清理工作等等,当您可以避免它呢? - ShadowRanger

1
当文档中说“invalidates”时,它是什么意思?这是否意味着Rust使s1无效并将值赋给了s2,所以... s1不存在了吗?
这意味着您作为开发人员不能再访问s1。因此,从您的角度来看,您可以认为s1不存在。
它有任何价值吗?
没有,因为它不存在(从您的角度来看)。
它真的被移动了吗?
这取决于程序和编译器。概念上,是的。
还是s1在内存中仍然有任何值?
即使不考虑移动,一般情况下变量可能根本不在内存中!这取决于优化器的操作。
这个检查是在编译时发生的
是的,它发生在编译时。
所以这让我想到s1在内存中确实不存在,只有s2,因为s1确实被移动到s2
不,这取决于情况。两者、一个或都不存在于内存、缓存、寄存器中... 要知道需要检查特定情况。

但是,根据文档中的示例:`fn main() { let s = String::from("hello"); is_free(s);println!("Other Code..."); // 此时,变量“s”是否已从内存中删除? println!("Other Code..."); println!("Other Code...");}fn is_free(some_string: String) { println!("{}", some_string); } 由于变量s被“移动”,当它退出is_free函数的作用域时,它是否被从内存中删除?还是当main函数的作用域结束时,变量s`被从内存中删除? - Grizzly
1
堆中的内存在 is_free 结束时被释放(尽管优化器原则上可以避免分配...),但 s 是不同的。它可能会一直存在于内存中或其他地方,直到 main 结束。但即使存在,您也不能使用它。 - Acorn
1
@Grizzly:明确一点,当Acorn说“s不同”时,他们指的是用于存储字符串“元数据”的微小内存量(长度和指向实际数据的指针,实际数据可能在堆上分配)。 s本身(而不是它所指向的字符串信息)通常是堆栈分配的(甚至仅保留在寄存器中),大多数编译器在函数进入时为局部变量保留堆栈,并在返回时释放它;在调用is_free后,将为s“元数据”保留堆栈内存,但它未被使用,提前清理没有任何好处。 - ShadowRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接