The borrow checker is a bit smart and a bit stupid, and so is Polonius

I think at least //Err after will compile, but even with Polonius it doesn't.

use std::collections::HashMap;

struct X<'a> {
    v: &'a str,
}

struct XS<'a> {
    v: &'a str,
    s: String,
}

struct MF<'a, 'b> {
    fa: &'a str,
    fb: &'b str,
    fc: String,
    fd: String,
}

fn pl() {
    println!("-----------------");
}

fn main() {
    polonius();
    pl();

    s();
    pl();

    s_s();
    pl();

    x();
    pl();

    xx();
    pl();

    xs();
    pl();

    xs_ok_b();
    pl();

    m_f();
    pl();

    m_f_ok_b();
    pl();
}

fn s() {
    let s = String::from("s");
    let s_2 = String::from("s_2");

    let s2 = String::from("s2");
    let s2_2 = String::from("s2_2");

    let s3 = String::from("s3");
    let s3_str = "s3_str";

    let mut a = &s;
    let mut b = &s2;
    let mut c = &s3[..];
    println!("{:6} {:6} {:6}", a, b, c);

    a = &s_2;
    drop(s); //Ok after

    drop(s2); //Ok before
    b = &s2_2;

    drop(s3);
    c = &s3_str;

    println!("{:6} {:6} {:6}", a, b, c);
}

fn s_s() {
    let s = String::from("s");
    let s_2 = String::from("s_2");

    let s2 = String::from("s2");
    let s2_2 = String::from("s2_2");

    let s3 = String::from("s3");
    let s3_str = "s3_str";

    let mut a = &s;
    let mut b = &s2;
    let mut c = &s3[..];
    println!("{:6} {:6} {:6}", a, b, c);

    *(&mut a) = &s_2;
    //drop(s); //Err after

    //drop(s2); //Err before
    *(&mut b) = &s2_2;

    *(&mut c) = &s3_str;
    //drop(s3); //Err after

    println!("{:6} {:6} {:6}", a, b, c);
}

fn x() {
    let x = String::from("x");
    let x_2 = String::from("x_2");

    let x2 = String::from("x2");
    let x2_2 = String::from("x2_2");

    let x_str = "x_str";

    let mut a = X { v: &x };
    let mut b = X { v: &x2 };
    println!("{:6} {:6}", a.v, b.v);

    a.v = &x_2;
    //drop(x); //Err after

    //drop(x2); //Err before
    b.v = &x2_2;
    println!("{:6} {:6}", a.v, b.v);

    a.v = &x_str;
    //drop(x_2); //Err after
    println!("{:6} {:6}", a.v, b.v);
}

fn xx() {
    let xx = String::from("xx");
    let xx_2 = String::from("xx_2");

    let xx2 = String::from("xx2");
    let xx2_2 = String::from("xx2_2");

    let mut a = X { v: &xx };
    let mut b = X { v: &xx2 };
    println!("{:6} {:6}", a.v, b.v);

    a = X { v: &xx_2 };
    drop(xx); //Ok after

    drop(xx2); //Ok before
    b = X { v: &xx2_2 };
    println!("{:6} {:6}", a.v, b.v);
}

fn xs() {
    let xs = String::from("xs");
    let xs_2 = String::from("xs_2");

    let xs2 = String::from("xs2");
    let xs2_2 = String::from("xs2_2");

    let s = String::from("s");
    let s2 = String::from("s2");

    let mut a = XS { v: &xs, s };
    let mut b = XS { v: &xs2, s: s2 };
    println!("{:6} {:6} {:6} {:6}", a.v, a.s, b.v, b.s);

    //a = XS { v: &xs_2, s}; //cannot, s has moved into a
    //a = XS { v: &xs_2, s: a.s }; //can
    a = XS { v: &xs_2, ..a };
    drop(xs); //Ok after

    //drop(xs2); //Err before
    b = XS { v: &xs2_2, ..b };

    println!("{:6} {:6} {:6} {:6}", a.v, a.s, b.v, b.s);
}

