Limitations of the borrow checker like these can often be overcome by conditionally “re-tracing” the path into a borrow that is only conditionally further used.
The compiler sees some kind of dependency here due to the fact that:
inner depends on ptr, and
ptr conditionally gets set to something depending on inner
without trying to fully go into any more detailed analysis of how exactly the borrow checker thinks about the lifetimes involved here, the main point is that inner is used unconditionally in the if statement
and the conditional assignment still influences the lifetime of inner even if the else branch is taken, so after leaving the else branch and the while loop with break, the mutable reference that lived in inner is still considered alive.
Long story short: rewriting the ptr = &mut inner.next into something no longer depending on inner fixes the problem
if inner.next.is_some() {
ptr = &mut ptr.as_mut().unwrap().next; // conditionally re-tracing
// the path from `ptr` to `inner`
} else {
break;
}
the unwrap here can’t fail because we already are in the while let Some(inner) = ptr loop that checked ptr to be Some, and thus there’s a good chance that the compile can optimize away the whole unwrap() call anyway
The above change removed the dependency of ptr on inner, which means that inner can be more short-lived and you can access ptr again after the loop.
Note that your original code not compiling is part of a known limitation of the borrow checker that will most likely get fixed eventually (i.e. maybe in a few years or so) with the new borrow checker “polonius” when that becomes part of standard rust. You can see it already compile on nightly with the unstable -Z polonius option here: