Перемещение
Присваивание передаёт владение между переменными:
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
fn main() {
let s1 = String::from("Hello!");
let s2 = s1;
dbg!(s2);
// dbg!(s1);
}
- Присваивание
s1вs2передаёт владение. - Когда
s1выходит из области видимости, ничего не происходит: она ничем не владеет. - Когда
s2выходит из области видимости, данные строки освобождаются.
До перемещения в s2:
После перемещения в s2:
Когда вы передаёте значение в функцию, оно присваивается параметру функции. Это передаёт владение:
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
fn say_hello(name: String) {
println!("{name}, привет!")
}
fn main() {
let name = String::from("Алиса");
say_hello(name);
// say_hello(name);
}
-
Отметьте, что это противоположность поведению по умолчанию в C++, где значение копируется, если не использовать
std::move(и определён конструктор перемещения!). -
Перемещается только владение. Генерируется ли машинный код для манипуляции самими данными, зависит от оптимизации, и такие копии агрессивно убираются.
-
Простые значения (например, целые) можно пометить как
Copy(см. следующие слайды). -
В Rust клонирование явное (с помощью
clone).
В примере say_hello:
- При первом вызове
say_hellomainотдаёт владениеname. После этогоnameвmainбольше нельзя использовать. - Память в куче, выделенная для
name, будет освобождена в конце функцииsay_hello. mainможет сохранить владение, если передаётnameкак ссылку (&name) и еслиsay_helloпринимает ссылку в качестве параметра.- Либо
mainможет передать клонnameв первом вызове (name.clone()). - Rust усложняет случайное создание копий по сравнению с C++: семантика перемещения здесь по умолчанию, а программисты должны явно создавать клоны.
Для дальнейшего изучения
Защитные копии в современном C++
Современный C++ решает эту задачу иначе:
std::string s1 = "Cpp";
std::string s2 = s1; // Дублируем данные из s1.
- Данные в куче из
s1дублируются, иs2получает собственную независимую копию. - Когда
s1иs2выходят из области видимости, каждая из них освобождает свою память.
До копирующего присваивания:
После копирующего присваивания:
Ключевые моменты:
-
C++ сделал немного другой выбор, чем Rust. Поскольку
=копирует данные, данные строки приходится клонировать. Иначе при выходе из области видимости любой из строк мы получили бы двойное освобождение. -
В C++ также есть
std::move, которым обозначают, что значение может быть перемещено. Если бы в примере былоs2 = std::move(s1), выделения памяти в куче не произошло бы. После перемещенияs1оказалась бы в корректном, но не уточнённом состоянии. В отличие от Rust, программисту разрешено продолжать использоватьs1. -
В отличие от Rust,
=в C++ может выполнять произвольный код, определённый типом, который копируется или перемещается.