I recently wanted to use anyhow in a project, but I could not because my errors were not 'static. This made me realize that we do not have the ability to default lifetime parameters. It would be reasonable to change anyhows API to be:
pub struct Error<'a = 'static> {
// anyhow doesnt actually use a box, but thats not important
inner: Box<dyn std::error::Error + Send + Sync + 'a>,
}
This would make anyhow::Error an error with a 'static lifetime, but anyhow::Error<'_> with an elided lifetime according to the standard lifetime elision rules.
If someone wanted to spearhead this, I think it would be a relatively straightforward addition to the language that could be moved along at a fast pace. I'd like to see this feature implemented!
Right now we still allow Foo even if it has lifetime parameters, as meaning Foo<'_, '_, ...>.
So presumably adding defaults to existing lifetime parameters may not be backwards-compatible, but adding new lifetime parameters with defaults, should always be? (assuming the new parameter is used in places where its default used to be used, which I believe is the hypothetical anyhow::Error situation)
Implementation-wise I think we can do this, there's just some concerns around AST->HIR lowering, which still injects placeholders (i.e. one '_ per lifetime parameter) in path segments missing all lifetimes (for lifetime elision to have HirIds to attach elision results to).
Simplest thing to do would be to only inject as many '_ as there are lifetime parameters without defaults, hiding the defaulted parameters from elision (users can still write '_ themselves to get elision).
Long-term, AST->HIR lowering should probably leave lifetimes alone and lifetime elision can just use the path segment HirId and the index in that, instead.
However, that kind of refactoring should not be needed to implement this, we'd just need to be careful about the edge cases (e.g. struct Foo<'a, 'b = 'a> used as Foo, Foo<'_> or Foo<'_, '_>, in various elision contexts).
I would personally find it pretty confusing if Error and Error<'_> were different types. I think we should be really wary of adding more complexity to lifetimes as they are already one of the hardest parts for beginners.
This seems consistent with the treatment of types though:
fn main() {
type Foo<T = u8> = T;
let ok: Foo;
let error: Foo<_>;
}
This seems nice. In the best of worlds, having:
pub struct Error<trait Extra = Send + Sync + 'static> {
// anyhow doesnt actually use a box, but thats not important
inner: Box<dyn std::error::Error + Extra>,
}
seems like it could add additional flexibility atop of this, although the implementation is much less readily possible right now without Chalk.
That's true, but I don't think that behavior is very obvious for types either; it's just less confusing because people understand types better.
Also, one can't elide type parameters, whereas one can elide lifetime parameters. To me, leaving off a lifetime parameter is like saying "hey compiler figure out what goes here" and using _ for something does the same. But that mental model breaks if elide != fresh inference variable.
Maybe, but in terms of learnability, it seems better to be consistently "wrong" than to be inconsistently right and wrong about things. Aligning lifetimes more towards a more known quantity (types, as you say) seems like a good idea.
Well lifetime elision is a fairly syntactic thing; it's not unification based in the sense of "fresh inference variable, please figure these constraints out compiler" (although I think that would have made the language more ergonomic). I imagine that had we allowed _ in function signatures, it would have meant an inference variable and not "please universally quantify a parameter here" (which is sorta what it means for lifetimes).
and have that infer the lifetime of the dyn to 'static, but if you want to use the futures::future::BoxFuture alias you have to specify that explicitly
fn foo() -> BoxFuture<'static, ()>;
Unfortunately this appears to run into the same back-compat issue that BoxFuture<T> is currently treated as BoxFuture<'_, T>, so it wouldn't be possible to use this feature until futures-core 0.4.
This is already how lifetime ellision in trait objects work. dyn Error and dyn Error + '_ are different: Rust Playground
We're trying to move away from eliding lifetimes in structs without a '_ marker because its very confusing for something with no lifetime markers to have anything but a 'static lifetime. Continuing to push those idioms should alleviate your concern.