ПрограммированиеBackend разработчик

В чём разница между использованием move и borrow при передаче переменных в функции в Rust?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Когда переменная передаётся в функцию, она может быть либо передана по ссылке (borrow, с помощью & или &mut), либо перемещена (move, без ссылки).

Borrow: передаётся ссылка на данные. Данные остаются доступны после вызова функции, но для неизменяемой ссылки нельзя менять содержимое, для изменяемой — может быть только 1 активная ссылка.

fn read_length(s: &String) -> usize { s.len() }

Move: переменная целиком "переезжает" в функцию. После передачи вы не сможете использовать исходную переменную — она была перемещена, и любая попытка обращения приведёт к ошибке компиляции.

fn destroy(s: String) { println!("{}", s); } // s будет уничтожена при выходе let s = String::from("world"); destroy(s); // s больше нельзя использовать

Это предотвращает появление двойного освобождения памяти и других ошибок владения.

Вопрос с подвохом

Могу ли я использовать переменную после того, как она была передана в функцию по значению (move)?

Нет! После передачи переменной по значению — например, String — исходная переменная становится недействительной:

let s = String::from("abc"); consume(s); // s больше не валидна здесь println!("{}", s); // ошибка компиляции

Многие путают это с поведением типов с Copy (например, i32), где переменная остаётся валидной после передачи.

Примеры реальных ошибок из-за незнания тонкостей темы


История

Молодой программист написал функцию обработки строки, которая принимала String, а не &String. В итоге исходная строка после вызова функции становилась недоступной, что привело к двойной загрузке и дополнительному выделению памяти в сервере логирования.


История

В одном микросервисе критичный ресурс передавался в разные потоки через move, после чего попытки доступа к этому ресурсу в основном потоке приводили к ошибке компиляции. Пришлось срочно рефакторить архитектуру, чтобы ресурс передавался по ссылке или через обёртки типа Arc.


История

Во внутреннем парсере событий при обработке данных переменная оперативно забиралась (move) в замыкание, после чего обращения к ней вне closure вызывали компиляционные ошибки. Проблему заметили только при ревью — был введён стиль обязательного использования borrow для данных, которые должны жить дольше локальной функции.