Skip to content
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

Introduce a default object lifetime bound #599

Merged
merged 2 commits into from
Feb 12, 2015

Conversation

nikomatsakis
Copy link
Contributor

Add a default lifetime bound for object types, so that it is no longer necessary to write things like Box<Trait+'static> or &'a (Trait+'a). The default will be based on the context in which the object type appears. Object types that appear underneath a reference take the lifetime of the innermost reference under which they appear, and otherwise the default is 'static.

Examples:

  • &'a &'b SomeTrait becomes &'a &'b (SomeTrait+'b)
  • &'a Box<SomeTrait> becomes &'a Box<SomeTrait+'a>
  • Box<SomeTrait> becomes Box<SomeTrait+'static>
  • Rc<SomeTrait> becomes Rc<SomeTrait+'static>

Cases where the lifetime bound is either given explicitly or can be inferred from the traits involved are naturally unaffected.

text/ rendered

draft rendered

Acked-by: Niko Matsakis <niko@alum.mit.edu>
@aturon
Copy link
Member

aturon commented Jan 20, 2015

I wholeheartedly support this RFC. Besides the quantitative data presented here, I feel that these defaults closely match the intuitions I generally have about ownership and trait objects.

The interaction with lifetime elision seems similar to the use of sugared Fn notation (with HRTB), in that the interior lifetimes of a type are considered opaque from the point of view of lifetime elision. This, again, supports an ownership intuition where &Trait represents a single "unit" of borrowing, for lack of a better term.

I also support the idea of opt-in for "hidden reference" types -- that is, rather than making & special, allowing any "reference-like" type to participate in these rules.

I wonder how much extra work such an opt-in would be? There's some urgency because, presumably, one cannot decide to have a type opt in after it has already been stabilized. Since we already have some reference-like types in std, it seems prudent to get the story sorted out before 1.0.

@nikomatsakis
Copy link
Contributor Author

@aturon Hmm, I hadn't considered the fact that we could allow types to opt-in. I thought of it as backwards compatible, but you're right. Doesn't seem like it should be that hard, we mostly need to devise a syntax.

@reem
Copy link

reem commented Jan 20, 2015

+1, + 'static everywhere gets tiresome very quickly.

proposed here, the result would be `Ref<'a, SomeTrait+'static>`. If
this becomes a serious annoyance, in the future we could add the
option for declaring on the struct `Ref` that objects which appear in
type `T` should be defaulted with the lifetime `'a`.

Choose a reason for hiding this comment

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

It seems to me that this change would not be backwards compatible (it would change the bound from 'static to 'a).

So I think this needs to be implemented now or it must be an error to not specify a bound in any case where an additional rule is to be implemented in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding some opt-in mechanism to override the default would be backwards compatible, but it'd be incompatible to actually use that mechanism on an existing type. Regardless, there are types in the standard library for which we might want it, so it might be worth discussing now, as @aturon pointed out.

@nikomatsakis
Copy link
Contributor Author

So as both @aturon and @bill-myers pointed out, it is perhaps a good idea to consider if we can adopt an extensible default scheme now. In the RFC I proposed (and rejected) building on the existing T:'a annotations that appear in types. I'd like to reconsider that idea as a strawman proposal. Consider what a Ref type looks like today

struct Ref<'a, T:'a> {
    x: &'a T
}

We could say that if the struct declaration specifies exactly one bound of the form T : 'a for some type parameter T, then this is the default lifetime bound for values of that parameter (after suitable substitutions). If multiple bounds are given, then there is no default bound (as today, an explicit value would be required). If no bounds are given, then the default is inherited from the environment.

Some examples:

struct RefStatic<T:'static> { ... }
struct Ref0<'a,T> { ... }
struct Ref1<'a,T:'a> { ... }
struct Ref2<'a,'b,T:'a+'b>{ ... }

RefStatic<Writer> --> RefStatic<Writer+'static> // as in the RFC
&'a RefStatic<Writer> --> &'a RefStatic<Writer+'static> // explicit 'static overrides the 'a, unlike RFC
Ref0<'a, Writer> --> Ref0<'a, Writer+'static> // as in the RFC
Ref1<'a, Writer> --> Ref1<'a, Writer+'a> // unlike RFC
Ref2<'a, Writer> --> Ambiguity error // as today, unlike RFC

In the future, we could potentially try to handle multiple bounds, but only if one of them is unambiguously "biggest". For example struct Ref2<'a,'b:'a,T:'a+'b> -- but in that case, why use both T:'a+'b and not just T:'b which implies both?

The advantage of this is that it is guided by the existing type annotations and tries to pick a type that best meets what the struct said is required. However, it is subtle and may be hard to explain. Moreover, if we do decide to infer the T:'a annotations, I don't think we'll be able to use the results of inference to drive defaults, since it's very awkward to have to do inference when you don't even know the types of the fields (and we can't figure out the full types without knowing what defaults to apply). So we'd presumably still require explicit annotations if you want to guide this lifetime bound default.

I'm mildly concerned that this matter of lifetime bounds is a kind of obscure concern. I simultaneously don't want to dedicate a big keyword to it (pro to this comment's proposal) but also don't want it to ride on some subtle bit of syntax that is so easily overlooked and maybe confusing (con to this comment's proposal).

@Ericson2314
Copy link
Contributor

I'm normally suspicious of such inference, but this RFC seems remedy a situation where rustc helped you uncharacteristically little, so +1. After this, it will be more consistently magical.

Less sure about the previous comment, bounds on parameters doing that just spooks me out.

@Kimundi
Copy link
Member

Kimundi commented Jan 25, 2015

I'm in favor of this RFC in general, and also think it would be a good idea to generalize the defaulting behavior behind reference types as proposed in @nikomatsakis last comment.

It feels more consistent if both the build-in and custom reference types behave the same in regard to the proposal. Otherwise, custom ones might feel a bit second-class due to added lifetime verbosity.

@steveklabnik
Copy link
Member

Big 👍 from me here, this matches my intuitions as well.

@pythonesque
Copy link
Contributor

+1 from me (I do kind of like the inference idea as well).

@huonw
Copy link
Member

huonw commented Jan 30, 2015

I'm in favour.

Another way to make avoiding default 'static bounds easier would be to support some marker (e.g. _) to introduce an explicit anonymous lifetime, i.e. fn foo(x: Box<Trait + '_>) == fn foo<'a>(x: Box<Trait + 'a>).

(The precise '_ notation may not be the best, since it differs slightly to _ in types.)

@nikomatsakis
Copy link
Contributor Author

@huonw I would very much like some sort of marker like that, and I've considered just that syntax. I actually think it fits ok -- that is, _ in fn signatures could correspond to "fresh parameter", and _ in bodies could mean "fresh type variable". This is precisely analogous to what we do with omitted lifetimes and I personally find it ok. ymmv.

Anyway, the other use cases where I would like that in the compiler is this:

fn foo<'a>(x: &SomeContext<'a,'tcx>, ...)

Here I only need the 'a so that I can specify the 'tcx. I'd rather write:

fn foo(x: &SomeContext<'_,'tcx>, ...)

This comes up fairly regularly in the compiler anyhow, and I suspect it will come up in other programs that are making aggressive use of lifetimes/arenas/etc.

@brson
Copy link
Contributor

brson commented Feb 5, 2015

For the record, I continue to be worried about the subtle implicit sugar that Rust is growing. Hard to know what's going on.

@nikomatsakis
Copy link
Contributor Author

I adapted the text to describe driving defaults based on user annotations, as discussed in this comment.

@aturon
Copy link
Member

aturon commented Feb 5, 2015

@nikomatsakis

I adapted the text to describe driving defaults based on user annotations, as discussed in this comment.

Looks great! I'm happy with this RFC.

@aturon
Copy link
Member

aturon commented Feb 12, 2015

This RFC has received broad support, and is a blocker for Send changes. We have decided to move forward with it, including the updates for annotation-driven defaults.

Tracking issue

bors added a commit to rust-lang/rust that referenced this pull request Feb 16, 2015
…felix

Implement rules described in rust-lang/rfcs#599.

Fixes #22211.

~~Based atop PR #22182, so the first few commits (up to and including "Pacify the mercilous nrc") have already been reviewed.~~
@Centril Centril added A-typesystem Type system related proposals & ideas A-inference Type inference related proposals & ideas A-lifetimes Lifetime related proposals. labels Nov 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-inference Type inference related proposals & ideas A-lifetimes Lifetime related proposals. A-typesystem Type system related proposals & ideas
Projects
None yet
Development

Successfully merging this pull request may close these issues.