Что такое модель ownership в Rust и какую проблему она решает?
Ownership — система управления памятью без GC: каждое значение имеет одного владельца, при выходе из scope автоматически вызывается Drop. Это исключает use-after-free, double-free и data races на этапе компиляции.
Что такое ownership
Ownership — система правил управления памятью в Rust, которая работает на этапе компиляции без garbage collector. У каждого значения есть ровно один владелец (owner). Когда владелец выходит из области видимости, значение автоматически освобождается через деструктор (Drop).
Какую проблему решает ownership
В C/C++ программист управляет памятью вручную, что приводит к двум классам ошибок:
- Use-after-free — обращение к памяти после её освобождения.
- Double-free — освобождение одного блока памяти дважды.
- Memory leak — забытое освобождение памяти.
- Data races — одновременный доступ к данным из нескольких потоков.
Языки с GC (Java, Python, Go) решают проблему через рантайм, платя ценой пауз сборщика мусора. Rust решает её статически через ownership: компилятор гарантирует корректность на этапе сборки.
Три правила ownership
- У каждого значения есть ровно один owner.
- В каждый момент времени существует только один owner.
- Когда owner выходит из scope, значение уничтожается.
fn main() {
let s1 = String::from("hello"); // s1 — owner строки
let s2 = s1; // ownership перешёл к s2 (move)
// println!("{s1}"); // ОШИБКА: s1 больше не владеет строкой
println!("{s2}"); // OK
} // s2 выходит из scope, String автоматически освобождается
// Move semantics для heap-данных
fn takes_ownership(s: String) {
println!("{s}"); // s принадлежит этой функции
} // s уничтожается здесь
// Copy semantics для stack-данных (i32, bool, f64, ...)
fn main2() {
let x = 5;
let y = x; // x копируется (Copy trait), оба живут
println!("x={x}, y={y}"); // OK
}
Освобождение ресурсов через Drop
struct FileHandle {
path: String,
}
impl Drop for FileHandle {
fn drop(&mut self) {
println!("Закрываем файл: {}", self.path);
// Реальный код: закрыть file descriptor
}
}
fn process() {
let f = FileHandle { path: "/tmp/data.txt".to_string() };
// работаем с f...
} // Drop::drop вызывается автоматически здесь
// Файл закрыт, ресурсы освобождены — без явного close()
Подводные камни
- Move по умолчанию неожиданен для разработчиков с Python/Java-фоном, где присваивание всегда создаёт ссылку.
- Деструктор (
drop) вызывается в обратном порядке объявления локальных переменных — важно для ресурсов с зависимостями. std::mem::forget(val)предотвращает вызов Drop — может привести к утечкам ресурсов при неправильном использовании.- Типы с
Copyне перемещаются, а копируются — это включает все примитивы, но не String, Vec, Box. - Ownership не защищает от logical memory leaks: циклические
Arcне освобождаются автоматически. - FFI через
Box::into_raw«отвязывает» ownership — дальнейшее освобождение вручную черезBox::from_raw. - Деструкторы могут паниковать — это UB в Rust если паника происходит во время раскрутки стека от другой паники.
- Partial moves из struct возможны, но оставшиеся поля нельзя использовать до полного перемещения.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.