I worked on a small issue for Mozilla’s Servo project recently and though it was a four line change I learned something interesting about borrowing in Rust within a match statement.

Consider the following code:

// Let's say baz() returns a Result.
let m = faz();

// At the end of this, equivalent of:
//   let mut y = z.bar(),
//   let x = z.foo(),
let (mut y, x) = match m {
    Ok(z): (z.bar(), z.foo()),
    Err(err): println!("Error: {:?}", err),
}

fizz(x);
buzz(&mut y);

Does this work?

The answer is no: the borrow checker yells at you and says you try to create an immutable borrow of z after creating a mutable borrow that hasn’t been returned.

What’s happening?

  1. z.bar() does a mutable borrow of z so it can make mutable variable y.
  2. z.foo() (tries to) make an immutable borrow of z so it can make immutable variable x.
  3. At the end of the match statement, the mutable borrow is returned.

This can’t happen because borrowing rules say, either you get 1 mutable reference or 1+ immutable references. Not both.

What fixed this code? Reordering!!

// Let's say baz() returns a Result.
let m = faz();

// At the end of this, equivalent of:
//   let x = z.foo(),
//   let mut y = z.bar(),
let (x, mut y) = match m {
    Ok(z): (z.foo(), z.bar()),
    Err(err): println!("Error: {:?}", err),
}

fizz(x);
buzz(&mut y);

Now it works. Because:

  1. z.foo() does an immutable borrow of z so it can make immutable variable z.
  2. Instead of returning this immutable borrow at the end of the match scope, it returns it immediately when z.foo() returns.
  3. z.bar() is now able to do a mutable borrow, which returns at the end of the match scope.

So the big question is, why does the mutable borrow only return at the end of the match scope? Why can’t it return the borrow after z.bar() returns?