Contravariance and borrow checking


#21

So Rust really only has 2 variances: variant and invariant. I realize fn() has contravariance (at least based on nomicon) but it seems to me it has nothing to do with borrows at all. Nomicon says it’s used for trait method selection and doesn’t elaborate. I’m frankly not sure what the story is on it. I do know that fn() cannot hold any references which includes closures coerced to it. So my intuition on it is that it’s like a String in that regard.

Yeah, bad choice of terminology on my part. What I really wanted to convey is that the fn has no state captured and the 'a is a bound on the lifetime of args given to it.


#22

I hadn’t looked before but glanced at it now. It’s an interesting one. The borrow is taken when the fn is called though, so I think this is a different thing altogether. The a binding creates a scope where if a is called the reference is borrowed for it thereafter. Looks like NLL fixes this case though, so this appears to be a lexical lifetime issue.

I don’t know if there’s a better formal explanation.


#23

Ah, good catch.


:spades: We must go deeper!

struct CovCov<'a>(fn(fn(&'a ()))); // Two wrongs make a right
struct ConCon<'a>(fn(fn(fn(&'a ())))); // Three wrongs make a left

fn verify_covariance<'a, 'b:'a>(c: CovCov<'b>) -> CovCov<'a> { c }
fn verify_contravariance<'a, 'b:'a>(c: ConCon<'a>) -> ConCon<'b> { c }

struct Struct;
impl Struct {
    fn concon_mut(&mut self) -> ConCon { ConCon(|_| ()) }
    fn covcov_mut(&mut self) -> CovCov { CovCov(|_| ()) }
}

The results are the same: (playground)

fn main() {
    let mut x = Struct;

    {
        // This is forbidden.
        let a = x.covcov_mut();
        let b = x.covcov_mut();
    }

    {
        // This is okay.
        let a = x.concon_mut();
        let b = x.concon_mut();
    }
}

#24

I wouldn’t call it a bug (even without NLL.) It is a strange case where the lifetime for the variable (borrow in the first code I posted) isn’t an entirely new lifetime but instead taken from the restriction the function a(). Leading to extra oddity the borrow extends beyond what is normally expected.

Made a few more additions, demos where hrtb (or not) changes what is allowed.
https://play.rust-lang.org/?gist=510c5d1f8fdf37d86ed8ad50f70019c4&version=nightly


#25

I think it’s more or less equivalent to:

struct Ref<'a, T: 'a>(&'a T);
impl<'a, T> Ref<'a, T> {
    fn borrow(&'a mut self) -> &'a T {
        &self.0
    }
}

fn main() {
    let s = "h".to_string();
    {
        let mut r = Ref(&s);
        let _b = &s;
        r.borrow();
        //r.borrow(); not allowed
    }
    println!("{}", s);
}

a in your example acts like r - it creates a scope such that once borrow() is called (or a is invoked) the borrow is extended to the scope of the value itself (r or a).