Ответ 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
после того, как мы его переместили, компилятор ржавчины отклонит наш код.