|
|
|
% Checked Uninitialized Memory
|
|
|
|
|
|
|
|
Like C, all stack variables in Rust are uninitialized until a value is
|
|
|
|
explicitly assigned to them. Unlike C, Rust statically prevents you from ever
|
|
|
|
reading them until you do:
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
fn main() {
|
|
|
|
let x: i32;
|
|
|
|
println!("{}", x);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```text
|
|
|
|
src/main.rs:3:20: 3:21 error: use of possibly uninitialized variable: `x`
|
|
|
|
src/main.rs:3 println!("{}", x);
|
|
|
|
^
|
|
|
|
```
|
|
|
|
|
|
|
|
This is based off of a basic branch analysis: every branch must assign a value
|
|
|
|
to `x` before it is first used. Interestingly, Rust doesn't require the variable
|
|
|
|
to be mutable to perform a delayed initialization if every branch assigns
|
|
|
|
exactly once. However the analysis does not take advantage of constant analysis
|
|
|
|
or anything like that. So this compiles:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
fn main() {
|
|
|
|
let x: i32;
|
|
|
|
|
|
|
|
if true {
|
|
|
|
x = 1;
|
|
|
|
} else {
|
|
|
|
x = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
println!("{}", x);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
but this doesn't:
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
fn main() {
|
|
|
|
let x: i32;
|
|
|
|
if true {
|
|
|
|
x = 1;
|
|
|
|
}
|
|
|
|
println!("{}", x);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```text
|
|
|
|
src/main.rs:6:17: 6:18 error: use of possibly uninitialized variable: `x`
|
|
|
|
src/main.rs:6 println!("{}", x);
|
|
|
|
```
|
|
|
|
|
|
|
|
while this does:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
fn main() {
|
|
|
|
let x: i32;
|
|
|
|
if true {
|
|
|
|
x = 1;
|
|
|
|
println!("{}", x);
|
|
|
|
}
|
|
|
|
// Don't care that there are branches where it's not initialized
|
|
|
|
// since we don't use the value in those branches
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Of course, while the analysis doesn't consider actual values, it does
|
|
|
|
have a relatively sophisticated understanding of dependencies and control
|
|
|
|
flow. For instance, this works:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
let x: i32;
|
|
|
|
|
|
|
|
loop {
|
|
|
|
// Rust doesn't understand that this branch will be taken unconditionally,
|
|
|
|
// because it relies on actual values.
|
|
|
|
if true {
|
|
|
|
// But it does understand that it will only be taken once because
|
|
|
|
// we unconditionally break out of it. Therefore `x` doesn't
|
|
|
|
// need to be marked as mutable.
|
|
|
|
x = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// It also knows that it's impossible to get here without reaching the break.
|
|
|
|
// And therefore that `x` must be initialized here!
|
|
|
|
println!("{}", x);
|
|
|
|
```
|
|
|
|
|
|
|
|
If a value is moved out of a variable, that variable becomes logically
|
|
|
|
uninitialized if the type of the value isn't Copy. That is:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
fn main() {
|
|
|
|
let x = 0;
|
|
|
|
let y = Box::new(0);
|
|
|
|
let z1 = x; // x is still valid because i32 is Copy
|
|
|
|
let z2 = y; // y is now logically uninitialized because Box isn't Copy
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
However reassigning `y` in this example *would* require `y` to be marked as
|
|
|
|
mutable, as a Safe Rust program could observe that the value of `y` changed:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
fn main() {
|
|
|
|
let mut y = Box::new(0);
|
|
|
|
let z = y; // y is now logically uninitialized because Box isn't Copy
|
|
|
|
y = Box::new(1); // reinitialize y
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Otherwise it's like `y` is a brand new variable.
|