Rust newbie questions


#1

I have been learning Rust occasionally for a while and I have a lot of questions about explicit lifetimes. Some of the answers might be found in the documents but I still want to make sure I really get it.

  1. Given that only references, as opposed to objects, can be borrowed in the situations of arguments passing, does that mean explicit lifetime annotations are only associated with parameters of reference type?

  2. Following the above, which means if a function does not have any reference type in its function signature (parameter and return value) then it doesn’t bother with explicit lifetime annotations at all. Right?

  3. A lifetime variable is meaningful only if it creates a relation between two or more values/references. Which means a lifetime variable 'a must associate something with unknown lifetime to something else with a known lifetime in order to propagate the lifetime information. Something like fn<'a> foo(x : &'a str) int or fn<'a> bar(x : &str) -> &'a str doesn’t convey any useful lifetimes information, much like the ‘defined by unused variables’. Am I correct?

  4. In the case of struct declarations, does the lifetime annotations mean the lifetime relationships between the containing struct object and its fields (of reference type) ?

  5. If lifetime annotations create relation between two references/objects, can I say something like the following:
    a. Define a binary operator ~
    b. A ~ B means A and B belong to the same lifetime (or in other words, has the same lifetime value).
    c. Then fn<'a> foo(x : &'a str) -> &'a str can be interpreted as: Given that x has an *initialized* lifetime L denoted by the variable'a, the returned str reference has an uninitialized (unknown) lifetimeb, We declarea ~ `b, so the compiler can assign L to both the variable 'a and 'b (or IOW to propagate L from 'a to 'b)

This is what all I have imagined about lifetimes annotations. :smile:
Do I get something wrong?


#2

Meta comment: make sure you understand lifetime elision rules: https://doc.rust-lang.org/beta/nomicon/lifetime-elision.html. This syntactic sugar is not calories free: sometimes your function itself compiles fine, but you get lifetimes errors at the call site because lifetime elision resulted in the wrong signature.

  1. Not exactly. You can have a generic type, parametrized over lifetimes:
struct Holder<'a> {
    field: &'a i32
}

fn make_holder(thing: &i32) -> Holder {
    Holder { field: thing }
}

fn make_holder_explicit<'a>(thing: &'a i32) -> Holder<'a> {
    Holder { field: thing }
}

In some sense, the opposite is true: you can think of &'a T syntax as a sugar for a generic type Reference<'a, T: 'a>.

  1. See the example above :slight_smile:

  2. Yes: lifetimes are for relating things to each other. The only lifetime you can get out of thin air is 'static.

  3. Not sure how to answer this question properly. I think the relation is the same as with plain references. Imagine this wrapper around a reference struct Holder<'a> { r: &'i32 }. It behaves exactly as &i32.

  4. Yes, this sounds right! Keep in mind though that it is the compiler who decides the lifetime L. That is, if the result of foo is required to live long, then it adds the constraint that the original L must live that long as well.


#3

Thanks for your explanations.

I have to digest this subject carefully again later when I have had better sleep. Yes, I am a little confused.

Regarding a type parametrized over lifetimes, it sounds like types in Rust is not just a ‘scalar’ but a tuple where one of its (two?) dimensions is of lifetimes.
I would probably post follow-up questions after I cleaning up my thoughts.


#4

(The following is my view toward my experience of learning Rust, a general self-centered ranting from a noob’s perspective :wink:)

I really feel like I need to learn the internal data model and representations of lifetimes to fully grasp the idea rather than just trying to get used to it or to appease the complier errors (despite I haven’t encountered too many so far). This reminds me of how I finally learned Git and confidently adopted it without backing up my repo every time I had to use a new combo of commands. I had to read the materials about the things under-the-hood rather than reciting the recipes copied from other people. Even for a much simpler language like Go, it helps a lot after reading the blog posts about the internal representation of slices and interfaces. Is there something similar for Rust, especially about the implementation of lifetimes? Hopefully it’s not monstrously complicated.


#5

Yes, in a sense, though only references use the extra bit. (I think most call these “kinds”).


#6

And you can think of the former as constraining the type spatially (or topologically :slight_smile: ) and the latter as constraining it temporally.


#7

This. (Now that I do have better sleep.)
I think the clarification about Reference<'a, T: 'a> is gold. Syntax sugar is really the enemy for noobs :smile:
It’s probably worth adding to the very beginning of the introduction of lifetimes in the docs at which the strange &'a notations suddenly start to appear everywhere.