Closed vojtechkral closed 2 months ago
As a consequence, I'd be careful with these takeaways:
T
does not have to be valid for the entire program
- It does, the type has to be valid for the entire program, but this is unrelated to values of that type. Those might or might not exist at arbitrary times.
can have lifetimes of different durations
- If a type
T
is static, then either it has no lifetime parameters at all, or it has, but they are instantiated as'static
. Values ofT
may have arbitrary lfietimes, but this is inrelated to the type itself.
(Editing as I'm trying to make this sound less pedantic / annoying / etc., not sure if that's a succes, sorry! :grimacing:)
I absolutely agree with the blogpost that T: 'static
shouldn't be read as "T has a 'static lifetime", that's indeed pretty misleading. However, I don't believe reading it as "T is bounded by a 'static lifetime" is a good idea either, because the bound actually goes in the other direction :grin:
The syntax 'a: 'b
means "'a
lives at least as long as 'b
", ie. 'b
is bounded by 'a
, not the other way around.
Thereby, the syntax T: 'static
literally means "Type T
exists at least as long as the 'static
lifetime" (where "Type T
" really is just talking about the type, not values of it).
Personally I read T: 'static
as "T is a static type".
The syntax
'a: 'b
means "'a
lives at least as long as'b
", ie.'b
is bounded by'a
, not the other way around.
Bound in this context should not be confused with bound as in upperbound/lowerbounded. That is, 'a: 'b
or 'a bounded by 'b
doesn't mean lifetime 'a
is upperbounded by lifetime 'b
.
Bound in this context should be understood as bound as in bounded set. That is, 'a: 'b
or 'a bounded by 'b
means 'a
is a subtype of 'b
. Representing them in Venn Diagram, the subtype 'a
is within the supertype 'b
, thus 'a is bounded by 'b
.
Representing them in Venn Diagram, the subtype
'a
is within the supertype'b
, thus'a is bounded by 'b
.
What would be the meaning of such a Venn diagram?
If you wanted to make, for example, a Venn diagram of code lines / locations where a lifetime is valid, then it would be exactly the other way around - 'b
would be within 'a
(hence 'a
"outlives" 'b
as the reference puts it)
it would be exactly the other way around - 'b would be within 'a (hence 'a "outlives" 'b as the reference puts it)
'a
outlives 'b
thus 'a
is a smaller set in the Venn diagram, which is bounded by a larger set 'b
.
See https://doc.rust-lang.org/nomicon/subtyping.html?highlight=subtyp#subtyping-and-variance
T: U
iff T
is a subtype of U
. 'a: 'b
iff 'a
is a subytpe of 'b
.Representing the subtype and supertype as sets as in set theory, it's clear that the set of subtype must be a subset of the set of supertype. In Venn Diagram, subset is contained insides superset.
That's the correct way to understand bound
.
Oh, I see what you mean. The set of types B: 'b
is a subset of the types A: 'a
. You're right.
Still not sure about the "bound" wording, in context of Rust by "bound" I usually understand the whole clause 'a: 'b
or any other such clause.
The problem is that people conflate validity of types with lifetime of values.
If the reader interprets "If T: 'static
then T
must be valid for the entire program" as the former then it's probably inaccurate, but if they interpret it as the latter then it is accurate, so I guess it's arguably ambiguous. If I changed the phrasing to be "If T: 'static
then values of type T
must be valid for the entire program" would you consider this issue fixed?
Thereby, the syntax
T: 'static
literally means "TypeT
exists at least as long as the'static
lifetime"
That's not true though, I can create and drop a String
before the end of the program, and String: 'static
. Nobody would say that all String
s have 'static
lifetimes though.
We could say "if T: 'static
then T
CAN live at least as long as 'static
" but does that mental model still work for 'b: 'a
? Actually, it kinda does, although most Rust material describes the latter as "'b
outlives 'a
" the following is also technically true: "'b
CAN live at least as long as 'a
".
Hmmm, I'm gonna think about the phrasing some more.
If I changed the phrasing to be "If
T: 'static
then values of typeT
must be valid for the entire program" would you consider this issue fixed?
Yeah, that seems perfectly fine :slightly_smiling_face:
That's not true though, I can create and drop a
String
before the end of the program, andString: 'static
. Nobody would say that allString
s have'static
lifetimes though.
Well that's right String
s indeed don't have 'static
lifetimes, the String type has no lifetimes. References to such values contain lifetimes (and thereby their type exist for a limited time unlike the String's)...
We could say "if
T: 'static
thenT
CAN live at least as long as'static
" but does that mental model still work for'b: 'a
? Actually, it kinda does, although most Rust material describes the latter as "'b
outlives'a
" the following is also technically true: "'b
CAN live at least as long as'a
".
Yeah, the "outlives" terminology is kind of confusing since it's actually inclusive (ie. it's a >=
, not only >
). In fact, if T: 'static
, then it implies that T
must exists at least "as long as" 'static
(and since there's no longer lifetime than 'static
, this defaults to equivalence) and that implies that values of T
might exist that long as well, in theory. For example, you can do this:
use std::mem::MaybeUninit;
static FOO: MaybeUninit<String> = MaybeUninit::uninit();
This wouldn't be possible if String
(as a type) weren't 'static
. The value isn't going to be initialized properly of course, but that's a technical issue... Maybe if the OS provided some preallocated heap space, this could be done 'for real'. But whatever, what matters here is that it type-checks.
Thanks for looking into this.
although official rust documentation and even the rust compiler refers to lifetime bounds as... well... "lifetimes bounds", i agree that saying "T is bounded by 'static" is kinda confusing for people to read and "T can live at least as long as 'static" is much clearer, so i've made those changes in this commit: https://github.com/pretzelhammer/rust-blog/commit/ed95bcbf7f0efdbd20d94d5e77b9870b9d2b72e7
i know i said earlier that i would change the name of the section to "If T: 'static then values of type T must be valid for the entire program" but i actually can't do that since it would break people's links to that section (github derives the hash links from the document's headings), so the title will have to stay as it is currently, which i think is fine, since everyone reading the article more-or-less gets what i'm saying anyway
TL;DR The misconception that if T: 'static then T must be valid for the entire program ... is not a misconception. It's literally true, in a sort of pedantic way.
The problem is that people conflate validity of types with lifetime of values. The fact that a type is valid for an entire duration of a program is disctinct from whether or not there are values of that type physically existing at some point or another.
A type
T: 'static
is valid even before you create any values of it, in fact, it's valid even if it's absolutely impossible to create any values of it, such as with the!
orInfallible
or similar types. On the other hand, a non-static type can only be referred to in some part of a program where the type contains some specific lifetime, which only exists in that part of the prorgam. Again, this is distinct from any actual values of that type - those might not exist either.As a consequence, leaking is completely unrelated to whether a type is
'static
or not, since leaking deals with values. You can leak values of non-static types just fine. I'm afraid mentioning leaking in that section only increases the confusion.Here's a demonstration of this:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3615f118612c24891f44e2e0c820d3a5
Hopefully this'll make sense. It's kind of hard to show this in an actual code, since I don't think there's a way of implementing functions for a non-static type only (there's no non-static marker trait etc.).