Проверяемая неинициализированная память
Как и в Си, в 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
станет абсолютно новой переменной.