ToString和IntoString之间的区别

时间:2014-08-14 19:28:32

标签: rust

我想知道它们之间有什么区别:

"some string".to_string()

"some string".into_string()

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

然而,后者似乎来自IntoString,这对我来说不太清楚。

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


进行一些挖掘后的其他信息。

这里the current implementation of into_string代表String。如您所见,它只返回自身,因此不进行任何分配。

2 个答案:

答案 0 :(得分:25)

移动语义

  

consume值是什么意思?

消耗值与移动值有关。在我讨论两个特征之间的差异之前,我将给出一些移动值意味着什么的例子。让我们创建一个Vec Ascii个字符:asciis

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

InternallyVec是一个包含三个字段的结构:

  1. Vec
  2. 的长度
  3. Vec
  4. 的容量
  5. 指向由Vec管理的数据的指针。
  6. 图示,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_copyasciis的副本。在我们制作副本之后,我们的记忆可能如下所示。

    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

    那么我们如何纠正这种情况呢?嗯,这里有两个选择。

    1. 当我们将asciis复制到an_attempted_copy时,我们可以将asciis指向的数据复制到新分配的内存中。像C ++这样的其他语言可以做到这一点。
    2. 我们可以移动它而不是复制asciis!生锈就是这样。
    3. 那么搬家意味着什么?这意味着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 examplethis 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);
      }
      

      我们再次创建VecAscii个字符。然后,当我们致电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();
                         ^

这有意义吗?