Isn't rust too difficult to be widely adopted?


#61

Some ideas off the top of my head:

  1. Talk about what exactly 'a means in &'a T. For instance, it’s not necessarily the scope of where T itself is live but potentially a sub region that represents this particular borrow.
  2. Relatedly, draw a distinction between lifetimes and scopes/regions of values.
  3. Explain why methods of the form fn m(&'a mut self), where 'a is a lifetime parameter of the struct, is almost always not what’s needed.
  4. Talk a bit more and provide examples of how compiler is able to coerce longer lifetimes to shorter ones when possible (eg variance of immutable refs).
  5. Explain how lifetime parameter bounds (eg `b:'a) can’t actually enforce that some value outlives another. This has come up a few times on this forum.
  6. Explain and give examples of what it means for &'a mut T to be variant over 'a but invariant over T
  7. Show some examples of variance in play (eg rerurning a longer lived reference, such as 'static, from an otherwise generic lifetime using fn.
  8. Show examples where a struct should have multiple independent lifetime parameters vs being able to reuse one across fields. This is back to invariance.
  9. Show some examples of traits having a lifetime parameter, and explain the purpose.
  10. HRTB examples, such as expressing generic bounds for references
  11. Explain difference between mut ref moving and reborrowing, and ways to select one of them manually when need be
  12. Explain what the T: 'a bound means in struct Foo<'a, T: 'a> and when/why it’s needed.

#62

You’re right that it’s not sufficient. But by “anything and everything” I kinda meant all that a person proficient with lifetimes has needed to know to become so. I was hoping someone like @vitalyd would show up and spell it out (and what d’ya know, he already did that :grinning:). My own knowledge being incomplete, I wouldn’t have been able to provide reliable answers. What I can say is that the material should not shy away from CS theory or compiler internals if that is what it takes to complete the understanding. Also I feel the use of illustrations to show where lifetimes start, how they are longer or shorter than other lifetimes etc. will work very well.


#63

Yeah it’s all good! I didn’t mean that it had to be you, just the hard part is coming up with the details.

@vitalyd this is excellent, thank you! If anyone else has stuff like this, it would be excellent.


#64

Just added #12 there that I forgot to jot down yesterday.


#65

Bonus point if you can explain lifetimes without using words covariance/invariant. IMHO they’re PLT-jargon, and wikipedia article for Covariance and contravariance is quite bad (long-winded introduction, and explains it in terms of type constructors, whichi is another PLT-jargon term).


#66

Well, see this is the tradeoff. If you want an in-depth treatment of lifetimes, you have to talk about variance.

What do you think of https://doc.rust-lang.org/nomicon/subtyping.html ?


#67

I think you have to mention and explain (to some degree, it doesn’t have to be a full on treatise) variance in a (mini) book on lifetimes. There’s only so much handwaving before the rubber hits the ground. Subtyping might be an easier way to relate it, rather than the different variances. But, most importantly - show plenty of different examples so people can build at least an intuitive understanding/feel for it even if they want to gloss over the formal terms/definitions.

The intuition about lifetimes, or rather what they’re used for, is something that a lot of people will understand. For example, a dangling reference due to a mismatch between the lifetime of a referent and reference is fairly intuitive, even for people coming from managed languages. The difficult part is learning how the syntax and the rules are used to express these different relationships, and why some cases appear different from others (i.e. immutable vs mutable refs). One example that springs to mind is iterators returning immutable vs mutable references. Explaining why a streaming iterator cannot be built out of the current Iterator trait is a useful exercise, which has taken place many times on this (and likely others) forum.

A particularly helpful thing would be to demonstrate different lifetime errors that a compiler spits out, explain what it’s trying to prevent in that particular case, and then show how to express the desired contract to the compiler (or if it’s not expressible in the current type system, provide a suitable way around it). I think there’s quite a bit that can be done without constantly mentioning the different variance terms.


#68

That’s a pretty gentle introduction to these concepts, +1


#69

Better yet, explain with and without those words and provide clear, concise definitions (as used by Rust) for those terms with links/references for further understanding of those terms.


#70

Now if I only knew what PLT stood for :wink:


#71

If rather than ‘subtyping’ we called it ‘substitution’ then intuitively you’d be able to substitute 'a for 'b as 'a is larger.


#72

That would be backwards. If 'a : `b (a outlives b) then you could give something with a lifetime of 'a where something with a lifetime 'b was needed. Not the other way around.


#73

Ah yes got my 'a and 'b the wrong way around. Maybe instead of 'a and 'b we need 'universe 'sun or 'mayfly and 'whale…


#74

To add my half cent for the mini-book idea, as gilescope mentions, please mention that lifetimes don’t have to have these short, single letter, names.

I feel half of my own confusion is just dealing with “this 'a on this struct is different from that 'a on that other struct”. Even though I know it rationally, it is a cognitive burden to keep juggling all of the identical-name-different-meaning’s. Some more diversity in the names used would make this more intuitive.

For example, I’ve seen 'ctx used in graphics (glium, I think?), to denote the GL-context.
Another Glium example: impl<'a, T: ?Sized> From<&'a Buffer<T>> for BufferSlice<'a, T>: if I’m taking a slice of a Buffer, why not just call it’s lifetime “'buf”, or at the very least, 'b;

I understand the convention 'a is often used in much the same as we use T for random types, where no concrete “better” name exists, I just think that “better” names exist way more often than we currently think. (compare using <K,V> for maps, <I> for iterator types, etc.)

edit: challenging an accepted community naming convention may be slightly out of scope for a mini-book, though :stuck_out_tongue:


#75

I’m still a bit confused by the whole subtyping thing too. If, as per the mini-book, 'static can be substituted for 'a because 'static is 'a and more, why can’t 'a be substituted as 'b and more?


#76

Here’s an example of substituting generic lifetimes with an outlives bound:

struct Refs<'a, 'b: 'a> {
    shorter: &'a str,
    longer: &'b str,
}

impl<'a, 'b> Refs<'a, 'b> {
    fn get_shorter(&self) -> &'a str {
        // ok, we return a longer one, it’s not 'static
        // but compiler will ensure the outlives holds
        // when we create values of this struct.
        // if we didn’t have 'b:'a bound this would be invalid
        self.longer
    }

    fn get_longer(&self) -> &'b str {
        // this won’t compile
        // self.shorter
        // this is fine, of course
        "static"
    }
}

Not sure if that’s what you were asking about.


#77

In the mini-book 'a is longer, but in your example, 'b is longer–does that mean you’ve flipped them or is my (lack of) understanding the issue?


#78

What mini book are you referring to? It doesn’t matter which of them is longer - you can substitute the longer for shorter one in these cases.


#79

Maybe it’s not a mini-book. Looks like it’s the 'Nomicon–the subtyping and variance chapter: https://doc.rust-lang.org/nomicon/subtyping.html that @steveklabnik linked to earlier.

In this particular case, since we’re discussing why the longer is a subtype of the shorter, rather than the other way around, I do think which is longer matters, as things are somewhat confusing unless one already understands the concept.


#80

Ah the nomicon. We’re discussing creating a mini book on lifetimes so I was confused as to what you’re referring to.

So which particular example? Something on that nomicon page or the example I gave above with the Refs struct?

It does matter which is longer. My point was whether you do 'a:'b or 'b:'a just assigns a name to the outlives lifetime (the “longer” one). 'static is a subtype of every lifetime. For generic lifetime parameters you need to indicate which one outlives the other if that’s a property you require, and then assign those lifetimes appropriately (eg express which field has the longer or shorter, like in the Refs example).