Перемещение и копирование
Присваивание в Rust перемещает владение (move), а простые типы — копируются (Copy). Разбираем, почему.
Перемещение (move) — при присваивании значения из кучи владение переходит к новой переменной, а старая становится недействительной.
Move: владение переходит
Вернёмся к аналогии с единственной книгой. Если Алиса передаёт книгу Бобу, у неё самой книги больше нет — иначе их стало бы две. В Rust присваивание значения из кучи работает так же: владение перемещается, а старая переменная становится недействительной.
fn main() {
let s1 = String::from("привет");
let s2 = s1; // владение строкой ПЕРЕМЕЩАЕТСЯ из s1 в s2
// println!("{s1}"); // ОШИБКА: value borrowed here after move
println!("{s2}"); // s2 — новый владелец, всё в порядке
}Вывод:
привет
Почему так строго? String хранит данные в куче, а сама переменная — это указатель на них. Если бы s1 и s2 остались оба действительны, оба указывали бы на одну память. Когда оба вышли бы из области видимости, Rust попытался бы освободить её дважды — классический баг «двойного освобождения». Move решает это: после перемещения старая переменная просто запрещена.
Copy: простые типы копируются
А что насчёт чисел? Они маленькие, фиксированного размера и целиком лежат на стеке. Копировать их дёшево, и никакой кучи освобождать не надо. Поэтому такие типы реализуют трейт Copy: при присваивании они копируются, и старая переменная остаётся годной.
fn main() {
let x = 5;
let y = x; // x КОПИРУЕТСЯ в y
println!("{x} {y}"); // оба доступны — это ок
}Вывод:
5 5
Типы с Copy: все целые и дробные числа, bool, char и кортежи, состоящие только из таких типов. String, Vec и всё, что владеет кучей, — не Copy, поэтому перемещаются.
Move при передаче в функцию
То же правило действует при вызове функции: передача значения по значению перемещает владение внутрь функции. После вызова исходная переменная недействительна.
fn print_and_drop(s: String) {
println!("{s}");
} // s удаляется здесь
fn main() {
let text = String::from("данные");
print_and_drop(text); // владение text уходит в функцию
// println!("{text}"); // ОШИБКА: text перемещён
}Вывод:
данные
Как вернуть владение
Функция может вернуть значение, передав владение обратно. Но постоянно перемещать значение туда-сюда неудобно. Для этого есть заимствование — следующий урок.
Клонирование, когда копия нужна
Если действительно нужна вторая независимая копия данных из кучи, её делают явно через .clone(). Это честно: вы видите, что происходит дорогое копирование.
fn main() {
let s1 = String::from("привет");
let s2 = s1.clone(); // глубокая копия: своя память в куче
println!("{s1} и {s2}"); // оба владеют своими данными
}Вывод:
привет и привет
Итог
- Присваивание данных из кучи (
String,Vec) перемещает владение; старая переменная запрещена. - Простые типы на стеке (числа,
bool,char) реализуютCopyи копируются. - Передача в функцию тоже перемещает владение;
.clone()делает явную глубокую копию.