Мне интересно узнать о «семантике перемещения» в Rust и о том, копируются ли данные при передаче права собственности. Вот демо-код:
#[derive(Debug)]
struct Foo {
name: i32,
age: i32,
}
fn main() {
let foo = Foo { name: 111, age: 1 };
let ptr_foo = std::ptr::addr_of!(foo);
println!("Address of foo: {:p}", ptr_foo);
let bar = foo;
let ptr_bar = std::ptr::addr_of!(bar);
println!("Address of bar: {:p}", ptr_bar);
}
Я изначально подумал, что «есть переменная foo, и после перемещения мы просто переименовали ее в bar», тем самым избежав необходимости копировать соответствующие ей данные.
Для дальнейшего исследования я использовал функцию отладки в VSCode с плагином под названием «Hex Editor» и обнаружил, что оба foo
и bar
(адреса памяти 0x7fffffffd7d8
и 0x7fffffffd828
) содержат идентичные данные (6F 00 00 00 01 00 00 00
).
Означает ли это, что Rust действительно выполняет операцию копирования даже во время move
, например, в данном случае копирование структуры? Может ли это поведение меняться в зависимости от того, находится ли оно в режиме выпуска?
«Перемещение» во многих языках (не только в Rust) — это семантический термин, который относится к передаче права собственности и не обязательно определяет механизм этой передачи. В Rust перемещение значения копирует данные куда-то еще, но оставляет источник значения непригодным для использования (если только этот источник позже не будет повторно инициализирован действительным значением или если перемещаемый тип не реализует Copy
).
В Rust это реализовано с помощью поразрядного копирования исходного значения в место назначения. На более высоких уровнях оптимизации эту копию можно исключить, а может и нет.
Обратите внимание, что копия распространяется только на значения, содержащиеся непосредственно в перемещаемом значении. Например, при перемещении Vec
копируется указатель данных, длина и емкость Vec
, но не фактические элементы Vec
, которые находятся за этим указателем. Это позволяет вам перемещать любой Vec
за одну и ту же стоимость — перемещение огромного Vec
и пустого Vec
требует копирования одинакового количества материала (3 значения размером с указатель).
В вашем коде использование адреса foo
и bar
, скорее всего, не позволит оптимизатору фактически исключить копию, поскольку он не может объединить их расположение в памяти. Если сделать это и вернуть один и тот же адрес для обеих переменных, это нарушит правило как если бы.
Другими словами:
Если вы пытаетесь увидеть, как будет компилироваться код, не меняйте этот код для проверки значений изнутри кода, иначе вы почти наверняка каким-то образом измените поведение оптимизатора. Вместо этого посмотрите на сгенерированный машинный код.
«копирование» и «глубокое копирование» — это не одно и то же, на самом деле они совершенно разные, и ваша путаница, похоже, начинается именно с этого. Я бы предложил удалить все ссылки на то, что вам сказал ChatGPT, и вместо этого просто описать свое понимание.