mirror of https://github.com/rust-lang/nomicon
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
82 lines
3.3 KiB
82 lines
3.3 KiB
% Drop Flags
|
|
|
|
The examples in the previous section introduce an interesting problem for Rust.
|
|
We have seen that's possible to conditionally initialize, deinitialize, and
|
|
*reinitialize* locations of memory totally safely. For Copy types, this isn't
|
|
particularly notable since they're just a random pile of bits. However types
|
|
with destructors are a different story: Rust needs to know whether to call a
|
|
destructor whenever a variable is assigned to, or a variable goes out of scope.
|
|
How can it do this with conditional initialization?
|
|
|
|
It turns out that Rust actually tracks whether a type should be dropped or not
|
|
*at runtime*. As a variable becomes initialized and uninitialized, a *drop flag*
|
|
for that variable is toggled. When a variable *might* need to be dropped, this
|
|
flag is evaluated to determine if it *should* be dropped.
|
|
|
|
Of course, it is *often* the case that a value's initialization state can be
|
|
*statically* known at every point in the program. If this is the case, then the
|
|
compiler can theoretically generate more effecient code! For instance, straight-
|
|
line code has such *static drop semantics*:
|
|
|
|
```rust
|
|
let mut x = Box::new(0); // x was uninit; just overwrite.
|
|
let mut y = x; // y was uninit; just overwrite and make x uninit.
|
|
x = Box::new(0); // x was uninit; just overwrite.
|
|
y = x; // y was init; Drop y, overwrite it, and make x uninit!
|
|
// y was init; Drop y!
|
|
// x was uninit; do nothing.
|
|
```
|
|
|
|
And even branched code where all branches have the same behaviour with respect
|
|
to initialization:
|
|
|
|
```rust
|
|
# let condition = true;
|
|
let mut x = Box::new(0); // x was uninit; just overwrite.
|
|
if condition {
|
|
drop(x) // x gets moved out; make x uninit.
|
|
} else {
|
|
println!("{}", x);
|
|
drop(x) // x gets moved out; make x uninit.
|
|
}
|
|
x = Box::new(0); // x was uninit; just overwrite.
|
|
// x was init; Drop x!
|
|
```
|
|
|
|
However code like this *requires* runtime information to correctly Drop:
|
|
|
|
```rust
|
|
# let condition = true;
|
|
let x;
|
|
if condition {
|
|
x = Box::new(0); // x was uninit; just overwrite.
|
|
println!("{}", x);
|
|
}
|
|
// x *might* be uninit; check the flag!
|
|
```
|
|
|
|
Of course, in this case it's trivial to retrieve static drop semantics:
|
|
|
|
```rust
|
|
# let condition = true;
|
|
if condition {
|
|
let x = Box::new(0);
|
|
println!("{}", x);
|
|
}
|
|
```
|
|
|
|
As of Rust 1.0, the drop flags are actually not-so-secretly stashed in a hidden
|
|
field of any type that implements Drop. Rust sets the drop flag by
|
|
overwriting the *entire* value with a particular byte. This is pretty obviously
|
|
Not The Fastest and causes a bunch of trouble with optimizing code. It's legacy
|
|
from a time when you could do much more complex conditional initialization.
|
|
|
|
As such work is currently under way to move the flags out onto the stack frame
|
|
where they more reasonably belong. Unfortunately, this work will take some time
|
|
as it requires fairly substantial changes to the compiler.
|
|
|
|
Regardless, Rust programs don't need to worry about uninitialized values on
|
|
the stack for correctness. Although they might care for performance. Thankfully,
|
|
Rust makes it easy to take control here! Uninitialized values are there, and
|
|
you can work with them in Safe Rust, but you're *never* in danger.
|