fn xs_ok_b() {
    println!("xs_ok_b");
    let xs = String::from("xs");
    let xs_2 = String::from("xs_2");

    let xs2 = String::from("xs2");
    let xs2_2 = String::from("xs2_2");

    let s = String::from("s");
    let s2 = String::from("s2");

    let mut a = XS { v: &xs, s };
    let mut b = XS { v: &xs2, s: s2 };
    println!("{:6} {:6} {:6} {:6}", a.v, a.s, b.v, b.s);

    a = XS { v: &xs_2, ..a };
    drop(xs); //Ok after

    let s2 = b.s;
    drop(xs2); //Ok before
    b = XS { v: &xs2_2, s: s2 };

    println!("{:6} {:6} {:6} {:6}", a.v, a.s, b.v, b.s);
}

fn m_f() {
    let fa = String::from("fa");
    let fa_2 = String::from("fa_2");

    let fb = String::from("fb");
    let fb_2 = String::from("fb_2");

    let fc = String::from("fc");
    let fd = String::from("fd");

    let fa2 = String::from("fa2");
    let fa2_2 = String::from("fa2_2");

    let fb2 = String::from("fb2");
    let fb2_2 = String::from("fb2_2");

    let fc2 = String::from("fc2");
    let fd2 = String::from("fd2");

    let mut a = MF {
        fa: &fa,
        fb: &fb,
        fc,
        fd,
    };

    let mut b = MF {
        fa: &fa2,
        fb: &fb2,
        fc: fc2,
        fd: fd2,
    };

    println!(
        "{:6} {:6} {:6} {:6} {:6} {:6} {:6} {:6}",
        a.fa, a.fb, a.fc, a.fd, b.fa, b.fb, b.fc, b.fd
    );

    a = MF {
        fa: &fa_2,
        fb: &fb_2,
        ..a
    };
    drop(fa); //Ok after
    drop(fb); //Ok after

    //drop(fa2); // Err before
    //drop(fb2); // Err before
    b = MF {
        fa: &fa2_2,
        fb: &fb2_2,
        ..b
    };

    println!(
        "{:6} {:6} {:6} {:6} {:6} {:6} {:6} {:6}",
        a.fa, a.fb, a.fc, a.fd, b.fa, b.fb, b.fc, b.fd
    );
}

fn m_f_ok_b() {
    println!("m_f_ok_b");
    let fa = String::from("fa");
    let fa_2 = String::from("fa_2");

    let fb = String::from("fb");
    let fb_2 = String::from("fb_2");

    let fc = String::from("fc");
    let fd = String::from("fd");

    let fa2 = String::from("fa2");
    let fa2_2 = String::from("fa2_2");

    let fb2 = String::from("fb2");
    let fb2_2 = String::from("fb2_2");

    let fc2 = String::from("fc2");
    let fd2 = String::from("fd2");

    let mut a = MF {
        fa: &fa,
        fb: &fb,
        fc,
        fd,
    };

    let mut b = MF {
        fa: &fa2,
        fb: &fb2,
        fc: fc2,
        fd: fd2,
    };

    println!(
        "{:6} {:6} {:6} {:6} {:6} {:6} {:6} {:6}",
        a.fa, a.fb, a.fc, a.fd, b.fa, b.fb, b.fc, b.fd
    );

    a = MF {
        fa: &fa_2,
        fb: &fb_2,
        ..a
    };
    drop(fa); //Ok after
    drop(fb); //Ok after

    let t = (b.fc, b.fd);
    drop(fa2); // Ok before
    drop(fb2); // Ok before
    b = MF {
        fa: &fa2_2,
        fb: &fb2_2,
        fc: t.0,
        fd: t.1,
    };

    println!(
        "{:6} {:6} {:6} {:6} {:6} {:6} {:6} {:6}",
        a.fa, a.fb, a.fc, a.fd, b.fa, b.fb, b.fc, b.fd
    );
}

