Проверяемая неинициализированная память

Как и в Си, в Rust все переменные на стеке не инициализированы до тех пор пока им явно не присвоено значение. В отличие от Си Rust статически ограничивает их чтение, пока вы не сделаете это:

fn main() {
    let x: i32;
    println!("{}", x);
}
  |
3 |     println!("{}", x);
  |                    ^ use of possibly uninitialized `x`

Все основывается на базовом анализе веток: каждая ветка должна присвоить x значение до его первого использования.

Интересно, что Rust не требует, чтобы переменная была изменяемой, чтобы выполнить отложенную инициализацию, если каждая ветка присваивает значение лишь однажды. В то же время такой анализ не использует анализ констант или что-либо подобное. Поэтому это компилируется:

fn main() {
    let x: i32;

    if true {
        x = 1;
    } else {
        x = 2;
    }

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

а это нет:

fn main() {
    let x: i32;
    if true {
        x = 1;
    }
    println!("{}", x);
}
  |
6 |     println!("{}", x);
  |                    ^ use of possibly uninitialized `x`

хотя это тоже компилируется:

fn main() {
    let x: i32;
    if true {
        x = 1;
        println!("{}", x);
    }
    // Не обращайте внимания на то, что есть еще ветки, в которых x не
    // инициализирована, ведь мы не используем в этих ветках ее значение
}

Несмотря на то, что в анализе не участвуют настоящие значения, компилятор довольно хорошо понимает зависимости и поток выполнения. Например, это работает:


#![allow(unused)]
fn main() {
let x: i32;

loop {
    // Rust не понимает, что эта ветка безоговорочно выполнится,
    // потому что это зависит от настоящих значений.
    if true {
        // Но он понимает, что попадет сюда лишь один раз, потому что
        // мы однозначно выходим отсюда. Поэтому `x` не надо помечать
        // изменяемым.
        x = 0;
        break;
    }
}
// Он также понимает, что невозможно добраться сюда, не достигнув break.
// И, следовательно, `x` должна быть уже инициализирована здесь!
println!("{}", x);
}

Если переменная перестаёт владеть значением, эта переменная становится логически неинициализированной, если только тип значения не реализует Copy. Это означает:

fn main() {
    let x = 0;
    let y = Box::new(0);
    let z1 = x; // x остается в силе из-за того, что i32 реализует Copy
    let z2 = y; // y теперь логически не инициализирована, потому что Box не
                // реализует Copy
}

Но переприсваивание y в этом примере потребует, чтобы y была помечена изменяемой, дабы программа на Безопасном Rust могла заметить, что значение y поменялось:

fn main() {
    let mut y = Box::new(0);
    let z = y; // y теперь логически не инициализирована, потому что Box не
               // реализует Copy
    y = Box::new(1); // переинициализация y
}

Иначе y станет абсолютно новой переменной.