Strange lifetime inference

Why does this work? Does rust somehow infer a "nullish" lifetime for test.f that is only valid in the body of the function?

playground link

struct Test<'a> {
    f: fn(&'a i32),
    x: &'a i32
}

fn foo(x: &i32) {
    println!("{x}");
}

fn main() {
    let mut f;
    {
        let i = 1;
        let test = Test{f: foo, x: &i};
        f = test.f;
    }
    {
        let i = 1;
        f(&i);
    }
}

You'll get a lifetime error if you add f(&i); to the first block.

I'd guess that what is going on is that the x: &i doesn't actually constrain the lifetime in the type of test or f, because test.x is never used, and in general it's allowed for a structure to contain invalid references as long as they're not used.

This is an unusual case of that rule because the required lifetime, being within the second block, “hasn't even started yet”, but lifetimes don't really have start-points, only end-points.

Indeed the first change causes an error. However, when I use test.x it still works:

struct Test<'a> {
    f: fn(&'a i32),
    x: &'a i32
}

fn foo(x: &i32) {
    println!("{x}");
}

fn main() {
    let mut f;
    {
        let i = 1;
        let test = Test{f: foo, x: &i};
        f = test.f;
        let x = test.x;
        println!("{x}");
    }
    {
        let i = 1;
        f(&i);
    }
}

fn(&'a i32) is contravariant in 'a, so upon copying it out of test it can coerce to something that takes a longer lifetime, say from the copy location through the end of main. That would be compatible with calling it in the second block -- but means you can't call it with a reference that isn't able to live that long.

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.