Code change:
In , the NLL checker incorrectly judges to be borrowed more than once: I get this error: This works without NLL. This was found while trying to bootstrap rustc with NLL enabled, where it manifests as:
cc
This is a case where our debug flags are very helpful for isolating the cause of a bug like this. Step 1: Remove from the example. Step 2: Incrementally work way to recreating effect of ; my hypothesis is (well, was) that this bug was injected by either mir-borrowck or, somehow, by NLL itself... lets test that hypothesis: I was scratching my head at this point... and then it hit me:
ah, good catch! I guess this is because those mutable borrows are "activating" at this later point? Fascinating.
I'm going to take a look at this bug.
Here's the MIR dump I managed to extract: <details</detailsAnd its corresponding MIR dump looks like the following: <details</detailsIt's probably not that important to look at the MIR dump, because the second line of the RUSTLOG output contained this: Where the last parameter is the value of , which strikes me as odd since a Reserved borrow should be able to be mutated... Oh wait, perhaps I'm wrong? Is this supposed to be activated before being mutated? Note that _4 is a temporary created by MIR that will be used to call .emit() upon.
I think what is happening is not that surprising. I don't think it's related to the flag, really. can prob confirm, but I think it goes something like this. Let's simplify to two arms. I think you'll find this has the same error: I've also labeled the two borrows that occur. Now, the control-flow is something like this: As you can see from the diagram, borrow B2 is already active when borrow B1 is activated. This is in part why the initial RFC for two-phase borrows was limited to the temporaries created for method calls: I wanted to ensure that the temporary was (a) assigned once and (b) that assigned was postdominated by a use which was dominated by the one assignment.
I'm a bit surprised though that this doesn't work now. Maybe PR that limits two-phase borrows hasn't yet made it into nightly? Would be good to verify whether this still activates with the latest master.
So I just rebased against master, and I still run into this exact same bug. which PR were you referring to?
I was referring to cc thoughts?
Hmm I wonder if is still too naive? I’ll try to investigate tomorrow
(I've started looking at the dataflow state, via . The initial information I'm getting from there is confusing me at best, so I'm not sure yet what conclusion to draw from it. Will report more later once I've delved into it a little more deeply.)
So, looking at the dataflow graph (after the dataflow has been run, so the entry sets contain the fixed point results), generated via , and also inspecting the debug output immediately before the error is issued: ... (this data is based on niko's reduced example from The dataflow is concluding, on entry to bb7, that both of the borrow (bb3[2]) and the borrow (bb2[0]) are reserved, and the bb3[2] is also active (since it was used in the invocation of on the bb3 terminator). Thus we get an error that it is not valid to have two simultaneous multiple borrows of . One logical step to take next would be to compare against the dataflow that is derived when we do not have two-phase-borrows turned on. I am going to do that in a bit.
The bug here is probably that we are erroring on having two mutable-borrows of the same path reach the use of in (bb7[0]). I often get the details wrong; I suspect that the issue is that we should solely be detecting conflicts between mutable borrows of a given path at the point where the borrow is taken (i.e. the points were we do ). Or to use the two-phase borrow terminology, the point where the borrow is reserved. Here, we are checking for it at the point where the borrow is activated (the use of in ). And so that explains why we do not see the problem without two-phase borrows turned on. When two-phase borrows are turned off, the check in question is performed at the point of reservation (since activation points are irrelevant when two-phase borrows are turned off).
I believe that is roughly the same , right?
Well, let me read your comments more closely, because I'm confused which borrow is not activating.
there's some differences compared to your diagnosis, but I don't know if they matter. For example, your hypothesized control flow graph has two activations that reach the same control flow point. The actual graph has a reservation on one path (but no activation), and the other path has a reservation and an activation.
So, the thing that is confusing me from the output is that the borrow appears not to be activating. However, I believe that comes from , and that (per your recent changes) ought not to be two-phase. (Note: this might also need )
An update regarding the previous comment: We discussed further on ; to summarize, I think that the intention is that two-phase borrows have no effect on the dataflow -- instead, their effect is meant to be encoded solely in the borrowcheck code itself. Specific to this case, the dataflow only shows a reservation, not an activation, at the statement , because that is indeed just the initial borrow of , not the use of `4, and its the job of the borrowcheck code to actually react properly to that reservation.
After even further discussion on gitter and also in video chat, and came to the conclusion that the answer here is probably not a surgical fix covering a small number of lines, but rather a slightly larger-scale (but mentorable) effort to redo two-phase borrows so that it more closely matches the original intention of the RFC. See

Related discussion:
text: <ins> // Copyright 2018 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. // run-pass // revisions: lxl nll #![cfg_attr(nll, feature(nll))] struct Foo { x: u32 } impl Foo { fn twiddle(&mut self) -> &mut Self { self } fn twaddle(&mut self) -> &mut Self { self } fn emit(&mut self) { self.x += 1; } } fn main() { let mut foo = Foo { x: 0 }; match 22 { 22 => &mut foo, 44 => foo.twiddle(), _ => foo.twaddle(), }.emit(); } </ins>