关于rust:ToString和IntoString之间的区别

Difference between ToString and IntoString

我想知道两者之间有什么区别:

1
"some string".to_string()

1
"some string".into_string()

前者似乎来自ToString,这很清楚。

但是,后者似乎来自IntoString,这对我来说还不太清楚。

consume值是什么意思?这两个特征有什么区别?

深入研究后的其他信息。

这是Stringinto_string当前实现。如您所见,它仅返回自身,因此没有分配。


移动语义

What does it mean to consume a value?

使用值与移动值有关。在讨论两个特征之间的差异之前,我将举一些例子说明移动值的含义。让我们创建一个由Ascii个字符组成的Vecasciis

1
2
3
4
fn main() {
    let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
    println!("{}", asciis);
}

内部,Vec是具有三个字段的结构:

  • Vec的长度。
  • Vec的容量。
  • 指向Vec管理的数据的指针。
  • 从图片上来说,Vec的内存布局及其所管理的数据可能看起来像这样。

    1
    2
    3
    4
    5
    6
    7
    8
    Stack: asciis           Heap:
         +----------+            +----------+
    0xF0 | data     | ----> 0xA0 | 'h'      |
         +----------+            +----------+
    0xF4 | length   |       0xA1 | 'i'      |
         +----------+            +----------+
    0xF8 | capacity |
         +----------+

    当我们的Vec超出范围时,它将释放它正在管理的内存。释放的内存对我们来说是垃圾。访问释放的内存将是错误的。这看起来类似于以下内容。 Vec消失了,堆上的内存已被释放。

    1
    2
    3
    4
    5
    6
                            Heap:
                                 +----------+
                            0xA0 | GARBAGE  |
                                 +----------+
                            0xA1 | GARBAGE  |
                                 +----------+

    现在,让我们回到我们的代码,并尝试制作asciis的副本。

    1
    2
    3
    4
    5
    6
    7
    fn main() {
        let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
        {
            let an_attempted_copy = asciis;
        }
        println!("{}", asciis);
    }

    让我们猜测an_attempted_copyasciis的副本。制作完副本后,我们的内存可能类似于以下内容。

    1
    2
    3
    4
    5
    6
    7
    8
    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指向的数据被释放。

    1
    2
    3
    4
    5
    6
    7
    8
    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

    1
    2
    3
    4
    5
    6
    7
    fn main() {
        let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
        {
            let actually_a_move = asciis;
        }
        println!("{}", asciis);
    }

    现在,让我们进入actually_a_move之后立即绘制内存布局。

    1
    2
    3
    4
    5
    6
    7
    8
    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!时会发生什么呢?我们收到以下错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    :6:24: 6:30 error: use of moved value: `asciis`
    :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!
    :6:9: 6:32 note: expansion site
    :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)
    :4             let actually_a_move = asciis;
                             ^~~~~~~~~~~~~~~
    error: aborting due to previous error

    正如预期的那样,rust编译器告诉我们我们正在尝试使用Ascii,但是Ascii是移动的值;这是错误的。

    移动语义(以及诸如借用和生命周期之类的相关主题)是很困难的事情。我只是在这里勉强刮过表面。有关更多信息,以示例为例的锈以及这个stackoverflow问题都是不错的资源。

    to_stringinto_string

    What is the difference between the two traits?

    现在,我已经探讨了使用或移动值的概念,让我们来了解两个特征之间的区别。我们首先来看一下to_string

    的类型签名。

    1
    fn to_string(&self) -> String;

    此函数引用self并返回新的String供我们使用。我没有讨论参考文献以及参考文献如何影响运动,但是当我说这里没有动静时,请相信我。

    现在让我们来看一下into_string的类型签名。

    1
    fn into_string(self) -> String;

    此功能未引用self。而是将self移到函数中。

    那么这种差异的含义是什么?让我们看一个例子。

    1
    2
    3
    4
    5
    fn main() {
        let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
        let no_moves_here = asciis.to_string();
        println!("{}", asciis);
    }

    我们再次创建VecAscii个字符。然后,当我们调用asciis.to_string()时,将创建一个全新的String,并且永不移动asciis。该代码将按您期望的方式构建和运行,并输出[h, i]。现在,让我们使用into_string

    1
    2
    3
    4
    5
    fn main() {
        let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
        let uh_oh_we_just_moved_asciis = asciis.into_string();
        println!("{}", asciis);
    }

    这是我们尝试构建此代码时收到的错误消息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    :4:24: 4:30 error: use of moved value: `asciis`
    :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!
    :4:9: 4:32 note: expansion site
    :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()?)
    :3         let uh_oh_we_just_moved_asciis = asciis.into_string();
                                                      ^~~~~~
    error: aborting due to previous error

    那怎么了? asciis井将移至函数into_string中。就像上次在移动asciis之后尝试使用asciis一样,rust编译器将拒绝我们的代码。


    这是对"移动语义"的引用,该语言基本上没有任何文档。对于那个很抱歉!所不同的是,如果值移动,则无法使用它。换句话说,这有效:

    1
    2
    3
    4
    5
    6
    fn main() {
        let x ="hello".to_string();

        let y = x.to_string();
        let z = x.into_string();
    }

    但此错误:

    1
    2
    3
    4
    5
    6
    fn main() {
        let x ="hello".to_string();

        let z = x.into_string();
        let y = x.to_string();
    }

    1
    2
    3
    4
    5
    6
    :5:13: 5:14 error: use of moved value: `x`
    :5     let y = x.to_string();
                         ^
    :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()?)
    :3         let z = x.into_string();
                             ^

    这有意义吗?