playground
this will not work
while let Some(n) = &mut temp.next {
temp = n.next.as_mut().unwrap()
}
this will
while let Some(ref mut n) = temp.next {
temp = n.next.as_mut().unwrap()
}
playground
this will not work
while let Some(n) = &mut temp.next {
temp = n.next.as_mut().unwrap()
}
this will
while let Some(ref mut n) = temp.next {
temp = n.next.as_mut().unwrap()
}
Normally I have more to say than thus, but:
O_o
Edit: I remember reading that some stdlib macros expand to match $expr { e => ... }
rather than using let
due to "extremely nuanced lifetime issues." Could that possibly be related? Or maybe that just has to do with temporaries, and this is... I don't know what this is.
I think this is the same thing as macros expanding to a match
rather than using let.
while let
pretty much desugars to
while let $pat = $expr {
$body
}
loop {
match $expr {
$pat => { $body },
_ => break
}
}
So if you have &mut temp.next
at the top, it will lock temp.next
for the entire match statement. But if you do temp.next
with a Some(ref mut n)
then it only locks the branch, and it restricts the scope of the borrow using nll.
Note that a match is basically this, (using made up syntax)
match $expr {
$pat => $expr,
...,
_ => $default_expr
}
let match_value = $expr;
if discriminant(match_value) == $pat {
let $pat = match_value;
$expr
}
...
else if ... {
...
}
...
else {
$default_expr
}
So if you put a &mut _
at the top, that borrow will propagate through the entire chain, but if it is in the pattern, Rust can better reason about it.
Disclaimer: This is not the desugaring for match, but it does explain the reason for it's behavior.
but there is a loop {} outer scope, should not affects assignment after went out of loop.
and I tried to explicit add scope like this:
{
while let Some(n) = &mut temp.next {
temp = n.next.as_mut().unwrap()
}
}
won't work either
wait.. what!? you mean the code afterward will be treated as else { //the code } ???
It is unfortunate that the compiler cannot show MIR when there is a compile error, because this basically must come down to whatever changes in the MIR.
Yes, this exactly
Okay, so I replaced the innards of the loop with a trivial expression 42;
. This allowed me to compare the MIRs. Unfortunately it still does not make sense to me.
The MIR shown in the following graph is the MIR for when &mut temp.next
is used.
&mut temp.next
, not for ref mut n
.((*_3).1)
when ref mut n
is used.// rust code
while let Some(n) = &mut temp.next {
42;
}
Now, we have to imagine replacing the orange line with the MIR of temp = n.next.as_mut().unwrap()
. This gets annoying and complicated to show because each of the function calls breaks it up into a new basic block in order to introduce unwind edges. If I try to simplify that, the MIR for the loop that compiles looks something like:
// rust code
while let Some(ref mut n) = temp.next {
temp = n.next.as_mut().unwrap();
}
bb5: {
_7 = discriminant(((*_3).1));
switchInt(move _7) -> [1isize: bb6, otherwise: bb9];
}
bb6: {
StorageLive(_8);
_8 = &mut ((((*_3).1) as Some).0);
StorageLive(_9);
StorageLive(_10);
StorageLive(_11);
_11 = &mut ((*(*_8)).1);
_10 = const Option::<T>::as_mut(move _11);
StorageDead(_11);
_9 = const Option::<T>::unwrap(move _10);
StorageDead(_10);
_3 = &mut (*(*_9));
StorageDead(_9);
StorageDead(_8);
goto -> bb5;
}
bb9: {
StorageDead(_8);
StorageDead(_3);
drop(_2) -> bb10;
}
bb10: {
return;
}
Putting it all together I get:
where red and blue have the same meaning as before, and the green line would appear to be the important one.
If this is correct, then to address @RustyYato :
&mut temp.node
results in a borrow with a slightly wider scope...StorageDead(_6)
appears at the end of bb6
, before it enters the next iteration, so I don't understand why there is an issue.The primary difference seems to be that, in the loop that compiles, n
(_8
) borrows directly from temp
(_3
), but in the loop that fails to compile, it only borrows indirectly through _6
...
Ok, so it looks like because of the indirect borrow through _6
, which then dies at the end of the block. Rust
thinks that the borrow doesn't live long enough, and errors out? But when it directly borrows from _3
everything is fine because the lifetimes are all the same.
This looks suspiciously similar to reborrows.
This. One point that should have been raised is the sugar present in the
while let Some(n) = &mut temp.next {
Before match ergonomics, this would have been written as
while let &mut Some(ref mut n) = &mut temp.next {
temp.next
innards (borrowed for the whole implicit match, and reborrowed for the non break
-ing branch);vs:
while let Some(ref mut n) = temp.next {
temp.next
innards (direct borrow for the non break
-ing branch).Now, as to why a borrow acts differently than a reborrow, I think that @ExpHP's MIR exploration has led us as close as possible to an explanation.
I guess it may be related to NLL's limitations, which would hopefully be overcome by Polonius. We'll see.
Not sure if it's the same user (if not, then it's quite fantastic timing!), but somebody posted a more minimal example to the bug tracker. I posted similar MIR images there.
pub struct Demo {
foo: Option<Box<Demo>>
}
pub fn simplified (mut a: &mut Demo){
match &mut a.foo {
&mut Some(ref mut a_foo) => a = a_foo,
None => ()
}
a.foo = None
}
Removing the &mut
from the expr and pattern makes it compile. The a.foo = None
is also essential to the error.
This shows that it has nothing to do with loops, or match ergonomics.
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.