What is the meaning of 'a: 'a in rust lifetime parameters

#![allow(unused)]

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

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

I have a strange piece of code like this,

May I ask why the 7th line cannot be compiled, but the 8th line can be compiled.

What is the meaning of 'a: 'a in line 4, thank you

5 Likes

Wow, that's weird. For reference, the error if you uncomment line 7 is

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

Weirder still, there's no error number like E0308, so you can't use rustc --explain, and I don't see anything in the Reference about "late bound lifetime parameters".

2 Likes

yes, it is!
:joy:

I think this post by @nikomatsakis is related?

http://smallcultfollowing.com/babysteps/blog/2013/10/29/intermingled-parameter-lists/#early--vs-late-bound-lifetimes

Reading that makes it a little clearer -- the 'a in fn f<'a>() is a "late bound lifetime parameter" in the sense that it can be instantiated with different values for different calls to the same f. So you can't instantiate 'a explicitly at compile time because that's supposed to happen for each call automatically at run time, or something?

I have no idea what's going on with 'a: 'a though, or why it silences the error. Why would that kind of recursive bound even compile??

3 Likes

Relevant PR:

Also and even more relevantly:

cc @petrochenkov

2 Likes

Isn't that because the bound is always true? 'a always lives (exactly) as long as 'a

This comment in the issue that @cole-miller linked tells you how to make a late-bound lifetime early-bound: put it into a where clause, such as 'static: 'a. (It's also early-bound if it's part of an argument type.)

And @SkiFire13 is correct that 'a: 'a is just a trivially true bound... similar to 'static: 'a. Moreover, the bound within <...> is equivalent to a where clause, which explains why it works around the compiler error -- it forces the lifetime to be early bound.

1 Like

Sure, but I'm surprised that 'a is even in scope on the right-hand side of a 'a: bound.

Ahh, that clears it up. (And explains why this pattern would appear in code in the first place.)

It seems like

  • this error should have an error number and corresponding rustc --explain text that briefly explains the early- vs. late-bound distinction for lifetimes
  • the console output should have a help that suggests adding where 'a: 'a to any late-bound lifetime parameters, since it seems this always fixes the error
3 Likes

I always think that only the lifetime parameter of the form of 'a:'b is legal, which means that the lifetime of b does not exceed a, but I did not expect that 'a:'a is also legal, what 'a: 'a mean?

Like @skifire13 and @quinedot said, it's a vacuous (= always true) constraint that translates to "the lifetime represented by 'a must not outlive itself". It never adds new requirements to the code, it's just a trick to get around this particular compiler error.

Thanks!
Why f<'a> is an late bound and why late bound cannot specify lifetime arguments explicitly

I can't explain it any better than Tracking issue for `late_bound_lifetime_arguments` compatibility lint · Issue #42868 · rust-lang/rust · GitHub does.

Thank you, I will read it carefully

Do you even need f::<'static> as fn()? Have you tried just f as fn()? Or is this mostly intellectual curiosity type of thing?

The way I interpret this (perhaps incorrectly but nonetheless) is:

  • early-bound => part of the type system, so to speak - compiler needs to evaluate and constrain things without specific regions in play
  • late-bound => not part of the type system, per se, and the actual lifetime region is assigned when borrowck is evaluating the types/code and hence the region can vary depending on the specifics of the call to the fn, hence the "late" (i.e. specific region isn't known until a given callsite is evaluated)

The issue @cole-miller linked names late bound params as "existential" as in the example provided there:

for<'a> fn<'b>(input: &'a i32) -> &'b i32

The 'a is an existential lifetime parameter and does not parameterize fn in the same manner as 'b.

I agree that putting 'a:'a to turn 'a into early bound is an interesting quirk.

1 Like

This question from here: Rust Quiz

2 Likes

Ah, I see. The explanation there is quite good actually :slight_smile:

2 Likes

That quiz makes me think of this line of Dijkstra's:

In the case of a well-known conversational programming language I have been told from various sides that as soon as a programming community is equipped with a terminal for it, a specific phenomenon occurs that even has a well-established name: it is called “the one-liners”. It takes one of two different forms: one programmer places a one-line program on the desk of another and either he proudly tells what it does and adds the question “Can you code this in less symbols?” —as if this were of any conceptual relevance!— or he just asks “Guess what it does!”.

(Source.)

1 Like