我想知道它们之间有什么区别:
"some string".to_string()
和
"some string".into_string()
前者似乎来自ToString,这很清楚。
然而,后者似乎来自IntoString,这对我来说不太清楚。
consume
值是什么意思?这两个特征有什么区别?
进行一些挖掘后的其他信息。
这里the current implementation of into_string
代表String
。如您所见,它只返回自身,因此不进行任何分配。
答案 0 :(得分:25)
consume
值是什么意思?
消耗值与移动值有关。在我讨论两个特征之间的差异之前,我将给出一些移动值意味着什么的例子。让我们创建一个Vec
Ascii
个字符:asciis
。
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
println!("{}", asciis);
}
Internally,Vec
是一个包含三个字段的结构:
Vec
。Vec
。Vec
管理的数据的指针。图示,Vec
的内存布局及其管理的数据可能如下所示。
Stack: asciis Heap:
+----------+ +----------+
0xF0 | data | ----> 0xA0 | 'h' |
+----------+ +----------+
0xF4 | length | 0xA1 | 'i' |
+----------+ +----------+
0xF8 | capacity |
+----------+
当我们的Vec
超出范围时,它会释放它所管理的内存。释放的记忆对我们来说是垃圾。访问释放的内存是错误的。这看起来像下面这样。 Vec
已经消失,堆上的内存已被释放。
Heap:
+----------+
0xA0 | GARBAGE |
+----------+
0xA1 | GARBAGE |
+----------+
现在,让我们回到我们的代码并尝试复制asciis
。
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
{
let an_attempted_copy = asciis;
}
println!("{}", asciis);
}
我们猜测an_attempted_copy
是asciis
的副本。在我们制作副本之后,我们的记忆可能如下所示。
Stack: asciis Heap: Stack: an_attempted_copy
+----------+ +----------+ +----------+
0xF0 | data | ----> 0xA0 | 'h' | <---- 0xE0 | data |
+----------+ +----------+ +----------+
0xF4 | length | 0xA1 | 'i' | | length |
+----------+ +----------+ +----------+
0xF8 | capacity | | capacity |
+----------+ +----------+
在我们尝试println!
asciis
之前,an_attempted_copy
超出了范围!就像以前一样,我们的Vec
指向的数据已被释放。
Stack: asciis Heap:
+----------+ +----------+
0xF0 | data | ----> 0xA0 | GARBAGE |
+----------+ +----------+
0xF4 | length | 0xA1 | GARBAGE |
+----------+ +----------+
0xF8 | capacity |
+----------+
哦,asciis
指向释放的记忆!这是个坏消息,因为我们即将println!
asciis
。
那么我们如何纠正这种情况呢?嗯,这里有两个选择。
asciis
复制到an_attempted_copy
时,我们可以将asciis
指向的数据复制到新分配的内存中。像C ++这样的其他语言可以做到这一点。asciis
!生锈就是这样。 那么搬家意味着什么?这意味着an_attempted_copy
将取得asciis
之前指向的数据的所有权。 asciis
失去了所有权,我们无法再使用它了。为清楚起见,请重命名an_attempted_copy
。
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
{
let actually_a_move = asciis;
}
println!("{}", asciis);
}
现在,让我们在进入actually_a_move
之后立即绘制内存布局。
Stack: asciis Heap: Stack: actually_a_move
+----------+ +----------+ +----------+
0xF0 | GARBAGE | 0xA0 | 'h' | <---- 0xE0 | data |
+----------+ +----------+ +----------+
0xF4 | GARBAGE | 0xA1 | 'i' | | length |
+----------+ +----------+ +----------+
0xF8 | GARBAGE | | capacity |
+----------+ +----------+
asciis
不再拥有内存,因此我们无法再使用asciis
了。这意味着它几乎是垃圾。因此,如果我们不再使用asciis
,那么当我们println!
时会发生什么?我们收到以下错误。
<anon>:6:24: 6:30 error: use of moved value: `asciis`
<anon>:6 println!("{}", asciis);
^~~~~~
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
<anon>:6:9: 6:32 note: expansion site
<anon>:4:17: 4:32 note: `asciis` moved here because it has type `collections::vec::Vec<std::ascii::Ascii>`, which is moved by default (use `ref` to override)
<anon>:4 let actually_a_move = asciis;
^~~~~~~~~~~~~~~
error: aborting due to previous error
正如预期的那样,Rust编译器告诉我们我们正在尝试使用ascii
,但ascii
是一个移动值;这是错误的。
移动语义(以及借用和生命周期等相关主题)是很难的事情。我在这里只是勉强抓住了表面。有关详细信息,rust by example和this stackoverflow问题都是很好的资源。
to_string
vs into_string
这两个特质有什么区别?
现在我已经探索了消费或移动价值的概念,让我们来看看两个特征之间的差异。让我们首先看一下to_string
的类型签名。
fn to_string(&self) -> String;
此函数引用self
并返回一个新的String
供我们使用。我还没有讨论参考文献以及它们如何影响运动,但是当我说这里没有动作时,请相信我。
现在让我们看一下into_string
的类型签名。
fn into_string(self) -> String;
此功能不会引用self
。而是将self
移入函数中。
那么这种差异的含义是什么?我们来看一个例子。
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
let no_moves_here = asciis.to_string();
println!("{}", asciis);
}
我们再次创建Vec
个Ascii
个字符。然后,当我们致电asciis.to_string()
时,会创建一个全新的String
并且永远不会移动asciis
。此代码将按预期构建和运行,打印出[h, i]
。现在,让我们使用into_string
。
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
let uh_oh_we_just_moved_asciis = asciis.into_string();
println!("{}", asciis);
}
这是我们在尝试构建此代码时收到的错误消息。
<anon>:4:24: 4:30 error: use of moved value: `asciis`
<anon>:4 println!("{}", asciis);
^~~~~~
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
<anon>:4:9: 4:32 note: expansion site
<anon>:3:42: 3:48 note: `asciis` moved here because it has type `collections::vec::Vec<std::ascii::Ascii>`, which is non-copyable (perhaps you meant to use clone()?)
<anon>:3 let uh_oh_we_just_moved_asciis = asciis.into_string();
^~~~~~
error: aborting due to previous error
那发生了什么?好的asciis
正被移入函数into_string
。就像上次我们在移动它之后尝试使用asciis
一样,生锈编译器会拒绝我们的代码。
答案 1 :(得分:3)
这是对“移动语义”的引用,当然基本上没有文档。对于那个很抱歉!不同之处在于,如果值移动,则不能再使用它。换句话说,这有效:
fn main() {
let x = "hello".to_string();
let y = x.to_string();
let z = x.into_string();
}
但是这个错误:
fn main() {
let x = "hello".to_string();
let z = x.into_string();
let y = x.to_string();
}
带
<anon>:5:13: 5:14 error: use of moved value: `x`
<anon>:5 let y = x.to_string();
^
<anon>:3:17: 3:18 note: `x` moved here because it has type `collections::string::String`, which is non-copyable (perhaps you meant to use clone()?)
<anon>:3 let z = x.into_string();
^
这有意义吗?