new RFC: static_lifetime_in_statics #1623

Merged
merged 5 commits into from Aug 22, 2016

Projects

None yet
@llogiq
Contributor
llogiq commented May 20, 2016 edited

Adding 'static lifetimes to every reference or generics lifetime value in static or const declarations adds no value. So this PR suggests we allow to omit them.

Rendered

@nikomatsakis
Contributor

Seems like a reasonable idea; I've thought about proposing a change like this from time to time. There are in fact no other lifetime names in scope at all, so 'static is literally the only thing you could write -- except for within higher-ranked functions and objects.

The RFC is a bit incomplete in that it doesn't seem to spell out how this should interact.

Functions I think work today, so presumably they continue the same:

static foo: fn(&u32) -> &u32 = ...;  // for<'a> fn(&'a u32) -> &'a u32
static foo: &Fn(&u32) -> &u32 = ...; // &'static for<'a> Fn(&'a u32) -> &'a u32

For object types using <>, I presume it defaults to 'static:

trait SomeObject<'a> { ... }
static foo: &SomeObject = ...; // &'static SomeObject<'static>
static foo: &for<'a> SomeObject<'a> = ...; // &'static for<'a> SomeObject<'a>
@llogiq
Contributor
llogiq commented May 20, 2016

Exactly. I will extend the Detailed Design section to include your examples.

@nikomatsakis nikomatsakis commented on an outdated diff May 20, 2016
text/0000-static.md
+- Feature Name: static_lifetime_in_statics
+- Start Date: 2016-05-20
+- RFC PR: (leave this empty)
+- Rust Issue: (leave this empty)
+
+# Summary
+[summary]: #summary
+
+Let's default lifetimes in static and const declarations to `'static`.
+
+# Motivation
+[motivation]: #motivation
+
+Currently, having references in `static` and `const` declarations is cumbersome
+due to having to explicitly write `&'static ..`. On the other hand anything but
+static is likely either useless, unsound or both. Also the long lifetime name
@nikomatsakis
nikomatsakis May 20, 2016 Contributor

As I wrote, since 'static is the only name in scope, it's certainly not "unsound" -- other names are just a guaranteed compilation error. I'd just drop this sentence.

@nikomatsakis nikomatsakis commented on an outdated diff May 20, 2016
text/0000-static.md
+```
+static my_awesome_table: &[&HashMap<Cow<str>, u32>] = ..
+```
+
+The type declaration still causes some rightwards drift, but at least all the
+contained information is useful.
+
+# Detailed design
+[design]: #detailed-design
+
+The same default that RFC #599 sets up for trait object is to be used for
+statics and const declarations. In those declarations, the compiler will assume
+`'static` when a lifetime is not explicitly given in both refs and generics.
+
+Note that this RFC does not forbid writing the lifetimes, it only sets a
+default when no is given. Thus the change is unlikely to cause any breakage and
@nikomatsakis
nikomatsakis May 20, 2016 Contributor

I wouldn't say "unlikely": I believe is completely backwards compatible.

@nikomatsakis nikomatsakis commented on an outdated diff May 20, 2016
text/0000-static.md
+having to add `'static` without any value to readability. People will resort to
+writing macros if they have many resources.
+* Write the aforementioned macro. This is inferior in terms of UX. Depending on
+the implementation it may or may not be possible to default lifetimes in
+generics.
+* Infer types for statics. The absence of types makes it harder to reason about
+the code, so even if type inference for statics was to be implemented,
+defaulting lifetimes would have the benefit of pulling the cost-benefit
+relation in the direction of more explicit code. Thus it is advisable to
+implement this change even with the possibility of implementing type inference
+later.
+
+# Unresolved questions
+[unresolved]: #unresolved-questions
+
+* Does this change requires changing the grammar?
@nikomatsakis
nikomatsakis May 20, 2016 Contributor

No, unless you had a distinct grammar for types in statics, which we do not and would not, I don't think.

@llogiq
Contributor
llogiq commented May 20, 2016

Thank you @nikomatsakis for those detailed comments!

@ticki
Contributor
ticki commented May 20, 2016

I know this might be a little controversial, but I am for allowing full type inference in statics and consts, since it adds a nice symmetry, and makes a lot of things more ergonomic.

@llogiq
Contributor
llogiq commented May 20, 2016

@ticki As I wrote in the Alternatives section, even with full type inference, having the default still allows us to declare the types thus documenting them without writing out all those 'static. On the other hand, if we only added type inference, the extra lifetime annotations would make writing and reading the types so cumbersome that users will likely have rustc infer them. So we should implement this issue especially if we'd later adopt inference.

@ticki
Contributor
ticki commented May 20, 2016

Sure, I was merely noting. I do support this RFC.

