diff --git a/SUMMARY.md b/SUMMARY.md index 225708e..3b5a04c 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -23,6 +23,7 @@ * [Transmutes](transmutes.md) * [Uninitialized Memory](uninitialized.md) * [Checked](checked-uninit.md) + * [Drop Flags](drop-flags.md) * [Unchecked](unchecked-uninit.md) * [Ownership-Oriented Resource Management](raii.md) * [Constructors](constructors.md) diff --git a/checked-uninit.md b/checked-uninit.md index e39a329..8896fc9 100644 --- a/checked-uninit.md +++ b/checked-uninit.md @@ -83,27 +83,3 @@ fn main() { 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. Otherwise the variable is exactly like new. - -This raises an interesting question with respect to `Drop`: where does Rust try -to call the destructor of a variable that is conditionally initialized? 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 set and unset. When a variable goes out of scope or is assigned -a value, it evaluates whether the current value of the variable should be dropped. -Of course, static analysis can remove these checks. If the compiler can prove that -a value is guaranteed to be either initialized or not, then it can theoretically -generate more efficient code! As such it may be desirable to structure code to -have *static drop semantics* when possible. - -As of Rust 1.0, the drop flags are actually not-so-secretly stashed in a hidden -field of any type that implements Drop. The language sets the drop flag by -overwriting the entire struct with a particular value. This is pretty obviously -Not The Fastest and causes a bunch of trouble with optimizing code. 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. - -So in general, 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 -Safe Rust lets you work with them, but you're never in danger. diff --git a/drop-flags.md b/drop-flags.md new file mode 100644 index 0000000..2d5bae6 --- /dev/null +++ b/drop-flags.md @@ -0,0 +1,78 @@ +% 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 +let mut y = x; // y was uninit +x = Box::new(0); // x was uninit +y = x; // y was init; Drop y! + // y was init; Drop y! + // x was uninit +``` + +And even branched code where all branches have the same behaviour with respect +to initialization: + +```rust +let mut x = Box::new(0); // x was uninit +if condition { + drop(x) // x gets moved out +} else { + println!("{}", x); + drop(x) // x gets moved out +} +x = Box::new(0); // x was uninit + // x was init; Drop x! +``` + +However code like this *requires* runtime information to correctly Drop: + +```rust +let x; +if condition { + x = Box::new(0); // x was uninit + println!("{}", x); +} + // x might be uninit; check the flag! +``` + +Of course, in this case it's trivial to retrieve static drop semantics: + +```rust +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. \ No newline at end of file