-
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
Anonymous/placeholder lifetime "'_". #1177
Conversation
I think we could make |
|
||
# Detailed design | ||
|
||
In `resolve_lifetime`: if the lifetime to be resolved matches "'_", store |
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.
"'_"
should be in backticks so GitHub doesn’t render the text after it italicised.
Removing existing I've wanted this feature, tried to use it only to find it didn't exist. |
I'm in favor of the general idea of this RFC, but I think I'd prefer a slightly more expanded version to make things more uniform. Basically So what this means is that: In a fn signature:
In fn body:
In a type definition or other context:
Besides being more uniform, this also offers a nice compromise with respect to lifetime elision. I sometimes find if I have a signature like this: struct Iter<'collection> { ... }
fn iter(&self) -> Iter { ... } it is very pretty but not as explicit as I might like, as there is no signal (outside of the type definition) that the borrow will be extended when the fn returns to cover the return value. Still, naming the lifetime seems like overkill: fn iter<'a>(&'a self) -> Iter<'a> { ... } I'd be happy with a version that uses fn iter(&self) -> Iter<'_> { ... } This tells me that the borrow will be extended, but avoids the need to give a name. |
Independent from this RFC, we should fix the bug about |
Marking this as T-lang: cc @rust-lang/lang |
Speaking of extensions,
|
@nikomatsakis the problem with making
expand to
I.e., it is not clear from the syntax if multiple uses represent one fresh variable or many fresh variables. I guess we have this problem with elision already, but some how representing the elided lifetimes with |
@nrc AFAICT, elision is not necessary for any of the planned uses. |
@eddyb well, it seems like the hypothetical confusion described by @nrc would arise under the planned uses... i'm thinking in particular of a case like: struct Context<'a, 'left: 'a, 'right: 'a, 'd> {
left: &'a Inner<'left>,
right: &'a Inner<'right>,
data: &'d str,
}
fn neither<'data>(cx: Context<'_, '_, '_, 'data>) -> &'data str {
cx.data
} where it sounds like @nrc is worried that people will interpret the above as injecting a single fresh lifetime and then constraining the first three lifetime parameters to (Having said that, I do not think we should worry too much about such misunderstandings.) |
@nrc I see your point about potential confusion, but I think that having |
Did a quick grep across crates.io (thanks, @brson 😻), found these: sxd-xpath-0.1.1/src/function.rs: fn pop_value_or_context_node<'_>(&mut self, context: &EvaluationContext<'_, 'd>) -> Value<'d> {
sxd-xpath-0.1.1/src/function.rs: fn pop_nodeset_or_context_node<'_>(&mut self, context: &EvaluationContext<'_, 'd>)
term_grid-0.1.1/src/lib.rs:impl<'_> convert::From<&'_ str> for Cell {
term_grid-0.1.1/src/lib.rs: fn from(string: &'_ str) -> Self { I honestly didn't expect anyone to use Now, the way this RFC is phrased right now (and the initial implementation) would let those cases compile, with the same semantics as before, regardless of the feature gate. |
The reason why at least some people use it is actually quite interesting - lifetimes |
Seems like conversation has stalled here. My current feeling is unchanged: I am in my favor if we make There is one interesting corner case: I think that That last part would mean that |
So we just discussed this RFC in a recent lang team meeting. The consensus was that we are not ready to move it to FCP. The current text (which assigns fresh lifetimes everywhere) isn't optimal. There was some discussion about what semantics to use. The primary contenders are:
The only real point where these proposals differ is on the semantics of
I still favor the third proposal, because, well, because it does what I expect in all cases. It means that There was agreement that using |
Note: it's worth pointing out that if we did adopt this RFC --- particularly with a lint --- we would probably want to wait until support for |
Further thoughts: I think we should move the question of a lint to a separate RFC, since it's a major stylistic adjustment, particularly my (now) preferred version, which only allows eliding lifetimes from |
(Note though that if we adopted this version of the lint, then the third alternative ( |
Talking with @wycats we were thinking that
It does seem like finding something less RSI-inducing than |
I really like the idea of a mechanism for expressing "there is a ref here" without the need to juggle the exact lifetimes. The lack of such a mechanism was part of the reason that we couldn't "go all the way" with lifetime elision (on structs), which was unfortunate in my opinion. I worry that |
This probably requires more thought, but... any reason not to just use |
@aidancully Wouldn't that conflict with the same syntax used for types? |
On Sat, Jul 18, 2015 at 09:22:26AM -0700, Felix S Klock II wrote:
I do not regard that as the same as an elided lifetime, no, and I would want |
@nikomatsakis maybe this gets at the heart of my problem; if the absent lifetime in |
I agree this is a key question. The way I see it, in the language today, you can break down lifetimes into two categories: "explicit" lifetimes and "defaulted" lifetimes (perhaps we need better names):
The behavior when these lifetimes are elided is already different, and I think the intuition for the difference is that, with explicit lifetimes, the normal thing is for there to be borrowed data present, whereas for defaulted lifetimes, we want to generally say that there is no borrowed data (i.e., a So, for example, in type signatures:
In fn arguments, the behavior is similar. For explicit lifetimes, we default to a fresh lifetime, but for defaulted lifetimes, we prefer All of this seems consistent to me with us saying that defaulted lifetimes are not expected to have region data -- we expect objects not to close over region data. If you want otherwise, you have to ask for it, either by having a Now this is where Now, as for the lint I've been tossing about: my concern is that, in practice, people may forget (or never have known) that structs were defined with lifetime parameters, and so they may not realize where region data is hiding. For example, I think that when you read a type like As a concrete example, I sometimes write functions in the compiler like: fn foo<'tcx>(tcx: &ty::ctxt<'tcx>, t: Ty) { ... } which looks fine, unless you remember that UPDATE: Lightly edited for clarity. |
This was important for me to understand your POV. I think making this intuition part of a user's mental model is important, so anything we can do to pick names that encourage such an intuition will make things better. So, yes, we definitely need better names than "elided" and "defaulted". :) |
having said that, now I that I better understand the reasoning behind your POV, I retract my (here unstated, I think) objection to the suggested semantics for |
(Note, current status is: the lang team has basic consensus around this RFC, but is waiting for the RFC itself to be updated accordingly.) |
So @eddyb and I touched base on this, and we also discussed in @rust-lang/lang meeting. I am thinking of closing this RFC for the time being. For me, the TL;DR is that this RFC doesn't go far enough -- it can get us a small improvement over the status quo for complex lifetime scenarios (such as the compiler), but I want an "order of magnitude" improvement. And I think we can get one. Moreover, the secondary goal of the RFC -- or at least an effect of the RFC, if not an original goal -- was to help make elision more explicit, and in particular to help make the case where a lifetime is "hidden" in the return type more explicit, without requiring an explicit name. The RFC achieves that too, but it does it with a syntax that nobody is thrilled about, and may not go far enough in this direction. (e.g., @wycats has had some thoughts about having some notation that indicates "elided lifetimes here", but doesn't require you to indicate how many) For the time being, the hope is that we can keep progress in the compiler by moving away from free functions and towards methods, which tend to reduce "lifetime clutter" by moving the declarations up to the impl. @eddyb has been experimenting here. Now, in terms of getting a 10x win, I don't want to claim a complete plan. But I think we should explore some more advanced ideas. One thing that I've been kicking around -- and hope to produce a blog post or something about -- is adding lifetime-parameterized modules (essentially, a limited and specialized form of ML functors). The rough idea is that one would be able to add lifetime parameters to modules: mod with_ctxt<'tcx> { ... /* most of the compiler goes here */ ... } The intution for this is "all code in this module runs in the context of some lifetime When you refer to types in the module Obviously this is not even a pre-RFC. It's just a sketch of an idea that I think is worth pursuing. I feel though that there is a "min-max" proposal waiting to emerge here that might not be that hard to implement and could mean a massive ergonomic improvement. (Of course, any such efforts this would be very much an active experiment, with the compiler as the laboratory. It's clear though that there are "scalability" issues that arise with trying to use arenas at scale, and the compiler is a big consumer, so it's a good place to do such experiments.) Another possibility for improvements is leveraging associated lifetimes, which have never been implemented (and indeed, probably a new RFC would be needed for some parts of them, since I think the original associated items RFC had some gaps. For example, it did not discuss projection syntax like The rough idea here would be that one could define a trait that "bundles up" a number of lifetimes and their relationships: trait Tcx {
lifetime 'tcx;
}
struct TcxArenas<'tcx> {
ty_arena: &'tcx TypedArena<Ty<'tcx>>
...
}
impl<'tcx> Tcx for TcxArenas<'tcx> {
lifetime 'tcx = 'tcx;
}
struct Ty<T: Tcx> { ... }
struct Substs<T: Tcx> { ... } thus one would write |
That said, while writing that, I started to wonder if it wouldn't be worth adding |
@nikomatsakis Using methods instead of free functions is not a panacea, but it goes a long way, enough to make this RFC quite underwhelming. |
Still makes sense to issue a future compatibility warning for lifetimes named |
As an example of what can be done right now with impls: struct TyCtxt<'a, 'gcx: 'a+'tcx, 'tcx: 'a>(&'a &'tcx &'gcx ());
#[allow(bad_style)]
struct module;
impl<'a, 'gcx, 'tcx> module {
fn foo(_: TyCtxt<'a, 'gcx, 'tcx>) {}
}
module::foo(TyCtxt(&&&())) Someone could turn this into a macro if they wanted to, and I haven't really abused it yet, but this flexibility of |
@nikomatsakis re lifetime-parametric modules - I would love to have this, I've wanted it for ages. See, although there is not much there in the way of details #424. We should discuss... |
@petrochenkov we really should deprecate |
Nominated for discussion at the lang meeting - I want to talk about salvaging some bits of this RFC |
Add AST validation pass and move some checks to it The purpose of this pass is to catch constructions that fit into AST data structures, but not permitted by the language. As an example, `impl`s don't have visibilities, but for convenience and uniformity with other items they are represented with a structure `Item` which has `Visibility` field. This pass is intended to run after expansion of macros and syntax extensions (and before lowering to HIR), so it can catch erroneous constructions that were generated by them. This pass allows to remove ad hoc semantic checks from the parser, which can be overruled by syntax extensions and occasionally macros. The checks can be put here if they are simple, local, don't require results of any complex analysis like name resolution or type checking and maybe don't logically fall into other passes. I expect most of errors generated by this pass to be non-fatal and allowing the compilation to proceed. I intend to move some more checks to this pass later and maybe extend it with new checks, like, for example, identifier validity. Given that syntax extensions are going to be stabilized in the measurable future, it's important that they would not be able to subvert usual language rules. In this patch I've added two new checks - a check for labels named `'static` and a check for lifetimes and labels named `'_`. The first one gives a hard error, the second one - a future compatibility warning. Fixes #33059 ([breaking-change]) cc rust-lang/rfcs#1177 r? @nrc
Add AST validation pass and move some checks to it The purpose of this pass is to catch constructions that fit into AST data structures, but not permitted by the language. As an example, `impl`s don't have visibilities, but for convenience and uniformity with other items they are represented with a structure `Item` which has `Visibility` field. This pass is intended to run after expansion of macros and syntax extensions (and before lowering to HIR), so it can catch erroneous constructions that were generated by them. This pass allows to remove ad hoc semantic checks from the parser, which can be overruled by syntax extensions and occasionally macros. The checks can be put here if they are simple, local, don't require results of any complex analysis like name resolution or type checking and maybe don't logically fall into other passes. I expect most of errors generated by this pass to be non-fatal and allowing the compilation to proceed. I intend to move some more checks to this pass later and maybe extend it with new checks, like, for example, identifier validity. Given that syntax extensions are going to be stabilized in the measurable future, it's important that they would not be able to subvert usual language rules. In this patch I've added two new checks - a check for labels named `'static` and a check for lifetimes and labels named `'_`. The first one gives a hard error, the second one - a future compatibility warning. Fixes #33059 ([breaking-change]) cc rust-lang/rfcs#1177 r? @nrc
Initial implementation available at rust-lang/rust#26598.