Maybe part of the confusion here is this: A function has a declaration and a body that are cleanly separated, and it's easy to say that "all the relevant borrow checker information is in the declaration; the body doesn't matter". Whereas a struct doesn't seem to have this fully-developed separation. @parasyte seems to be saying SomeStruct<'a, 'b> is essentially a declaration, but @quinedot is saying there is other relevant stuff in the body. Maybe that stuff is used to infer "invisible attributes" of the declaration, but since there's no syntax to express it, you can't even compactly show what the result is (unlike lifetime elision, for example).
Pedagogically, for me anyway, if you could somehow express this and show how what you write in the source text turns into the "declaration" that the borrow checker actually works with, it might make it clearer.
Yes ā in my brain this seems obvious, because they are generic parameters, and just like function parameters (or generic type parameters), their names don't matter, only their positions do. (Of course now I know they weren't always generic parameters, so, nice job there!)
I think your thought is confused by thinking; there is a single structure(or function) and it has generic parameters. If you approach it as a generic-structure is used to produce multiple concrete structures; you can hopefully better comprehend. It gets confusing though due to inference.
To me it's good enough. It could say "...canāt outlive the variable the part field is referring to." It's a maxim; It does not cover the importance that the lifetime parameter 'a could represent borrowing from multiple variables.
arguably some mention of auto-traits, or at least Sized or ?Sized
There's no surface syntax for some of these. Many of these properties don't even show up in the docs (but should IMO). It would also be nice if there was some succinct way to annotate the properties and have the compiler cross-check them for you, as these are all semver-related properties. The main tool we have now is writing some test for every leaky property you care about. Maybe cargo-semver-checks will be up to it eventually.
If there was syntax for variance, the "must use parameter" rules could be relaxed.
However, "outlives" is something you can determine from the header alone, since it's syntactically defined.
I feel like most of the invisible inference on structs are things you will rarely have issues with, in practice. Auto traits all show up in documentation/rust-analyzer (there are some deep cuts with higher-ranked trait bounds where this isn't a helpful approximation, but that's more of a function signature problem than a "struct signature" problem).
Other things like the need for #[my_dangle] will only show up when you indeed try to impl Drop on such a struct [1].
Variance is another great example: &T, Cell<&T> and fn(&T) fields all have different requirements that are important for implementers of the struct. But the variance almost never matters for users of the struct.
To me, SomeStruct<'a, 'b> is a type name. Its fields are not part of the name, per se, but its generic parameters certainly are.
Anyway, I'm by no means a great source for reasoning on this topic. I just felt I could add some intuitive explanation for why the lifetime annotation syntactically exists in this position on structs. The concept of being able to "trace" references through lifetime annotations has been immensely helpful to my mental model (though far from perfect).
I think you're too philosophical about it. For Rust users the most important aspect of this is not that the struct is generic, but that the struct containing loans becomes restricted by the borrow checker.
Lifetimes may be implemented as generics, but that's like an implementation detail, and the borrow checking restrictions are the primary feature here. It's important to understand that usage of the struct becomes severely limited, and there's much more to that than it being a generic parameter.
This is also why &'static gets a pass. It's a special case that doesn't add any restrictions, and therefore is uninteresting from the borrow checking perspective, and doesn't need syntax for "beware, this is temporary!".
@kornel Yes, exactly ā that's why statements like the original trigger for this thread seem wrong when they focus on the generic arguments as being what "dictates" the borrow checker restrictions. And as the 2012 post shows, there were alternatives to generic arguments that might have been used to express the same thing.
Thanks to everyone for their time and thoughtful comments on this thread. I realized part of the motivation for me is that as a language user (and, a long time ago, language designer), I want every utterance in the language to have a meaning. So this has really all been about answering the question: what is the meaning of SomeStruct<'a, 'b>? Does it mean (among other things) that SomeStruct must be outlived by two given lifetimes 'a and 'b? Or is that fact merely an inference you can make, based on your knowledge that generic parameters must be used, therefore the body of the struct must have some references with those lifetimes, therefore the struct must not outlive them?
In practice that may not make much difference, but in trying to learn and understand the language that is (to me) a source of ambiguity and confusion.
It means that SomeStruct<'a, 'b> is only a valid type within the intersection of 'a and 'b.[1] (Unfortunately there is no concise syntax for lifetime intersections.)
That is a way to think about it with the approximated mental model of "lifetime parameters mean there's a temporary reference inside". However, that approximation isn't always true, and that's not how things actually work at the type level.
From a practical point of view... the "temporary references inside" mental model is probably easier for most to grasp when learning the language.
That said, when such approximations are presented to me, I strongly desire to have a heads-up that this is an approximation. Otherwise I will eventually find out that my foundation is incomplete or incorrect and will have to go back and relearn many things with the more accurate understanding.
Are lifetime parameters special in this way? That is, do generic type parameters also have meaning(s) beyond the mere fact that the type is somehow used inside the definition? It feels like there is something a bit mystical going on that is hidden by making lifetime parameters look like generic parameters.
Is there a description you can link to (and havenāt already) that goes into the full implications of lifetime parameters? (The Reference hasnāt gotten that far, evidently.)
Well, Vec<T> is only a valid type where T is a valid type, and Vec<T>: 'x only if T: 'x, whether or not you have an actual T in a field.
In terms of when parameterized types are valid in the type system, and how outlives bounds work, the best thing I'm aware of is the previously linked RFC.