-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Difference in trait object default bounds for early/late bound lifetimes #47078
Comments
I did some exploration around this issue and wanted to write up the behavior I observed. First, note that the current reference description of trait object default bounds is not correct. My testing indicates that when a trait bound applies, it overrides the bounds on struct type parameters, not the other way around (as one relevant example). The tricky part is knowing when the trait bound applies, which is this issue is about (for function signatures). Second, note that lifetime bounds on traits introduce an implied bound on the trait object lifetime, similar to how the presence of a Given those notes, let us call a lifetime parameter of a trait which is also a bound of the trait a "bounding parameter". My observations on the behavior of the default trait object lifetime in function signatures is as follows:
The "one bounding parameter" rule is particular surprising in the case of traits with multiple bounds, due to the interaction with the implied bounds: trait Double<'a, 'b>: 'a + 'b {}
fn h<'a, 'b, T>(bx: Box<dyn Double<'a, 'b>>, t: &'a T)
where
&'a T: Send, // this makes `'a` early-bound
{
// `bx` is `Box<dyn Double<'a, 'b> + 'a>` as per the rules above,
// so this does not compile:
//let _: Box<dyn Double<'a, 'b> + 'static> = bx;
// However, the implied bounds still apply, which means:
// - `'a: 'a + 'b`
// - So `'a: 'b`
//
// Which is why this can compile even though that bound
// is not declared anywhere!
let t: &'b T = t;
// The lifetimes are still not the same, so this fails
//let _: &'a T = t;
} More Examples
use core::any::Any;
fn f(d: &dyn Any) {
// This a `&(dyn Any + 'static)`...
let _: &(dyn Any + 'static) = d;
// ...but this fails, so it's not a `&'static (dyn Any + 'static)`.
// Thus the trait bound is overriding the `&_` default trait object lifetime
// let _: &'static _ = d;
}
// Here we explicitly force the "normal" elision defaults for `&_`.
fn g<'a>(d: &'a (dyn Any + 'a)) {
// This is a `&'static (dyn Any + 'static)` because there is an implied
// `'a: 'static` bound, and we have forced the inner and outer lifetimes
// to be the same in the function signatures.
let _: &'static (dyn Any + 'static) = d;
} trait Single<'s>: 's {}
// This has "normal" `&_` elision, that is, the reference lifetime and the
// trait object lifetime are the same.
fn f<'r, 's>(d: &'r dyn Single<'s>) {
// But there is still an implied `'r: 's` bound from the trait bound
let _: &'s (dyn Single<'s> + 'r) = d;
} Due to the other implied bound The requirement that exactly one of the bounded parameters is early-bound or that any of them are pub trait Double<'a, 'b>: 'a + 'b {}
// Semantically, `'a` and `'b` must be `'static`. However the
// parameters were not explicitly `'static` and thus this
// trait object lifetime is considered ambiguous (even though,
// due to the implied bounds, it must be `'static` too).
fn foo<'a: 'static, 'b: 'static>(d: Box<dyn Double<'a, 'b>>) {}
// Semantically, `'a` and `'b` must be the same. They are also
// early-bound parameters due to the bounds. However the parameters
// are not syntatically the same lifetime and thus the trait
// object lifetime is considered ambiguous.
fn bar<'a: 'b, 'b: 'a>(d: &dyn Double<'a, 'b>) {}
/*
But if you change either example to `Double<'a, 'a>`, then
exactly one of the bounded parameters is early-bound, and they
will compile:
*/
fn foo2<'a: 'static, 'b: 'static>(d: Box<dyn Double<'a, 'a>>) {}
fn bar2<'a: 'b, 'b: 'a>(d: &dyn Double<'a, 'a>) {} The wildcard lifetime trait Double<'a, 'b>: 'a + 'b {}
// Here in the signature, `'_` acts like "normal" and creates an
// independent lifetime for the trait object lifetime; let us call
// it `'c`. Though independent, it is related due to the implied
// bounds: `'c: 'a + 'b`
fn foo<'a: 'a, 'b>(bx: Box<dyn Double<'a, 'b> + '_>) {
// This fails as `'a: 'b` is required
// It succeeds if `+ '_` is removed from the signature
// let _: Box<dyn Double<'a, 'b> + 'a> = bx;
// This fails as `'b: 'a` is required
// It still fails if `+ '_` is removed from the signature
// let _: Box<dyn Double<'a, 'b> + 'b> = bx;
// This fails as the `'_` lifetime may not be `'static`
// It still fails if `+ '_` is removed from the signature, as in that case
// the elided lifetime is `'a` and `'a: 'static` would be required
// let _: Box<dyn Double<'a, 'b> + 'static> = bx;
} Function bodies and other contextsThe exploration above was for function signatures in particular. The behavior is different elsewhere:
However those are probably better tracked in their own issues. |
It looks like early- and late-bound lifetimes behave differently in default object bounds:
The text was updated successfully, but these errors were encountered: