Nice to see that 3-point error messages end up so well! I remember there was some case where inference did end up non-linear (which would mean that we would get some “spurious” 3-point errors that can’t actually occur even if we ignore control flow), but I don’t remember exactly when. I should write up the “formal” fixed point rules, at least on paper.
That might not be a problem in practice, but there might be some annoying examples on which we would give non-obvious error messages.
A few comments on details:
Diverging Functions
One interesting thing with the new algorithm, is that if we have a function that loops endlessly with no potential of panics, the StorageDead for locals can get deleted, and we can treat them as &'static references:
fn foo(p: &mut &'static u32) {
let x = 2;
let z : &'static u32 = &p;
loop {
*y = p;
}
// the `StorageDead` for `z` is unreachable, so no borrowck conflict with anything
}
In current Rust, I don’t think you can do any communication without the possibility of panics, so that is a bit of a useless curiosity, but if we e.g. made intrinsics not have an unwind edge before borrowck, this might allow some “interesting” patterns.
Additionally, there’s a potential practical problem - ATM we don’t emit StorageDead on unwind paths, and that might actually be observed by code (that also means that if we want to avoid major unsoundness, we need to regard the resume terminator as being a StorageDead of all locals):
struct NoisyDrop<'a>(&'a mut u32);
impl<'a> Drop for NoisyDrop<'a> { /* .. */ } // not blind to anything
fn foo() {
let x;
let mut y = 0;
x = NoisyDrop(&mut y);
loop {
maybe_panic();
// again ^, there's no `StorageDead` for `y` inside `foo` to conflict with
// its borrow.
}
}
As a fix, we might want to emit the StorageDeads and later on remove them during pre-translation (to avoid landing pad pessimization).
Free regions are always alive, even in hairpin loops
A more “region-inferency” example to the previous one, that shows that we must treat free regions as alive in all cases, and not just at function return points (that’s more of an “implementation issue”, but it is something to be aware of).
fn foo<'a>(d: &'a mut Result<fn(), usize>) {
fn innocent() {}
*d = Ok(innocent);
send_to_other_thread(d);
loop {
*d = Err(0x6861636b65642100);
}
}