Theme
Q017. How does ownership flow through branches, loops, and conditional initialization?
Short Answer
Rust tracks ownership separately along control-flow paths and permits a later use only when the place is definitely initialized on every path reaching it. If one branch may move a value, it is unavailable after the branch. Loops must also preserve a valid state for every possible iteration, while conditional initialization must initialize before every use.
Deep Dive
Move checking is path-sensitive but conservative. After an if, a value is usable only if no reachable branch consumed it or every consuming branch reinitialized it before control merged.
An immutable variable may be initialized in separate mutually exclusive branches as long as every path initializes it exactly once before use. If any path leaves it uninitialized, later use is rejected.
In a loop, the compiler considers that the body may execute zero, one, or many times. Moving an outer value in one iteration is rejected when another iteration or code after the loop could use the moved-from place. Control flow such as an unconditional break can sometimes prove that no second iteration is possible.
Internal Model
rustc performs dataflow analysis over the control-flow graph. At merge points it combines initialization states from incoming edges. A place is definitely initialized only when the required state holds for all relevant predecessors.
When destruction depends on a runtime branch, drop elaboration may generate a flag or equivalent branch so that cleanup runs exactly for values initialized on the executed path.
Example
rust
fn consume(_: String) {}
fn main() {
let message = String::from("event");
let should_send = true;
if should_send {
consume(message);
}
// println!("{message}"); // error: message may have been moved
let status: String;
if should_send {
status = String::from("sent");
} else {
status = String::from("retained");
}
println!("{status}");
}Common Mistakes
- Expecting the compiler to assume which runtime branch will execute.
- Forgetting that a loop body may run more than once or not at all.
- Using a conditionally initialized variable without covering every path.
Follow-ups
- Why can conditional ownership require drop flags?
- When does moving a value inside a loop compile successfully?
- How does early return affect destruction on each path?
Related
- Q003. What invariants does ownership enforce?
- Q008. How is ownership enforced and represented under the hood?
- Q014. Can a moved binding be reinitialized?
References
- Rust Reference
- rustc-dev-guide