42

I have a strange piece of code:

#![allow(unused)]

fn f<'a>() {}
fn g<'a: 'a>() {}

fn main() {
    // let pf = f::<'static> as fn(); // (7)
    let pg = g::<'static> as fn();    // (8)
    //print!("{}", pf == pg);
}

The 7th line cannot be compiled if it is uncommented (with the error below), but the 8th line can be compiled.

error: cannot specify lifetime arguments explicitly if late bound lifetime parameters are present
 --> src/main.rs:7:18
  |
7 |     let pf = f::<'static> as fn(); // (7)
  |                  ^^^^^^^
  |
note: the late bound lifetime parameter is introduced here
 --> src/main.rs:3:6
  |
3 | fn f<'a>() {}
  |      ^^

What is the meaning of 'a: 'a in line 4?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
iDuanYingJie
  • 663
  • 4
  • 11
  • 9
    The only reference to the error in `f::<'static>` I could find is [in the rusc dev guide](https://rustc-dev-guide.rust-lang.org/early-late-bound.html). It looks like `<'a>` is a late-bound parameter but `<'a: 'a>` is an early-bound parameter, and it's illegal to specify late-bound parameters explicitly. I'm not sure *why* that is, but it might be worth a post in [rust-internals](https://internals.rust-lang.org/), if only because the error message is pretty rough and there might be ideas on how to clean it up. – ddulaney Jan 02 '21 at 05:08
  • 3
    You'll get an error in _both_ cases if the lifetime is actually used in a function argument. This simplified example is unlikely to come up in "real" code. The exact error message appears to be a side-effect of changes related to work on GATs. – Peter Hall Jan 02 '21 at 14:43
  • 3
    Source of the question: https://dtolnay.github.io/rust-quiz/11 – Konstantin W Jan 02 '21 at 20:46
  • 1
    Did you read the description of the problem on dtolnay's website? In particular “By these rules, the signature `fn f<'a>()` has a late bound lifetime parameter while the signature `fn g<'a: 'a>()` has an early bound lifetime parameter — even though the constraint here is ineffectual.” – mcarton Jan 20 '21 at 23:35

2 Answers2

1

I have found nothing in the rust documentation to confirm, but it seems that <'a: 'a> is short to qualify the output lifetime of the function (cf lifetime output in the documentation).

Here are some tests that seem confirm that :

#![allow(unused)]

fn f<'a>() {}
fn g<'a: 'a>() {}
fn h<'a: 'a>() -> &'a i32 { &5 }
fn i<'a>() -> &'a i32 { &6 }

fn tst1<'b>() {
   let w:&'b i32 = &6;
   //let pj = f::<'b> as fn();
   let pk = i::<'b> as fn() -> &'b i32;
}

fn tst2<'b>() {
   let pr = g::<'b> as fn();
}
fn main() {
    //let pf = f::<'static> as fn(); // (7)
    let pg = g::<'static> as fn();    // (8)
    let ph = h::<'static> as fn() -> &'static i32;
    let pi = i::<'static> as fn() -> &'static i32;

    //print!("{}", pf == pg);
}

This compile with Rustc 1.41.1. It's possible that recent updates impact this.

As you could see, let pi and let pk compile for fn i<'a>(), but not let pf nor let pj for fn f<'a>(). The fn i<'a>() only differs from fn f<'a>() as the former returns a value thus implying a output liftetime for the function.

The tst1 and tst2 verify this with inner lifetime of a function.

It cannot be 100% sure without documentation reference, but it seems that if you don't specify a output to your function, you still have, for function's pointers, to have a defined output lifetime, and <'a: 'a> seems implying that.

Regarding dtolnay quizz thanks to @Konstantin W, it seems that output lifetime of the function become earlier bounded with <'a: 'a> and the compiler doesn't scream in that case when assigning the 'static lifetime at the pointer assignation.

If that's the case, let pi and let pk compile for fn i<'a>() probably because the i function is fully defined in input like output then lifetime assignation could be done at pointer assignation (or the specified output implies earlier bounding for both input and output timeline, but I've a hard time believing that). Maybe an undocumented rust lifetime Elision rule too.

TravelerVihaan
  • 201
  • 2
  • 6
  • 19
Zilog80
  • 2,534
  • 2
  • 15
  • 20
-5

The a in 'a is just a name. You could call it 'mylifetime or 'golden_eternal_braid if you want to. The meaining of any lifetime parameter in that position is just to declare it. Lifetimes other than 'static are not defined globaly, so you can't declare a function as

fn f(param: &'a SomeType) {} // error

... but if you declare it, you can:

fn f<'a>(param: &'a SomeType) {} // ok

Of course, you could just as well leave that lifetime to be infered, declaring the function as

fn f(param: &SomeType) {}
Rasmus Kaj
  • 4,224
  • 1
  • 20
  • 23
  • 5
    I don't see how this answers what the meaning of `g<'a: 'a>` is. – Ted Klein Bergman Mar 09 '21 at 18:45
  • In general, `<'a: 'b>` means to define lifetime 'a constrained by lifetime 'b. In this case, it is only constrained by itself, so it is kind of pointless. – Rasmus Kaj Mar 10 '21 at 11:39
  • 5
    Then why is it required in order to compile the code in the question? So it's not pointless, it has a meaning. – Ted Klein Bergman Mar 10 '21 at 11:42
  • As written, the lifetimes of booth `f` and `g` is pointless, but the constraint used on `g` binds it enough to kind of fool the compiler that is has a use. Line 7 can be fixed by removing the lifetime specifikation, like `let pf = f as fn();`. – Rasmus Kaj Mar 10 '21 at 12:06