split out and rework drop flags section
This commit is contained in:
parent
35b8001f68
commit
987a8687db
3 changed files with 79 additions and 24 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
78
drop-flags.md
Normal file
78
drop-flags.md
Normal file
|
|
@ -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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue