Open petrochenkov opened 7 years ago
error: cannot specify lifetime arguments explicitly if late bound lifetime parameters are present
--> /home/travis/build/servo/servo-with-rust-nightly/servo/components/style/stylesheets/stylesheet.rs:243:27
|
243 | self.iter_rules::<'a, 'b, EffectiveRules>(device, guard)
| ^^
|
note: lint level defined here
--> /home/travis/build/servo/servo-with-rust-nightly/servo/components/style/lib.rs:26:9
|
26 | #![deny(warnings)]
| ^^^^^^^^
= note: #[deny(late_bound_lifetime_arguments)] implied by #[deny(warnings)]
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #42868 <https://github.com/rust-lang/rust/issues/42868>
What does "late bound" mean in this context? What should we do instead?
@SimonSapin
What does "late bound" mean in this context?
Well, that's a good question, but not something that can be explained in an error message. See for example http://smallcultfollowing.com/babysteps/blog/2013/10/29/intermingled-parameter-lists/#early--vs-late-bound-lifetimes. I believe nomicon had something about it, but I can't find it right now.
This issue should have a description, and @nikomatsakis is probably the best person to write it, but I can try as well.
What should we do instead?
Remove 'a, 'b,
, it never did anything anyway.
The error and lint should probably have an extra note pointing to one of these late bound lifetime parameters.
EDIT: Done in https://github.com/rust-lang/rust/pull/43343
What should we do instead?
Remove
'a, 'b
, it never did anything anyway.
That’s not true in the case of https://github.com/rust-lang/rust/issues/42508, where it is necessary to make something compile at all.
One possible solution for the problem of intermixed late- and early-bound parameters is to support explicit for<...>
clauses on function items in the same way it's done on function pointers:
for<'late> fn f<'early1, 'early2>(a: &'late u8, b: &'early1 u8, c: &/*'elided*/u8) -> &'early2 u8 { ... }
If for
is specified then all lifetimes defined in it are considered late-bound, and all lifetimes specified in generic parameters are considered early-bound.
For function pointers the equivalent construction is already supported:
type F<'early1, 'early2> = for<'late> fn(&'late u8, &'early1 u8, &/*'elided*/u8) -> &'early2 u8;
The function could then be called with explicitly specified lifetime arguments, despite the presence of late-bound parameters:
f::<'arg_early1, 'arg_early2>()
@SimonSapin
I believe there exists a workaround to force early-bound lifetimes at a time when they are appropriate. Are you blocked on this point? In particular, you could do something like where 'static: 'a
, which would force 'a
to be early bound (because it appears in a where-clause).
The definition of late bound lifetimes, mechanically at least, is that they are those lifetimes that both:
All other lifetimes bound on a function are early-bound. So if you have a dummy where clause like where 'static: 'a
, which has no particular meaning, it does force 'a
to be early-bound.
(The reason that the early- vs late-bound distinction exists is something else, of course.)
I do hope we can clean this up in the future -- perhaps in another epoch -- but for now that is how it works. There are some annoying interactions with inference that make this all hard to alter in a totally backwards compatible way.
I am blocked in the sense that I have warnings that I don’t know how to work around, don’t understand in the first place, and that say “it will become a hard error in a future release”.
I think you could use something like (parse_nested_block: fn(&mut Parser<'i, 't>, _))(...)
(using type ascription) instead of passing generic parameters. Ugly, but works.
Nope, that doesn't work for some reason.
@petrochenkov wrote (in the description):
It's not clear how to provide arguments for early-bound lifetime parameters if they are intermixed with late-bound parameters in the same list. For now providing any explicit arguments is prohibited if late-bound parameters are present, so in the future we can adopt any solution without hitting backward compatibility issues.
Note that late-bound lifetime parameters can be introduced implicitly through lifetime elision [...]
While each of these paragraphs made sense to me when I read them on my own, I am curious about the conclusion that it leads to.
Consider a case like this:
fn all_explicits_are_early<'a, 'b>(arg: &Foo<'a, 'b>) -> Bar<'a, 'b> where 'a: 'a, 'b: 'b { ... }
I would think that a call expression all_expilcits_are_early::<'x, 'y>(param)
would be sensible: we are providing arguments for the explicit parameters, which are all early bound.
But unfortunately, the above function definition actually desugars into something with a hidden late-bound lifetime parameter, due to lifetime elision on the arg: &Foo<'a, 'b>
. Which then causes the error here to fire in the case described above.
Would it be possible to loosen the error checking here so that if all the explicitly listed lifetime parameters are early-bound, then one still gets to specify the lifetimes explicitly at the call-site, regardless of any hidden late bound lifetimes introduced due to the elision rules?
@pnkfelix
Would it be possible to loosen the error checking here so that if all the explicitly listed lifetime parameters are early-bound, then one still gets to specify the lifetimes explicitly at the call-site, regardless of any hidden late bound lifetimes introduced due to the elision rules?
That would be possible, I think. It would commit us to supporting that interpretation, of course.
EDIT: But I'm ok with that -- it's actually roughly the model I want going forward. In particular, I want the model to be: if you write explicit lifetime names-- which would no longer be required -- then you must provide a value for them when you write explicit values, even if that value is '_
. I'm not sure quite how to get there from where we are, may require some change of behavior around epochs or something.
@pnkfelix The logic (beside just being the most conservative variant) is that elided lifetimes are purely a sugar for the explicitly written form, i.e.
// Full form
fn foo<'early: 'early, 'late>(arg: &'late u8) { ... }
// Short form
fn foo<'early: 'early>(arg: &u8) { ... }
are equivalent and can be switched freely without changing any observable behavior for foo
's users.
If the short form doesn't report an error and the full form does, then it's not a sugar and you can't switch between two forms backward-compatibly.
With my proposed solution (https://github.com/rust-lang/rust/issues/42868#issuecomment-325647940) elided lifetimes can always be written explicitly without affecting observable behavior for users, so the restriction is unnecessary and can be relaxed
// Short form
fn foo<'early: 'early>(arg: &u8) { ... } // `foo::<'static>` is okay
// Full desugared form
for<'late> fn foo<'early: 'early>(arg: &'late u8) { ... } // `foo::<'static>` is still okay
This came up again. https://crates.io/crates/cssparser still emits this warning, and I still don’t know of a way to fix it without getting borrowck errors instead.
It seems this shows up as either a warn-level lint or a hard error depending on the code.
Example that triggers a warning (on stable and nightly):
static X: u8 = 0;
fn f<'a, 'b>(_x: &'b u8) -> &'a u8 { &X }
fn main() { let _ = f::<'static>; }
Example that triggers a hard error (what led me to open #80618; again, stable or nightly), with no mention of the tracking issue:
fn f<'a>() {}
fn main() { let _ = f::<'static>; }
Is this expected?
@cole-miller It's reported as a lint only in cases where making it a hard error would cause breakage back in 2017. The intent is to produce an error, in general, the lint is only a way to give people some time to fix code.
@petrochenkov Thanks, understood. So I can go ahead and assign an error number and --explain
text?
How do I solve something like
fn lister<'a, 'b, T: LifetimedDisplay<'a>>(
buffer: &'b mut dyn Write,
element: project::element::ElementRef,
project: &'a project::project::Project,
) {
writeln!(buffer, "{}", T::new(element, project)).unwrap();
}
I get
warning: cannot specify lifetime arguments explicitly if late bound lifetime parameters are present
--> src/lib.rs:72:18
|
72 | lister::<'a, T>(buffer, element.clone(), project);
| ^^
...
78 | fn lister<'a, 'b, T: LifetimedDisplay<'a>>(
| -- the late bound lifetime parameter is introduced here
|
= note: `#[warn(late_bound_lifetime_arguments)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #42868 <https://github.com/rust-lang/rust/issues/42868>
warning: `girderstream` (lib) generated 1 warning
I have spent a few hours on this and am pretty stumpped
For anyone else finding things like this you might find this helpful
It was actually solved at the call sight rather than the function
-lister::<'a, T>(buffer, element.clone(), project);
+lister::<T>(buffer, element.clone(), project);
Normally rustc gives the most excellent tips but i think in this case we could do better..
I have to say I am quite confused by the issue description here, and I don't understand what the underlying issue is. The core of my confusion seems to be captured by this (repeated) statement
the first argument doesn't make much sense because the function is not parameterized by 'a.
I strongly disagree! The function is parameterized by 'a
. Sure, this parameter is a type system fiction (the actual runtime address of the function is the same for all choices of 'a
), but that doesn't make 'a
any less of a parameter. It can still be valuable to explicitly set that parameter to guide inference. In fact, even if I have a function pointer for<'a> fn(&'a i32) -> &'a i32
, I may want to explicitly set the lifetime -- that's a completely meaningful operation and entirely orthogonal to our monomorphization strategy. The issue description seems to presuppose that explicit instantiation is somehow tied to monomorphization, but I don't understand why that would be the case.
Taking a step back: The fact that some lifetime parameters are type system fictions while others are actually relevant for monomorphization is very confusing and surprising. (I have never fully understood why this is the case; I'll need to dig into this some time.) This will clearly incur some friction when creating function pointers since monomorphization-relevant ("early-bound") lifetime parameters need to be chosen the moment the function pointer is created. I can see that we might want to improve that situation. But reading just this issue I really don't understand the motivation for the current change, since there's a big gap between "unfortunately some lifetime parameters actually matter for monomorphization" and "other lifetime parameters (that don't matter for monomorphization) don't make sense and we should disallow people from explicitly stating them". Which problem is being solved here?
A related Stack Overflow discussion.
What is this lint about
In functions not all lifetime parameters are created equal. For example, if you have a function like
both
'a
and'b
are listed in the same parameter list, but when stripped from the surface syntax the function looks more likewhere
'b
is a "true" ("early-bound") parameter of the function, and'a
is an "existential" ("late-bound") parameter. This means the function is not parameterized by'a
. To give some more intuition, let's write a type for function pointer tof
:See more about this distinction in http://smallcultfollowing.com/babysteps/blog/2013/10/29/intermingled-parameter-lists/#early--vs-late-bound-lifetimes
When lifetime arguments are provided to a function explicitly, e.g.
the first argument doesn't make much sense because the function is not parameterized by
'a
. Providing arguments for "late-bound" lifetime parameters in general doesn't make sense, while arguments for "early-bound" lifetime parameters can be provided.It's not clear how to provide arguments for early-bound lifetime parameters if they are intermixed with late-bound parameters in the same list. For now providing any explicit arguments is prohibited if late-bound parameters are present, so in the future we can adopt any solution without hitting backward compatibility issues.
Note that late-bound lifetime parameters can be introduced implicitly through lifetime elision:
The precise rules discerning between early- and late-bound lifetimes can be found here: https://github.com/rust-lang/rust/blob/91aff5775d3b4a95e2b0c2fe50785f3d28fa3dd8/src/librustc/middle/resolve_lifetime.rs#L1541-L1700
How to fix this warning/error
Just removing the lifetime arguments pointed to by the lint should be enough in most cases.
Current status
late_bound_lifetime_arguments
lint as warn-by-defaultlate_bound_lifetime_arguments
lint deny-by-defaultlate_bound_lifetime_arguments
lint a hard error