@llogiq
Contributor
llogiq commented May 20, 2016 edited

Full inference would be great. I do however wonder two things: First, will this change give us a good etnough solution for the majority of cases (with its vastly smaller complexity)? And second, would I want to do the inference for complex statics in my head (because I cannot simply read the type if it's inferred, though IDEs could probably help with that)?

Currently I think that on balance for simple cases writing the type is relatively cheap in terms of code size, whereas with more involved types (especially with types containing closures), I'd rather see them written down as documentation. But I may be wrong.

@nikomatsakis
Contributor

It would certainly be convenient for us to do inference, but I am wary: it
is a significant complication to the compiler and I'd not be inclined to
undertake it right now, particularly as we are still working out the
interaction of constants, types, and generics. It may be that once we've
worked through that, though, adding inference would be relatively
straightforward (I think that solving these questions nicely will require
us resolving constants more lazilly, and with some kind of live cycle
detection, which we could probably leverage to do type inference there
too).

However, as the RFC argues, even if we did type inference, I imagine some
people will want to write explicit types, and in those cases 'static
feels like a sensible default.

On Fri, May 20, 2016 at 2:37 PM, llogiq notifications@github.com wrote:

I think full inference would be great. I do wonder two things: First, will
this change give us a good etnough solution for the majority of cases (with
its vastly smaller complexity)? And second, would I want to do the
inference for complex statics in my head (because I cannot simply read the
type if it's inferred, though IDEs could probably help with that)?

Currently I think that on balance for simple cases writing the type is
relatively cheap in terms of code size, whereas with more involved types
(especially with types containing closures), I'd rather see them written
down as documentation. But I may be wrong.

β€”
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#1623 (comment)

@birkenfeld

I think symmetry with function signatures and other items (public API is explicitly typed) is more important than symmetry with let bindings.

What I sometimes wish for is inference of the length for array types, i.e. being able to write const X: [T; _] = [...].

@ticki
Contributor
ticki commented May 20, 2016

@nikomatsakis, I certainly agree that there are priorities, and it will likely increase complexity (although I can think of multiple ways to attack this). The change described in this RFC is relatively simple.

@birkenfeld, I disagree. static and const can both be thought of as global variables, and thus in the same class as let bindings.

@llogiq
Contributor
llogiq commented May 20, 2016

@ticki One difference between let bindings and static/const declarations is that the latter two can appear and be used anywhere in the code, whereas the former is confined to its scope. The locality is what changes the balance in favor or against inference, not the declaration semantics.

@ticki
Contributor
ticki commented May 20, 2016

Yes, but the fundamental concept is the same: they're both value declarations associated with a particular identifier.

@birkenfeld

@birkenfeld, I disagree. static and const can both be thought of as global variables, and thus in the same class as let bindings.

I don't dispute that. But there isn't only one "class" in which these things can be placed - and I happen to think that consistency within another class - item-level signatures - is more important.

@petrochenkov
Contributor

Yes, but the fundamental concept is the same: they're both value declarations associated with a particular identifier.

There are a bunch of "fundamental concepts" lying around here - global vs local, interface vs implementation detail. Not all of them make local variables and constants/statics look similar.

@llogiq
Contributor
llogiq commented May 20, 2016 edited

@ticki I agree that the fundamental concept is the same. I just argue that this is beside the point, because unlike let bindings which have high locality, globals can be all over the place. Consider the following snippet:

static FOO = &[BAR];
// 100 lines of code here
static BAR = ... // whatever
// another 100 lines of code using BAR
.. x == FOO

and now let's say someone changes BAR. The error will appear with the FOO definition and use, though the change was somewhere else. With written types, the error will be at the declaration at fault.

@Ericson2314
Contributor

If we ever got contravariance back,

type Foo<'a> = fn(&'a Bar);
static BAZ: Foo = ...;
// BAZ: Foo<'static>

might be a bit weird. Otherwise I like this.

@llogiq
Contributor
llogiq commented May 22, 2016

I'm not sure if we can differentiate between function argument and reference lifetimes. If we could, defaulting only the latter while leaving the former generic would be the best solution.

@glaebhoerl
Contributor

The same issue already exists for lifetime elision so there's plenty of precedent.

@huonw
Member
huonw commented May 22, 2016 edited

Another thing that could allow non-'static lifetimes is associated constants, e.g.:

trait Foo<T> { const X: T; }

impl<'a> Foo<&'a u8> for &'a i32 {
    const X: &'a u8 = &0;
}

This doesn't seem particularly problematic on the face of it (especially given associated constants are unstable), but I haven't devoted much thought to it.

@llogiq
Contributor
llogiq commented May 22, 2016 edited

