|
|
|
@ -2,12 +2,25 @@
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
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?
|
|
|
|
|
|
|
|
|
|
Note that this is not a problem that all assignments need worry about. In
|
|
|
|
|
particular, assigning through a dereference unconditionally drops, and assigning
|
|
|
|
|
in a `let` unconditionally *doesn't* drop:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
let mut x = Box::new(0); // let makes a fresh variable, so never need to drop
|
|
|
|
|
let y = &mut x;
|
|
|
|
|
*y = Box::new(1); // Deref assumes the referent is initialized, so always drops
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This is only a problem when overwriting a previously initialized variable or
|
|
|
|
|
one of its subfields.
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
@ -15,7 +28,7 @@ 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-
|
|
|
|
|
compiler can theoretically generate more efficient code! For instance, straight-
|
|
|
|
|
line code has such *static drop semantics*:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
@ -23,8 +36,8 @@ 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.
|
|
|
|
|
// y goes out of scope; y was init; Drop y!
|
|
|
|
|
// x goes out of scope; x was uninit; do nothing.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
And even branched code where all branches have the same behaviour with respect
|
|
|
|
@ -40,7 +53,7 @@ if condition {
|
|
|
|
|
drop(x) // x gets moved out; make x uninit.
|
|
|
|
|
}
|
|
|
|
|
x = Box::new(0); // x was uninit; just overwrite.
|
|
|
|
|
// x was init; Drop x!
|
|
|
|
|
// x goes out of scope; x was init; Drop x!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
However code like this *requires* runtime information to correctly Drop:
|
|
|
|
@ -52,7 +65,8 @@ if condition {
|
|
|
|
|
x = Box::new(0); // x was uninit; just overwrite.
|
|
|
|
|
println!("{}", x);
|
|
|
|
|
}
|
|
|
|
|
// x *might* be uninit; check the flag!
|
|
|
|
|
// x goes out of scope; x *might* be uninit;
|
|
|
|
|
// check the flag!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Of course, in this case it's trivial to retrieve static drop semantics:
|
|
|
|
@ -66,10 +80,10 @@ if condition {
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
field of any type that implements Drop. Rust sets the drop flag by overwriting
|
|
|
|
|
the *entire* value with a particular bit pattern. 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
|
|
|
|
|