1

Here is a sample:

struct X(u32);

impl X {
    fn f(&mut self, v: u32) {}
}

fn main() {
    let mut x = X(42);

    // works
    let v = x.0;
    x.f(v);

    // cannot use `x.0` because it was mutably borrowed
    x.f(x.0);
}

(Rust playground)

error[E0503]: cannot use `x.0` because it was mutably borrowed
  --> src/main.rs:16:9
   |
16 |     x.f(x.0);
   |     -   ^^^ use of borrowed `x`
   |     |
   |     borrow of `x` occurs here

What is the reason why x.f(x.0) does not work? x.0 is passed as an argument, bound to the v parameter, of type u32: there is absolutely no possibility that the function body access x.0 through the parameter.

Moreover, it seems very weird to me that:

f(something);

doesn't work, while:

v = something;
f(v);

works.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
rom1v
  • 2,752
  • 3
  • 21
  • 47
  • 4
    It's a known deficiency of the borrow checker, for which the use of a temporary variable is a workaround. Google non-lexical lifetimes for more details. – user4815162342 May 18 '17 at 19:37
  • Thank you for the hint. The most related article is about "nested method calls": http://smallcultfollowing.com/babysteps/blog/2017/03/01/nested-method-calls-via-two-phase-borrowing/ – rom1v May 19 '17 at 07:25

1 Answers1

2

When you do x.f(x.0), you have borrowed x to provide the &mut self to f before trying to borrow x again to get a reference to x.0. It is not possible to refer to x.0 twice at the same time. That is, the method f can't have both mutable access to x via &mut self (which includes x.0) and a seemingly immutable reference to x.0 at the same time.

When using a temporary variable, you actually get a copy of the value; this means you no longer refer to that int in X but 42. That's allowed.

Regarding the "non-lexical lifetimes" comments: Since f takes a good old u32 instead of a reference to it, x.f(x.0) should basically be equivalent to x.f(42), because the compiler can let go of x after getting the value out of x.0 and then mut-borrow x again to provide &mut self to f. However, the compiler determines the lifetimes and their requirements very early on during compilation; lifetimes are therefor currently broader than they have to be. rustc is currently unable to determine that the borrow on x due to the x.0 argument has ended before borrowing x for &mut self. There is work underway to fix this.

user2722968
  • 13,636
  • 2
  • 46
  • 67
  • 1
    Why doesn't the compiler evaluate the arguments first, so that when it borrows `x` in `x.0`, it has not borrowed `x` in `x.f` yet? That is the expected order of evaluation, isn't it? – rom1v May 18 '17 at 20:48
  • 1
    Because by the time the compiler determines lifetimes, it knows nothing about the actual structure of the program yet: Lifetimes are currently determined by their "lexical scope", most basically by how the text is written. See [here](http://smallcultfollowing.com/babysteps/blog/2016/04/27/non-lexical-lifetimes-introduction/#problem-case-2-conditional-control-flow) for a good example for where this leads to warts. – user2722968 May 18 '17 at 20:56
  • 2
    @rom1v What about `foo().f(bar())`? If `foo` and `bar` were functions that had side effects, which would you expect to happen first? What about `X::f(foo(), bar())`? `x.f(x.0)` is an example where the "correct" behavior is obvious, but generalizing it -- without creating ambiguity, introducing arbitrary limitations, or breaking backwards compatibility -- is far from trivial. – trent May 18 '17 at 22:34
  • @user2722968 Thank you, this explains why the current implementation works the way it does. But now, it seems to me that the problem is more general, thanks to the example given by @trentcl. Note that the same problem occurs with `let (a, b) = (&mut x, x.0);` (compared to `let (b, a) = (x.0, &mut x);` – rom1v May 19 '17 at 07:31
  • @trentcl Is there an issue with treating `X(Y)` as equivalent to `{let _a = X; let _b = Y; _a(_b)}`. That seems to fix the too-strict borrow issue wherever entails doing the same thing manually, and without changing behavior (unless I am mistaken about the current order of evaluation of `X` and `Y`). Niko Matsakis [explicitly mentions](http://smallcultfollowing.com/babysteps/blog/2017/03/01/nested-method-calls-via-two-phase-borrowing/#how-can-we-fix-this) a fix by "changing how we desugar the method calls", but still prefers the more general approach, unfortunately without an explanation. – user4815162342 May 19 '17 at 08:17
  • @user4815162342 That still runs into the same problem when `X` and `Y` borrow from the same object. You need to evaluate `Y` first to make the equivalent of `x.f(x.0)` work, but doing that opens a can of worms - maybe several - in terms of reordering expressions – trent May 19 '17 at 13:15