| # Drop Flags |
| |
| The examples in the previous section introduce an interesting problem for Rust. |
| We have seen that it'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? |
| |
| 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: |
| |
| ```rust |
| 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 |
| 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 efficient 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 goes out of scope; y was init; Drop y! |
| // x goes out of scope; x was uninit; do nothing. |
| ``` |
| |
| Similarly, branched code where all branches have the same behavior with respect |
| to initialization has static drop semantics: |
| |
| ```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 goes out of scope; 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 goes out of scope; 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); |
| } |
| ``` |
| |
| The drop flags are tracked on the stack. |
| In old Rust versions, drop flags were stashed in a hidden field of types that implement `Drop`. |