Inband lifetimes blog


#1

@Mark_Simulacrum has a nice blog post exploring how inband lifetimes (Rust 2018 edition feature) look from the perspective of a specific rustc crate. I’ve not played with them myself just yet, but my initial reaction is they don’t bring enough benefit to the table and potentially create more confusion and/or leave room to make code harder to read/follow, as Mark’s examples show.

I’ve always liked that Rust is very explicit in its type signatures, for the most part (lifetime elision being the opposite of that, but I’ve found that to be mostly striking the right balance). I’m slightly worried that the language is being morphed in directions that add extra optionality to how things are written, hide a bit more things, and in general, seem to be optimized for writing code (as opposed to reading). I’ll reserve stronger judgment until I actually play with this myself, however.

Curious what others think.


#2

I’ve written some thoughts on in-band lifetimes here.

In summary, I believe that instead of landing in-band lifetimes we should improve lifetime ergonomics by:

  • Extending lifetime elision in impl.
  • Allowing '_ in more places.

This would get us most of the benefits without most of the drawbacks.


#3

One of the most common questions about lifetimes is about this line of code:

impl<'a> Foo for &'a i32 {

Why do I need the 'a twice? It feels like boilerplate.

Even when explained, most people still react with a “okay I guess I can live with that but I don’t like it.” What they tend to write,

impl Foo for &'a i32 {

will now just work.

I think this is another instance of https://terrytao.wordpress.com/career-advice/theres-more-to-mathematics-than-rigour-and-proofs/; this is a feature that’s great for the pre and post rigorous stages, but makes people in the “rigorous” stage uneasy.


#4

I think you have a typo in your second snippet, since it is identical to your first one.


#5

I’d be fine with more elision, like @stjepang suggested, so you can write:

impl Foo for &'_ i32

although I don’t mind the “boilerplate” of status quo all that much. The existing formulation is also consistent with generic types:

impl<T> Foo for T

once you take into account that &'a i32 is a type and not a “modified” i32.

Inband lifetimes, however, appear to go a lot further in what they allow. Couldn’t we move a bit more incrementally and conservatively, get some experience with slightly more elision, and then reassess? Why do we need a “big bang” type of migration?


#6

ugh, thanks


#7

People don’t like it equally as much, maybe even more, since generic types are used more often than lifetimes :slight_smile:


#8

All I can say is there will be groups of people who don’t like any particular aspect of Rust :slight_smile:. It doesn’t mean things need to change - the language needs to stay true to its principles, IMO, rather than being pulled in various directions. And for those changes that are deemed a possible improvement, try to do them incrementally, see how they play out with the wider userbase/collective experience, and then see if we need to go further.


#9

Completely agreed! The following should work as well, IMO:

impl Foo for &'_ i32 { ... }

And even this:

impl Foo for &i32 { ... }

But this is just elision and has nothing to do with in-band lifetimes. My point was that elision and '_ is what brings the most bang-for-buck in terms of lifetime ergonomics in Rust 2018.

Also, I’m personally not convinced that in-band lifetimes are that great in the pre-rigorous stage. Consider this:

fn foo(x: &'a i32, y: &i32) -> &'a i32 { x }

fn bar(x: &'a i32, y: &i32) -> &'a i32 { x }

Here’s something I can imagine myself asking as a beginner learning Rust:

  • Is 'a in foo and bar the same lifetime?
  • What if I use type Foo<'a> in one function, but Foo<'b> in another? Are those types different?

But if lifetimes are declared at each use rather than coming “out of thin air”, then it’s clear that each lifetime is only relevant to the function it’s declared at, and definitely not outside it.

By the way, @scottmcm had an interesting idea about adding more elision to associated types: https://internals.rust-lang.org/t/lifetime-elision-for-associated-types-unbaked-idea/7997
This could make lifetimes even easier.


#10

These forms were already accepted as part of RFC 141, and they are implemented today in the 2018 Edition preview: https://play.rust-lang.org/?gist=5c277e224a490a66c89cec85d1de0252&version=nightly&mode=debug&edition=2018


#11

FWIW, even now that I know what I’m supposed to be doing, that’s still not what my fingers tend to type. What I seem to always type the first time is just impl Foo for &i32, and then my brain kicks back in and I go add the lifetime.

So I’m way more excited by elision improvements, since just getting rid of the <'a> doesn’t feel like it’s solving my actual wish, here.

And not needing a named lifetime seems pretty common, like the first for & impl that comes to mind:

Is there a nightly-2015 feature gate that can be used to enable just them? Or is it mixed up in in_band_lifetimes?


#12

It’s part of in_band_lifetimes (plus underscore_lifetimes for the underscore version).