@Ericson2314 @glaebhoerl @huonw I think that's the exact spot where we can strengthen the RFC: By defining the interaction between the 'static default and lifetime elision. My currently favorite solution would be to elide function lifetimes and default reference lifetimes to 'static. This distinction would be carried over into generics. For example, if we have

struct SomeInnerType<'a> {
    foo: &'a [u32]
}

struct SomeType<'a, 'b, 'c: 'a> {
    x: &'a SomeInnerTyp<'c>,
    y: Fn<'b>(&'b Foo) -> &'b Bar
}

we could make a static some_instance : &SomeType = .. which would actually be of type &'static SomeType<'static, 'a, 'static> (so some_instance.x would be a &'static SomeInnerType<'static>, whereas some_instance.y would be a Fn<'a>(&'a Foo) -> &'a Bar).

Note that this would make a difference based on the internals of SomeInnerType. I believe this is acceptable, because it appears to do the right thing in all cases I've come up with so far.

Am I right or did I miss some corner case? (Edit: Fixed the wrong type)

@arielb1
Contributor
arielb1 commented May 22, 2016

@llogiq

You can't have for<'a> SomeType<'static, 'a, 'static> because SomeType is not a function or trait.

@llogiq
Contributor
llogiq commented May 22, 2016 edited

@arielb1: Sorry, my fault. Fixed it. Still, it'd be generic over some lifetime 'a. But maybe that's too complicated and I'm overthinking this.

@nrc nrc added the T-lang label May 23, 2016
@nikomatsakis
Contributor

@Ericson2314 in your example, contravariance is really neither here nor there. There is no other type that one could use for the type of BAZ but Foo<'static>.

@nikomatsakis
Contributor

@huonw associated constants are a good point; do we think that these defaulting rules should apply there too? It's odd for them not to, and yet also odd for them to apply.

Finally, my logic around 'static being the only reasonable choice doesn't hold up when you consider my vague plan to support lifetime-parameterized modules.

/me second guessing

@llogiq
Contributor
llogiq commented May 23, 2016

Currently, I'm on the edge between defaulting to 'static everywhere and setting out some rule where to apply lifetime elision (let's say only for functions with input lifetimes, if they have exactly one in and out, they be generic and the same, otherwise there be multiple distinct input lifetimes, and output lifetime may not odefault to static. For all other lifetimes that do not take part in a function signature, apply 'static default).

The former has the benefit of being ridiculously easy and handling probably at least 50% of cases (number pulled out of some place I don't want to mention here), while the latter should handle the vast majority of cases and make it an error not to specify lifetimes in corner cases (which I consider a benefit).

@glaebhoerl
Contributor

when you consider my vague plan to support lifetime-parameterized modules

(first time I've heard of this!)

It could minimally be forwards-compatible by just disabling the default inside of lifetime-parameterized modules, which don't currently exist.

@TimNN
TimNN commented May 24, 2016 edited

@notriddle: If I understand the RFC correctly, it explicitly talks about your example (emphasis mine):

"The 'static default does not override lifetime elision in function signatures"

@notriddle

I meant to reply to @llogiq. Sorry about the confusion.

@llogiq
Contributor
llogiq commented May 24, 2016

@notriddle The confusion is obviously mine. Thanks for helping to clear it up.

@llogiq
Contributor
llogiq commented May 24, 2016

Now I extended the Design section to hopefully clear up the confusion by describing interaction with a few other features.

@Ericson2314
Contributor

@nikomatsakis true. I guess my concern is we'd be providing sugar for something the user probably doesn't intend in that situation, but maybe it's better to be simple and consistent. We could instead just add a lint for inferred 'static in would-be contravariant position.

@nikomatsakis nikomatsakis self-assigned this Jun 2, 2016
@donaldpipowitch

That sounds nice. As a beginner I just stumbled over this a little bit.

@nikomatsakis
Contributor

Hear ye, hear ye! This RFC is now entering final comment period. The @rust-lang/lang team is currently inclined to accept the RFC.

The conversation focused on a few points of clarification (e.g., what happens to anonymous bound regions in a fn type signature). It was also pointed out that while 'static is a good default for statics and constants, it is not such a good choice for associated constants, which still require a fully explicit type (seems fine). It may also be a problem in the future if we add lifetime-parameterized modules, but in such a case we can simply disable the default in that context (or address in some other way).

@nrc
Contributor
nrc commented Aug 18, 2016

+1

@nikomatsakis
Contributor

Huzzah! The @rust-lang/lang team has decided to accept this RFC.

@nikomatsakis nikomatsakis merged commit 2526483 into rust-lang:master Aug 22, 2016
@llogiq llogiq deleted the llogiq:static_statics branch Aug 22, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment