Как исправить ошибку "use of moved value" в Rust?
Понимание владения в Rust
Ошибка "use of moved value" — одна из самых частых у новичков в Rust. Она связана с фундаментальной концепцией языка — владением (ownership). В Rust у каждого значения есть ровно один владелец. Когда вы присваиваете значение другой переменной или передаёте его в функцию, владение перемещается (move), и исходная переменная становится недействительной.
Почему это происходит?
В вашем первом примере:
let s = String::from("привет");
let t = s; // владение перемещается из s в t
println!("{}", s); // ❌ s больше не владеет данными
Тип String не реализует трейт Copy (в отличие от простых типов вроде i32 или bool). Поэтому присваивание let t = s — это перемещение, а не копирование. После перемещения s становится недействительной.
Три способа решения
1. Клонирование (если копирование допустимо)
Если вам нужно оставить исходные данные нетронутыми, можно явно клонировать значение:
let s = String::from("привет");
let t = s.clone(); // создаём глубокую копию
println!("{}", s); // ✅ работает
println!("{}", t); // ✅ тоже работает
Но будьте осторожны: clone() может быть дорогим для больших структур данных.
2. Заимствование (передача по ссылке)
Самый эффективный способ — передать ссылку на данные, а не сами данные. Ссылка не перемещает владение:
fn process(s: &String) -> &String {
// используем s, но не забираем владение
s
}
fn main() {
let my_string = String::from("тест");
let result = process(&my_string); // передаём ссылку
println!("{}", my_string); // ✅ my_string всё ещё владеет данными
}
Обратите внимание: функция принимает &String (ссылку), а не String. Вызов process(&my_string) создаёт ссылку, не перемещая владение.
3. Возврат владения (если функция должна модифицировать)
Если функция должна изменить строку и вернуть её обратно, можно вернуть владение:
fn process(mut s: String) -> String {
s.push_str(" обработан");
s // возвращаем владение обратно
}
fn main() {
let my_string = String::from("тест");
let my_string = process(my_string); // перемещаем, но сразу получаем обратно
println!("{}", my_string); // ✅ теперь my_string снова владеет
}
Правило большого пальца
- Если функция только читает данные — используйте
&T(неизменяемую ссылку). - Если функция изменяет данные — используйте
&mut T(изменяемую ссылку). - Если функция становится владельцем данных (например, сохраняет их в коллекции) — принимайте
Tпо значению.
Пример с изменяемой ссылкой
fn add_exclamation(s: &mut String) {
s.push('!');
}
fn main() {
let mut my_string = String::from("привет");
add_exclamation(&mut my_string);
println!("{}", my_string); // "привет!"
}
Изменяемая ссылка &mut T позволяет функции модифицировать данные, но не забирает владение.
Освоив эти три подхода, вы перестанете бояться ошибки "use of moved value". Помните: Rust защищает вас от двойного освобождения памяти и гонок данных — это плата за безопасность, которая окупается надёжностью кода.
<<Альтернативный подход: использование трейта Borrow
Если вы работаете с обобщённым кодом и не хотите жёстко привязываться к конкретному типу, можно использовать трейт Borrow из стандартной библиотеки. Это позволяет функциям принимать как владельца, так и ссылку:
use std::borrow::Borrow;
fn process<T: Borrow<str>>(data: T) -> String {
let s: &str = data.borrow();
format!("Обработано: {}", s)
}
fn main() {
let owned = String::from("тест");
let result = process(&owned); // можно передать ссылку
println!("{}", owned); // ✅ владение не перемещено
let borrowed = "статическая строка";
let result2 = process(borrowed); // можно передать &str
}
Этот подход особенно полезен в библиотеках, где вы хотите дать пользователю гибкость: передавать как владеющие строки String, так и срезы &str. Функция borrow() возвращает ссылку на внутренние данные, не перемещая владение. Это элегантный способ избежать "use of moved value" в обобщённом коде, сохраняя при этом производительность.