通过例子学习Rust

23.1 借用检查(borrow checker)

Let's see how the compiler prevents the creation of dangling pointers via its borrow checker. To simplify the analysis and explanation, we have two additions:

  • Lifetimes has been explicitly annotated in the source code.
  • We have drawn the lifetime "lines", which span from the creation of an object to its destruction. The block scopes have also been drawn.

Note that explicit lifetime annotation on references &'foo T is not allowed by the compiler, so you must remove the lifetime part 'foo to see the "real" compiler error.

// FIXME To see the "real" compiler error, change both `&'b` and `&'e` into `&` fn main() { // `'main` starts ────────────────────────────────────────────┐ let stack_integer: int = 5; // `'a` starts ─────────────────────────┐ │ let boxed_integer = box 4; // `'b` starts ────────────────────────┐ │ │ // │ │ │ // This is a valid operation │ │ │ let ref_to_box: &'b int = &*boxed_integer; // `'c` starts ──────┐ │ │ │ // │ │ │ │ // The compiler forbids this operation, because │ │ │ │ // `ref_to_another_box` would become a dangling pointer │ │ │ │ let ref_to_another_box: &'e int = { // `'let` `'d` start ───┬─┐ │ │ │ │ let another_boxed_integer = box 3; // `'e` starts ────┐ │ │ │ │ │ │ // │ │ │ │ │ │ │ &*another_boxed_integer // │ │ │ │ │ │ │ }; // `'e` `'let` end ────────────────────────────────────┴─┘ │ │ │ │ │ // │ │ │ │ │ let invalid_dereference = *ref_to_another_box; // │ │ │ │ │ } // `'d` `'c` `'b` `'a` `'main` end ─────────────────────────────┴─┴─┴─┴─┘

The "real" compiler error is: "another_boxed_integer does not live long enough". Let's analyze why this happens:

  • stack_integer has lifetime 'a
  • boxed_integer has lifetime 'b
  • ref_to_box has lifetime 'c
  • ref_to_another_box has lifetime 'd
  • another_boxed_integer has lifetime 'e
  • 'main and 'let are the scopes of the blocks
  • When a block scope ends, all the objects declared in it get destroyed
    • 'let ends, and so does 'e
    • 'main ends, and so does 'a 'b 'c and 'd
  • ref_to_box is a valid borrow, because
    • ref_to_box has lifetime 'c
    • ref_to_box points to an object with lifetime 'b
    • 'c will never outlive 'b (this is expressed as 'c < 'b)
    • therefore ref_to_box will always point to valid data
  • ref_to_another_box is an invalid borrow, because
    • ref_to_another_box has lifetime 'd
    • ref_to_another_box points to an object with lifetime 'e
    • 'd outlives 'e
    • therefore ref_to_another_box can become a dangling pointer (it can point to destroyed data)
      • creation of dangling pointers is forbidden, so this borrow is invalid

The borrow checker will do this job for the programmer behind his/her back, to prevent him/her from (unintentionally) creating dangling pointers. Although, the programmer can be saved by the borrow checker without knowing what is a lifetime.

The programmer doesn't need to explicitly annotate lifetimes (nor understand what are lifetimes), for the borrow checker to do its job in most cases. These are the cases where explicit lifetimes are required: