Владение и время жизни

Владение - это пробивная особенность Rust. Оно позволяет Rust быть полностью безопасным по памяти и эффективным, избегая сборки мусора. Перед тем как детально разобрать систему владения, мы объясним предпосылки такого дизайна.

Мы подразумеваем, что вы принимаете, что сборщик мусора - не всегда самое оптимальное решение, и что желательно в некоторых случаях управлять памятью вручную. Если вы это не принимаете, возможно, Rust не заинтересует вас.

Несмотря на ваши чувства к GC, он является огромным благом, позволяющим делать код безопасным. Вам никогда не нужно волноваться, что некоторые вещи удалятся слишком рано (хотя нужно ли вам по-прежнему иметь ссылку на них в этом случае - это другой вопрос...). Это распространённая проблема, с которой приходится иметь дело программам на C и C++. Посмотрите на эту простую ошибку, которую каждый из нас когда-то делал в языке без GC:


#![allow(unused)]
fn main() {
fn as_str(data: &u32) -> &str {
    // compute the string
    let s = format!("{}", data);

    // О НЕТ! Мы возвращаем ссылку на что-то,
    // что существует только в этой функции!
    // Висячий указатель! Используется после освобождения! Увы и ах!
    // (Rust не компилирует этот код)
    &s
}
}

Вот что именно должна решать система владения в Rust. Rust знает область видимости, в которой живёт &s, и поэтому не даст выйти из неё. Однако это простой случай, который, скорее всего, сможет поймать даже компилятор Си. Все становится сложнее, когда код растёт и указатели передаются в различные функции. В конечном счёте, компилятор Си не сможет выполнить анализ областей видимости, чтобы доказать, что ваш код сломан. Ему придётся заставить себя принять вашу программу, предполагая, что она правильна.

Этого никогда не произойдёт в Rust. Программист должен доказать компилятору, что ничего не сломается.

Конечно, рассказ о владении в Rust гораздо сложнее, чем просто проверка, что ссылка не выходит за область видимости того, на что она ссылается. Потому что доказать, что указатели всегда правильны, гораздо сложнее. Например, в этом коде


#![allow(unused)]
fn main() {
let mut data = vec![1, 2, 3];
// получаем внутреннюю ссылку
let x = &data[0];

// О НЕТ! `push` заставляет пересчитать занимаемое место `data`.
// Висячий указатель! Используется после освобождения! Увы и ах!
// (Rust не компилирует этот код)
data.push(4);

println!("{}", x);
}

обычный анализ областей видимости не сможет поймать ошибку, потому что data на самом деле живёт столько, сколько надо. Но она поменялась в то время, когда у нас есть ссылка на неё. Вот поэтому Rust требует, чтобы любые ссылки замораживали объекты, на которые ссылаются, и владельцев этих объектов.