Skip to content

Generalize object and type parameter bounds#192

Closed
nikomatsakis wants to merge 2 commits intorust-lang:masterfrom
nikomatsakis:bounds
Closed

Generalize object and type parameter bounds#192
nikomatsakis wants to merge 2 commits intorust-lang:masterfrom
nikomatsakis:bounds

Conversation

@nikomatsakis
Copy link
Contributor

An RFC summarizing type system changes that close up rust-lang/rust#5723 and related bugs.

Copy link
Contributor

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/

@lilyball
Copy link
Contributor

lilyball commented Aug 7, 2014

👍

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/hapenning/happening/

@Valloric
Copy link

Valloric commented Aug 7, 2014

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.

Copy link
Contributor

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).

Copy link
Member

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).

Copy link
Contributor Author

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 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).

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)?

@brson
Copy link
Contributor

brson commented Aug 12, 2014

@brson brson closed this Aug 12, 2014
@glaebhoerl
Copy link
Contributor

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:

struct Foo<'a> {
    f: fn(&'a Bar) -> ...;
}

instead of

struct Foo {
    f: <'a> fn(&'a Bar) -> ...;
}

?

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 Transmute/Coercible any time you want to safely change the types or lifetimes of inner types (type arguments). With the new 'a: 'b bounds the relevant impls could even be written in the language itself, instead of being magically wired-in: impl<'a, 'b: 'a, T> Transmute<&'a T> for &'b T { } (and likewise for &mut).

@nikomatsakis
Copy link
Contributor Author

On Wed, Aug 13, 2014 at 09:11:05AM -0700, Gábor Lehel wrote:

E.g., do you ever want something like:

struct Foo<'a> {
    f: fn(&'a Bar) -> ...;
}

instead of

struct Foo {
    f: <'a> fn(&'a Bar) -> ...;
}

This is supported and will be inferred to be covariant. However, this has seen very little use.

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.)

No, we cannot do this, there are definitely times when lifetime
parameters must be invariant. For example, 'b in a struct definition
like:

struct Foo<'a,'b:'a> {
    foo: &'a mut Foo<'b>
}

However, I have been contemplating a plan to make:

  • lifetime parameters contravariant by default
  • type parameters covariant by default in types
  • a mut prefix to declare invariance

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
couldn't be regained with some more potential annotations. One problem
with this plan is what to do about associated types (today: trait type
parameters -- which reminds me, I need to make a note on the RFC).
With the move to unboxed closures, fn types generally become object
types, and hence we may be unsatisfied without contravariance and
covariance. And it's not clear to me that the transmute plan below can
address this.

My "grand plan" would also involve making type parameters be always invariant , and using Transmute/Coercible any time you want to safely change the types or lifetimes of inner types (type arguments). With the new 'a: 'b bounds the relevant impls could even be written in the language itself, instead of being magically wired-in: impl<'a, 'b: 'a, T> Transmute<&'a T> for &'b T { } (and likewise for &mut).

I guess it'd be worth experimenting a bit. The last few times I
investigated "removing subtyping" it wound up either breaking a lot of
code or essentially being reintroduced through increasingly elaborate
coercion rules. I will note that I frequently encounter problems
because Option<&'a T> is (currently) invariant with respect to &'a T. Aggressive use of the Transmute relation would likely help here
in many cases, though it adds its own complications around inference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants