-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generalize object and type parameter bounds #192
Conversation
region parameters as follows. Within a function or method, we apply | ||
the wellformedness function `WF` to each function or parameter type. | ||
This yields up a set of relations that must hold. The idea here is | ||
that the caller could have type checked unless the types of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/could have/could not have/
👍 |
|
||
Currently, the type system is not supposed to allow references to | ||
escape into object types. However, there are various bugs where it | ||
fails to prevent this from hapenning. Moreover, it is very useful (and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/hapenning/happening/
Related bug: rust-lang/rust#13703. I hit that one all the freaking time. I'm ecstatic to see people are thinking about solutions to this issue. |
A *lifetime bound* is written `'a:'b` and it means that "`'a` outlives | ||
`'b`". For example, if `foo` were declared like so: | ||
|
||
fn foo<'a, 'b:'a>(...) { ... } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presentation suggestion: If you write the definition of -:-
as 'a:'b
(i.e. with formals 'a
and 'b
), do not immediately follow it with an example that uses 'b:'a
(i.e. swapping the arguments), since it is rather easy for someone to overlook that swap and then get very confused by the sentence "the lifetime 'a
was shorter than (or equal to) 'b
" that immediately follows "'a
outlives 'b
" above.
Instead, either use 'b:'a
in both places, or choose fresh variable names for the concrete example, like
foo<'x, 'y:'x>(...) { ... }
(the latter is my preference).
to exactly one lifetime.
However, there are complications: | ||
|
||
- What about a type like `struct Ref<'a, T> { x: &'a T }`? `Ref<'a, Trait>` | ||
should really work the same way as `&'a Trait`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I think that we can special case &
at least because it is a special case in general (it even has special syntax), I wouldn't necessarily expect to apply the same rules to Ref
. I agree Box
is tricky since it should behave like a regular struct. (Although having to rarely have a redundant lifetime seems a price worth paying for simplifying the common case, unless I misunderstand the complications here).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Thu, Aug 07, 2014 at 04:08:45AM -0700, Nick Cameron wrote:
I guess I think that we can special case
&
at least because it is a special case in general (it even has special syntax), I wouldn't necessarily expect to apply the same rules toRef
. I agreeBox
is tricky since it should behave like a regular struct. (Although having to rarely have a redundant lifetime seems a price worth paying for simplifying the common case, unless I misunderstand the complications here).
I have been using Box
as a standin for "a regular struct that does
not implicitly contain borrowed data", basically. So I would expect
e.g. Box
and Rc
(and Vec
) to behave the same with respect to
their type arguments. My bigger point here was that if you said, for
example, that we would apply a 'static
default as the lifetime bound
for object types that appear in a type argument, it would do an
annoying (and almost certainly wrong) thing for Ref
.
It is also mildly unclear to me whether it makes sense for there to be
one default for the lifetime bound on an object type and a distinct
set of defaults for lifetime parameters. In other words, should people
have to write &'a SomeStruct<'a>
or should the latter 'a
also be a
default (presuming we did do defaulting for traits)?
Discussed at https://github.com/rust-lang/meeting-minutes/blob/master/weekly-meetings/2014-08-12.md. Accepted as RFC 49. |
Man, this was fast. I hadn't even read it yet. But now that I have I don't have anything to add, other than +1. (I haven't fully understood the tension between the various forms of inference the RFC doesn't propose doing, but not inferring is safely forwards-compatible and can be liberalized later, so I agree with this as well, even without understanding it yet.) There's one thing I've been thinking about however, and this seems like an appropriate place to ask about it: Is there ever a use case for lifetime parameters on types to be anything other than contravariant? As a subcase: is it ever preferable to have the lifetimes on (higher-order) function parameters be fixed from outside, instead of higher-rank? E.g., do you ever want something like:
instead of
? The reason I'm wondering is that if we can declare that lifetime parameters on types are always contravariant, then we can avoid the need for any kind of variance inference or annotations, which would be very nice. (This would also nicely plug the hole in lifetime elision w.r.t. covariant lifetimes: if covariant lifetimes on types are impossible, then avoiding their elision is trivial.) My "grand plan" would also involve making type parameters be always invariant , and using |
On Wed, Aug 13, 2014 at 09:11:05AM -0700, Gábor Lehel wrote:
This is supported and will be inferred to be covariant. However, this has seen very little use.
No, we cannot do this, there are definitely times when lifetime
However, I have been contemplating a plan to make:
I wanted to gather some data and was hoping to do it in the next few days. Obviously this plan loses some expressiveness, though nothing that
I guess it'd be worth experimenting a bit. The last few times I |
Implements rust-lang/rfcs#192. In particular: 1. type parameters can have lifetime bounds and objects can close over borrowed values, presuming that they have suitable bounds. 2. objects must have a bound, though it may be derived from the trait itself or from a `Send` bound. 3. all types must be well-formed. 4. type parameters and lifetime parameters may themselves have lifetimes as bounds. Something like `T:'a` means "the type T outlives 'a`" and something like `'a:'b`" means "'a outlives 'b". Outlives here means "all borrowed data has a lifetime at least as long". This is a [breaking-change]. The most common things you have to fix after this change are: 1. Introduce lifetime bounds onto type parameters if your type (directly or indirectly) contains a reference. Thus a struct like `struct Ref<'a, T> { x: &'a T }` would be changed to `struct Ref<'a, T:'a> { x: &'a T }`. 2. Introduce lifetime bounds onto lifetime parameters if your type contains a double reference. Thus a type like `struct RefWrapper<'a, 'b> { r: &'a Ref<'b, int> }` (where `Ref` is defined as before) would need to be changed to `struct RefWrapper<'a, 'b:'a> { ... }`. 2. Explicitly give object lifetimes in structure definitions. Most commonly, this means changing something like `Box<Reader>` to `Box<Reader+'static>`, so as to indicate that this is a reader without any borrowed data. (Note: you may wish to just change to `Box<Reader+Send>` while you're at it; it's a more restrictive type, technically, but means you can send the reader between threads.) The intuition for points 1 and 2 is that a reference must never outlive its referent (the thing it points at). Therefore, if you have a type `&'a T`, we must know that `T` (whatever it is) outlives `'a`. And so on. Closes #5723.
Implements rust-lang/rfcs#192. In particular: 1. type parameters can have lifetime bounds and objects can close over borrowed values, presuming that they have suitable bounds. 2. objects must have a bound, though it may be derived from the trait itself or from a `Send` bound. 3. all types must be well-formed. 4. type parameters and lifetime parameters may themselves have lifetimes as bounds. Something like `T:'a` means "the type T outlives 'a`" and something like `'a:'b`" means "'a outlives 'b". Outlives here means "all borrowed data has a lifetime at least as long". This is a [breaking-change]. The most common things you have to fix after this change are: 1. Introduce lifetime bounds onto type parameters if your type (directly or indirectly) contains a reference. Thus a struct like `struct Ref<'a, T> { x: &'a T }` would be changed to `struct Ref<'a, T:'a> { x: &'a T }`. 2. Introduce lifetime bounds onto lifetime parameters if your type contains a double reference. Thus a type like `struct RefWrapper<'a, 'b> { r: &'a Ref<'b, int> }` (where `Ref` is defined as before) would need to be changed to `struct RefWrapper<'a, 'b:'a> { ... }`. 2. Explicitly give object lifetimes in structure definitions. Most commonly, this means changing something like `Box<Reader>` to `Box<Reader+'static>`, so as to indicate that this is a reader without any borrowed data. (Note: you may wish to just change to `Box<Reader+Send>` while you're at it; it's a more restrictive type, technically, but means you can send the reader between threads.) The intuition for points 1 and 2 is that a reference must never outlive its referent (the thing it points at). Therefore, if you have a type `&'a T`, we must know that `T` (whatever it is) outlives `'a`. And so on. Closes #5723.
An RFC summarizing type system changes that close up rust-lang/rust#5723 and related bugs.