fn get_or_insert(map: &'_ mut HashMap<u32, String>) -> &'_ String {
    if let Some(v) = map.get(&22) {
        return v;
    }
    map.insert(22, String::from("hi, polonius works fine"));
    &map[&22]
}

fn polonius() {
    let mut map = HashMap::new();
    get_or_insert(&mut map);
    println!("{}", map.get(&22).unwrap());
}

There are certainly other people on this forum who have better knowledge than me. But, as I understand things...

If the point of your post was about making this code work, then that is the purpose of the Entry API.

If the point of your post was about Polonius and future borrow checker improvements, my understanding is that your issue is basically "Problem case 3"

And the plan is that Polonius will fix it, but the version of Polonius available through the -Zpolonius=next feature is a complete rewrite, and is still under development. However the polonius-the-crab crate ought to work.

UPDATE: Amanda Stjerna articulates the plan here The "step 1" polonius is limited in its reasoning abilities, vs. the ultimate polonius.

The code as-is compiles with -Z polonius (legacy) and -Z polonius=next.

If you have a self-contained example that doesn't, I might take a look at it. (I'm afraid 335 lines with a couple dozen annotations is too much for me to want to guess your intentions.)

3 Likes

It compiles because I commented out the //Err line that doesn't compile.
Uncomment those lines and it won't compile.
The get_or_insert() function is what I use to make sure that polonius is actually used.

Short code:

struct X<'a> {
    v: &'a str,
}

fn main() {
    x();
}

fn x() {
    let x = String::from("x");
    let x_2 = String::from("x_2");

    let mut a = X { v: &x };
    println!("{}", a.v);

    a.v = &x_2;
    drop(x); //Err

    println!("{}", a.v);
}

Before the second print, you cannot drop x. It also does not compile with -Z polonius=next.

By changing the code, it will be able to drop x earlier.There are some examples in the long code.(fn xx())

That looks similar to this thread.[1]

Lifetimes are part of the type system, so on one layer you can think of it like this:

    let mut a = X { v: &x }; // Say `a: X<'a>`; `x` is borrowed for `'a`
    println!("{}", a.v);

    a.v = &x_2; // The type of the variable doesn't change, so
                // `x_2` is also borrowed for `'a`

    //drop(x); //Err

    // This use of `a: X<'a>` forces the lifetime `'a` to be alive here,
    // and in turn the borrows of `x_2` *and* `x` are kept active
    println!("{}", a.v);

However, to add another layer, if there's a gap in the liveness of a lifetime, borrows associated with that lifetime do not traverse it. When you overwrite all of a, the compiler recognizes that the lifetime in a's type is be dead for a portion of code before the overwrite, resulting in a gap in the lifetime. That's why the fn xx examples do compile: you're replacing all of a.

    println!("{:6} {:6}", a.v, b.v);
    // replaces all of `a`, not just `a.v`
    a = X { v: &xx_2 };
    drop(xx); 
    println!("{:6} {:6}", a.v, b.v);

The lifetime now has a gap between the first println! and the reassignment of a, so the borrow of xx does not stay active after the first println!.

Like the examples in the other thread also demonstrated, though, this gap isn't recognized when you overwrite just a field or a dereferenced place. Your fn x example could conceivably compile if Rust tracked the liveness of fields, and could recognize that all fields[2] being dead implies the lifetime as a whole is dead.

(Polonius adds more flexibility, but doesn't change the fact that lifetimes are part of the type system. The main Polonius improvement is making lifetime subtyping location-sensitive, which allows more control-flow sensitive lifetimes, but your examples involve little to no control flow. Polonius does not add the conceivable new analysis I just mentioned.[3])


  1. A couple examples that don't involve Box are in this comment. ↩︎

  2. or even just all fields that contribute to the container type lifetime ↩︎

  3. If the capability were added to NLL, it would work pre-Polonius for your example too. ↩︎

2 Likes