Try โ€‚โ€‰HackMD

False edges in match MIR lowering

I'm trying to understand them.

Code and docs

Comments says:

False edges

We don't want to have the exact structure of the decision tree be
visible through borrow checking. False edges ensure that the CFG as
seen by borrow checking doesn't encode this. False edges are added:

  • From each pre-binding block to the next pre-binding block.
  • From each otherwise block to the next pre-binding block.

The first case is set up here, the second here.

I couldn't find more explanation than that so I tried commenting out and looking at tests.

Tests

From looking at the tests, the overall idea is to make borrowck think that we're trying each arm in order despite the fact that we're often more clever than that. Also that any guard may of may not be run.

In each case, the error mentioned disappears when I disable false edges.

The pre-binding-to-pre-binding false edges

Originally added in https://github.com/rust-lang/rust/pull/45200 as "always true guards", now they do more than that.

  • From tests/ui/borrowck/or-patterns.rs
    โ€‹โ€‹  fn or_pattern_moves_all(x: (String, String)) {
    โ€‹โ€‹      match x {
    โ€‹โ€‹          (y, _) | (_, y) => (),
    โ€‹โ€‹      }
    โ€‹โ€‹      &x.1;
    โ€‹โ€‹      //~^ ERROR borrow of moved value
    โ€‹โ€‹  }
    
  • From tests/ui/borrowck/borrowck-match-already-borrowed.rs
    โ€‹โ€‹fn main() {
    โ€‹โ€‹    let mut x = 1;
    โ€‹โ€‹    let r = &mut x;
    โ€‹โ€‹    let _ = match x {
    โ€‹โ€‹        _ => 0,
    โ€‹โ€‹        y => y + 2, //~ ERROR cannot use `x` because it was mutably borrowed
    โ€‹โ€‹    };
    โ€‹โ€‹    drop(r);
    โ€‹โ€‹}
    
  • From tests/ui/nll/match-cfg-fake-edges.rs
    โ€‹โ€‹โ€‹โ€‹let x;
    โ€‹โ€‹โ€‹โ€‹// Even though x *is* always initialized, we don't want to have borrowck
    โ€‹โ€‹โ€‹โ€‹// results be based on whether patterns are exhaustive.
    โ€‹โ€‹โ€‹โ€‹match y {
    โ€‹โ€‹โ€‹โ€‹    _ if { x = 2; true } => 1,
    โ€‹โ€‹โ€‹โ€‹    _ if {
    โ€‹โ€‹โ€‹โ€‹        x; //~ ERROR used binding `x` isn't initialized
    โ€‹โ€‹โ€‹โ€‹        false
    โ€‹โ€‹โ€‹โ€‹    } => 2,
    โ€‹โ€‹โ€‹โ€‹    _ => 3,
    โ€‹โ€‹โ€‹โ€‹};
    
  • From tests/ui/nll/match-cfg-fake-edges2.rs
    โ€‹โ€‹โ€‹โ€‹fn all_previous_tests_may_be_done(y: &mut (bool, bool)) {
    โ€‹โ€‹โ€‹โ€‹    let r = &mut y.1;
    โ€‹โ€‹โ€‹โ€‹    // We don't actually test y.1 to select the second arm, but we don't want
    โ€‹โ€‹โ€‹โ€‹    // borrowck results to be based on the order we match patterns.
    โ€‹โ€‹โ€‹โ€‹    match y { //~ ERROR cannot use `y.1` because it was mutably borrowed
    โ€‹โ€‹โ€‹โ€‹        (false, true) => {},
    โ€‹โ€‹โ€‹โ€‹        (true, _) => drop(r),
    โ€‹โ€‹โ€‹โ€‹        (false, _) => {},
    โ€‹โ€‹โ€‹โ€‹    };
    โ€‹โ€‹โ€‹โ€‹}
    
  • From tests/ui/nll/match-guards-partially-borrow.rs
    โ€‹โ€‹โ€‹โ€‹fn bad_indirect_mutation_in_if_guard4(mut b: &bool) {
    โ€‹โ€‹โ€‹โ€‹    match b {
    โ€‹โ€‹โ€‹โ€‹        &_ => (),
    โ€‹โ€‹โ€‹โ€‹        &_ if {
    โ€‹โ€‹โ€‹โ€‹            b = &true; //~ ERROR cannot assign `b` in match guard
    โ€‹โ€‹โ€‹โ€‹            false
    โ€‹โ€‹โ€‹โ€‹        } => (),
    โ€‹โ€‹โ€‹โ€‹    }
    โ€‹โ€‹โ€‹โ€‹}
    

The otherwise-to-pre-binding false edges

  • From tests/ui/nll/match-cfg-fake-edges.rs
    โ€‹โ€‹โ€‹โ€‹let x = String::new();
    โ€‹โ€‹โ€‹โ€‹// Even though x *is* never moved before the use, we don't want to have
    โ€‹โ€‹โ€‹โ€‹// borrowck results be based on whether patterns are disjoint.
    โ€‹โ€‹โ€‹โ€‹match y {
    โ€‹โ€‹โ€‹โ€‹    false if { drop(x); true } => {},
    โ€‹โ€‹โ€‹โ€‹    true => drop(x), //~ ERROR use of moved value: `x`
    โ€‹โ€‹โ€‹โ€‹    false => {},
    โ€‹โ€‹โ€‹โ€‹};
    
    โ€‹โ€‹โ€‹โ€‹fn do_guards_in_order() {
    โ€‹โ€‹โ€‹โ€‹    // Like the previous case, making really sure we respect the order.
    โ€‹โ€‹โ€‹โ€‹    let [a, b, c, d]: [String; 4] = Default::default();
    โ€‹โ€‹โ€‹โ€‹    match true {
    โ€‹โ€‹โ€‹โ€‹        false if { drop(a); true } => {},
    โ€‹โ€‹โ€‹โ€‹        true  if { drop(b); true } => drop(a),
    โ€‹โ€‹โ€‹โ€‹        //~^ ERROR use of moved value: `a`
    โ€‹โ€‹โ€‹โ€‹        false if { drop(c); true } => drop(b),
    โ€‹โ€‹โ€‹โ€‹        //~^ ERROR use of moved value: `b`
    โ€‹โ€‹โ€‹โ€‹        true => drop(c),
    โ€‹โ€‹โ€‹โ€‹        //~^ ERROR use of moved value: `c`
    โ€‹โ€‹โ€‹โ€‹        _ => {},
    โ€‹โ€‹โ€‹โ€‹    };
    โ€‹โ€‹โ€‹โ€‹    let [a, b, c, d]: [String; 4] = Default::default();
    โ€‹โ€‹โ€‹โ€‹    match true {
    โ€‹โ€‹โ€‹โ€‹        true => drop(c),
    โ€‹โ€‹โ€‹โ€‹        false if { drop(c); true } => drop(b),
    โ€‹โ€‹โ€‹โ€‹        true  if { drop(b); true } => drop(a),
    โ€‹โ€‹โ€‹โ€‹        false if { drop(a); true } => {},
    โ€‹โ€‹โ€‹โ€‹        _ => {},
    โ€‹โ€‹โ€‹โ€‹    };
    โ€‹โ€‹โ€‹โ€‹}