Разница между ToString и IntoString

Мне интересно, какая разница между:

"some string".to_string()

и

"some string".into_string()

Похоже, что первое происходит от ToString, что совершенно ясно.

Однако последнее, кажется, происходит от IntoString, что менее понятно для меня.

Что означает consume значение? В чем разница между двумя чертами?


Дополнительная информация после некоторого поиска.

Здесь текущая реализация into_string для String. Как вы можете видеть, он только возвращает себя, поэтому выделение не производится.

Ответ 1

Переместить семантику

Что означает consume значение?

Потребление значения связано с перемещением значения. Прежде чем обсуждать различия между двумя чертами, я приведу несколько примеров того, что означает перемещение значения. Позвольте создать Vec Ascii символов: asciis.

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

Внутренне, 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 |                                    
     +----------+                                   

Uh oh, asciis указывает на свободную память! Это плохая новость, так как мы почти достигли println! asciis.

Итак, как бы мы исправили ситуацию? Ну, вот два варианта.

  • Когда мы копируем asciis в an_attempted_copy, мы можем скопировать данные, на которые указывает asciis, в недавно выделенный фрагмент памяти. Другие языки, такие как С++, делают это.
  • Вместо копирования 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

Как и ожидалось, компилятор ржавчины говорит нам, что мы пытались использовать Ascii, но Ascii было перемещенным значением; это ошибочно.

Переместить семантику (и связанные с ней темы, такие как заимствования и время жизни) - это тяжелый материал. Я только едва поцарапал поверхность здесь. Для получения дополнительной информации ржавчина на примере и fooobar.com/questions/524/... являются хорошими ресурсами.

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

Так что случилось? Well asciis перемещается в функцию into_string. Как и в прошлый раз, когда мы пытались использовать asciis после того, как мы его переместили, компилятор ржавчины отклонит наш код.

Ответ 2

Это ссылка на "семантику переноса", которая, по общему признанию, практически не содержит документации. Извини за это! Разница в том, что если значение движется, вы больше не сможете его использовать. Другими словами, это работает:

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();
                         ^

Это имеет смысл?