Введение: Момент, когда Rust вас «не понимает»
Вы пишете на Rust. Всё идёт хорошо. Компилятор молчит. Вы чувствуете себя повелителем памяти. А потом — бац! — появляется она:
error[E0507]: cannot move out of borrowed content
--> src/main.rs:10:9
|
10 | let value = self.data;
| ^^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
Или её близкая родственница:
error[E0507]: cannot move out of `*self` which is behind a shared reference
Если вы новичок, это похоже на магическое проклятие. Если опытный — вы знаете, что это просто очередной урок от Borrow Checker (проверщика заимствований).
В этой статье мы разберём, что означают эти ошибки, почему Rust так строг, и — самое главное — как их исправить с помощью живых примеров кода. Это не скучная теория, а практический гид по выживанию в мире владения и заимствования.
Коротко о главном: Владение, заимствование и «перемещение»
Прежде чем лезть в дебри, давайте вспомним три столпа Rust:
- Владение (Ownership): У каждого значения в Rust ровно один владелец.
- Заимствование (Borrowing): Вы можете «одолжить» значение по ссылке (&T — неизменяемая, &mut T — изменяемая), не забирая владение.
- Перемещение (Move): Когда вы присваиваете значение новой переменной или передаёте в функцию без &, владение перемещается. Исходная переменная становится недействительной.
Ошибка cannot move out of borrowed content возникает именно тогда, когда вы пытаетесь переместить значение, на которое у вас есть только ссылка (&T). Rust запрещает это, потому что после перемещения ссылка станет «висячей» (dangling pointer). А Rust, как вы знаете, таких вещей не прощает.
Сценарий 1: Попытка переместить поле структуры через &self
Самый частый случай. У вас есть структура, и вы хотите вернуть одно из её полей из метода, который принимает &self.
Код с ошибкой
struct User {
name: String,
age: u32,
}
impl User {
// Пытаемся отдать name "насовсем"
fn give_name(&self) -> String {
self.name // ОШИБКА!
}
}
fn main() {
let user = User { name: "Alice".to_string(), age: 30 };
let name = user.give_name();
println!("{}", name);
}
Компилятор скажет:
error[E0507]: cannot move out of `self.name` which is behind a shared reference
Почему? self — это &User. Мы не владеем self, мы лишь одолжили его. Попытка переместить self.name (тип String, который не реализует Copy) означает, что после вызова метода владелец (user) потеряет своё поле. Но ссылка &self всё ещё существует! Rust защищает нас от катастрофы.
Как исправить?
Есть три основных пути, в зависимости от вашей задачи.
Способ 1: Клонирование (самый простой, но не всегда эффективный)
Если вам нужно именно значение, а не ссылка, и вы готовы заплатить за копирование:
fn give_name(&self) -> String {
self.name.clone() // Работает! Создаём копию.
}
Минус: clone() для String делает полную копию данных в куче. На больших строках это дорого.
Способ 2: Возврат ссылки (изменяем сигнатуру)
Если вызывающему коду достаточно ссылки на поле:
fn give_name(&self) -> &String {
&self.name // Возвращаем ссылку, перемещения нет
}
Теперь мы не перемещаем, а заимствуем. Всё честно. Вызывающий код получит &String, а не String.
Способ 3: Забираем владение (меняем &self на self)
Если метод должен «потребить» структуру, измените сигнатуру:
fn give_name(self) -> String {
self.name // Теперь self — владелец, можно перемещать
}
После вызова этого метода структура user будет перемещена и больше не доступна. Используйте, если структура больше не нужна.
Сценарий 2: Работа с Option и Result
Ещё один популярный случай — попытка достать значение из Option или Result, который находится за ссылкой.
Код с ошибкой
struct Container {
item: Option<String>,
}
impl Container {
fn take_item(&self) -> String {
self.item.unwrap() // ОШИБКА!
}
}
Метод unwrap() пытается переместить String из Option. Но self.item — это &Option<String>. Мы не можем забрать значение.
Исправление: as_ref() и cloned()
Стандартная библиотека Rust предлагает элегантные решения:
fn take_item(&self) -> Option<&String> {
self.item.as_ref() // Даёт Option<&String>
}
// Или если нужна копия:
fn take_item_cloned(&self) -> Option<String> {
self.item.clone() // Клонируем весь Option
}
Метод as_ref() превращает &Option<T> в Option<&T> без перемещения. Это ключевой паттерн в Rust.
Сценарий 3: Итерация по коллекции, которой вы не владеете
Предположим, у вас есть &Vec<String>, и вы хотите переместить элементы в другой вектор:
fn collect_names(names: &Vec<String>) -> Vec<String> {
names.into_iter().collect() // ОШИБКА!
}
Метод into_iter() на ссылке не даёт перемещать элементы. Правильный способ:
fn collect_names(names: &Vec<String>) -> Vec<String> {
names.iter().cloned().collect() // Клонируем каждый элемент
}
Или, если вам нужны ссылки:
fn collect_names_ref(names: &Vec<String>) -> Vec<&String> {
names.iter().collect()
}