Why compiler reqires explicit vars to resolve "cannot borrow more than once" in this case?

Consider the following

 self.a(self.b());

where both a and b take self as &mut self. In this case the compiler complains with error[E0499]: cannot borrow `*self` as mutable more than once at a time and suggests to introduce explicit variables, to look it like this

let b_val = self.b();
self.a(b_val);

It is surprising to me because all argument values of a function should be known before call. This guarantees that no reference is held by argument calculations before calling the outer method, unless some of the returned values still keep references to self, which is not the case in this example. The argument expressions cannot be used anywhere outside the call. Therefore the code with an explicit var should be exactly equivalent with the shorter form.

So I wonder if this is a problem with the compiler implementation so it cannot resolve this situation automatically, or there are some other issues in the language itself that could cause a problem if compiler does it?

1 Like

self.a(self.b()) can be desugared more or less to:

let receiver = self;
let argument = self.b();
Self::a(receiver, argument);

As you can see the receiver is evaluated before the arguments. This was chosen because generally in a.b().c(d()) you don't expect the call to d() to happen before the a.b(). However as you noticed it creates a problem when you want to use the receiver to compute some argument.

There have been proposals to solve this problem, most notably RFC 2025 with the concept of two-phase borrows, however it limits the cases where this can happen to the ones where the borrow to compute the argument is a shared borrow, which is not the case in your example.

4 Likes

Can compiler do a topological sort according to the dependencies, at least within single expression? I believe there can be a procedure that is deterministic so the order will always be the same for the same expression shape. I mean, in this case self.a(...) -needs-> self.b() that would make self.b() to be evaluated first.

OK, I see in the RFC there are links to the discussions, have to read them first. Thanks.

Passing note that what we've actually ended up with currently is less constrained than originally intended and also not formally specified.

The problem is that the compiler doesn't see self.a as one item, but as two things x.y evaluated left to right. Think of it as:

self.deref_mut().a(self.deref_mut().b())

which is equivalent:

let a_self = self.deref_mut();
let b_self = self.deref_mut();
let b_arg = b_self.b();
a_self.a(b_arg);

and you get a_self used before and after b_self, which breaks the exclusivity guarantee.

But yes, the compiler could be smarter about it.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.