You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
&dyn Trait default lifetime can be inferred in expressions
The reference says the default is only inferred in expressions if there are no type bounds and the default outside of expressions is 'static, but this is not true.
traitTrait{}implTraitfor(){}fnexample(){let local = ();// The outer reference lifetime cannot be `'static`...let obj:&dynTrait = &local;// Yet the `dyn Trait` lifetime is! Thus the default must be inferredlet _:&(dynTrait + 'static) = obj;}
Note, however, that if you annotate a named lifetime for the generic type (here the reference lifetime), then the default is that named lifetime. This is the behavior the reference currently says always happens.
traitTrait{}implTraitfor(){}structWeird<'a,'b,T:'a + 'b + ?Sized>(&'a T,&'b T);fnexample<'a,'b>(){// Either of `dyn Trait + 'a` or `dyn Trait + 'b` is an error,// so the `dyn Trait` lifetime must be inferred independently// from `'a` and `'b`let _:Weird<'a,'b,dynTrait> = Weird(&(),&());}
Trait bounds can override type bounds
According to the reference, trait bounds aren't considered for the default lifetime if the type has bounds. That is, according to the reference, type bounds override trait bounds. However, this is not true: trait bounds can override type bounds.
(When exactly this happens is complicated; see further below.)
pubtraitLifetimeTrait<'a>:'a {}implLifetimeTrait<'_>for(){}// n.b. `'a` is invariant due to being a trait parameterfnfp<'a>(t:&(dynLifetimeTrait<'a> + 'a)){f(t);}// The fact that `fp` compiled means that the trait bound does in fact apply,// and results in a trait object lifetime independent of the reference lifetime.//// That is, the type is not `&'r (dyn LifetimeTrait<'a> + 'r)`; // the trait bound applied despite the presence of a type boundpubfnf<'a:'a>(_:&dynLifetimeTrait<'a>){}
This example compiles, but fails with the commented change. Playground.
use core::marker::PhantomData;// Remove `: 'a` to see the compile errorpubtraitLifetimeTrait<'a>:'a {}// This type has bounds; moreover, according to the reference, an explicit// trait object lifetime is required due to having multiple bounds.pubstructOver<'a,T:'a + 'static + ?Sized>(&'a T);pubstructInvariant<T: ?Sized>(*mutPhantomData<T>);unsafeimpl<T: ?Sized>SyncforInvariant<T>{}// Here, the `'_` is `'static`. As this compiles, the trait bound must be// overriding the ambiguous type bounds and making the default// trait object lifetime `'static` as well.//// When the trait bound is removed, an explicit lifetime is indeed required.pubstaticOS:Invariant<Over<'_,dynLifetimeTrait>> = Invariant(std::ptr::null_mut());
'static trait bounds always override type bounds
As a special case of trait bounds overriding type bounds, a 'static trait bound always applies.
use std::any::Any;// n.b. `Any` has a `'static` trait boundfnexample(r:&dynAny){let _r:&(dynAny + 'static) = r;}
This is true even if there are multiple lifetime bounds and one of them is 'static (in contrast with the analogous case for type bounds, which remains ambiguous).
A single trait bound is not always the default
According to the reference, if the type has no lifetime bound and the trait has a single lifetime bound, the default trait object lifetime is the bound on the trait. This is true if the bound is 'static, as covered above. However, it is not always true for a non-'static bound.
(Again, when it is or isn't true -- when trait bounds "apply" or not -- is complicated).
traitSingle<'a>:'a {}// `Box` has no type lifetime bounds, so according to the reference the// default lifetime should be `'a`. But instead it is `'static`.fnfoo<'a>(s:Box<dynSingle<'a>>){let s:Box<dynSingle<'a> + 'static> = s;}
The default depends on lifetimes being early or late bound
According to the reference, the default lifetime only depends on the presence or absence of bounds on type and traits. However, the presence or absence of bounds on function lifetime parameters also plays a role. Namely, early-bound lifetime parameters (lifetimes involved in a where clause) act differently than late-bound lifetime parameters.
Example
This example does not compile. Playground. However, the only change from the last is making the lifetime parameter 'a early-bound.
traitSingle<'a>:'a {}// Unlike the last example, the trait bound now applies and the// default trait object lifetime is `'a`, causing a compiler error.fnfoo<'a:'a>(s:Box<dynSingle<'a>>){let s:Box<dynSingle<'a> + 'static> = s;}
This behavior has been known about for some time. Note that the linked issue was cited when the current reference material was written; I don't know why the information wasn't included in the reference at that time.
The default lifetime can override the wildcard '_ notation
According to the reference, using '_ in place of elision restores "the usual elision rules". However, this is not always true: trait bounds override both type bounds and the wildcard '_ lifetime in function bodies.
traitSingle<'a>:'a {}fnfoo<'a>(bx:Box<dynSingle<'a> + 'static>){// The trait bound applies and the default lifetime is `'a`let bx:Box<dynSingle<'a> + '_> = bx;// So this failslet _:Box<dynSingle<'a> + 'static> = bx;}
traitSingle<'a>:'a {}fnfoo<'a>(rf:&(dynSingle<'a> + 'static)){// The trait bound applies and the default lifetime is `'a`let a:&(dynSingle<'a> + '_) = rf;// So this succeedslet _:&(dynSingle<'a> + 'a) = a;// And this failslet _:&(dynSingle<'a> + 'static) = a;}
This example does not compile. Playground. The only difference from the last example is making the reference lifetime explicit. (As was noted above, this changes the default lifetime when there is no trait bound; this demonstrates that the trait bound still overrides the type bound in this scenario.)
traitSingle<'a>:'a {}fnfoo<'r,'a>(rf:&'r (dynSingle<'a> + 'static)){// The trait bound applies and the default lifetime is `'a`let a:&'r (dynSingle<'a> + '_) = rf;// So this succeedslet _:&'r (dynSingle<'a> + 'a) = a;// And this failslet _:&'r (dynSingle<'a> + 'static) = a;}
Other underdocumented behavior of note
I don't think the current reference material contradicts these observations, but it doesn't point them out either.
Trait bounds introduce implied bounds on the trait object lifetime
Similar to how &'a &'b () introduces an implied 'b: 'a bound, trait Trait<'b>: 'b introduces an implied 'b: 'a bound on dyn Trait<'b> + 'a. (The bound is always present, e.g. even if the trait object lifetime is elided.)
pubtraitLifetimeTrait<'a,'b>:'a {}pubfnf<'b>(_:Box<dynLifetimeTrait<'_,'b> + 'b>){}fnfp<'a,'b,'c>(t:Box<dynLifetimeTrait<'a,'b> + 'c>){let c:&'c [()] = &[];// This fails, demonstrating that `'c: 'b` is not implied// (i.e. the implied bound is on the trait object lifetime only, and// not on the other parameters.)let _:&'b [()] = c;// This fails as it requires `'c: 'b` and `'b: 'a`f(t);}
Type bounds of aliases take precedence
That's to say, if you have an alias without the lifetime bound, it will act like Box<T> (default is often 'static) even if the underlying type had the bound. And if you have an alias with the lifetime bound, it will act like &T (default is often the bound lifetime) even if the underlying type did not have the bound.
traitTrait{}// Without the `T: 'a` bound, the default trait object lifetime// for this alias is `'static`typeMyRef<'a,T> = &'a T;// So this compilesfnfoo(mr:MyRef<'_,dynTrait>) -> &(dynTrait + 'static){
mr
}
traitTrait{}// Without the `T: 'a` bound, the default trait object lifetime// for this alias is `'static`typeMyRef<'a,T> = &'a T;// With the `T: 'a` bound, the default trait object lifetime for// this alias is the lifetime parametertypeMyOtherRef<'a,T:'a> = MyRef<'a,T>;// So this does not compilefnbar(mr:MyOtherRef<'_,dynTrait>) -> &(dynTrait + 'static){
mr
}
If there are multiple lifetime bounds (even if one of them is 'static)
The default is inferred in expressions
The default is considered ambiguous everywhere else and must be explicitly annotated instead
When there are trait bounds, the bounds always implicitly apply to the trait object lifetime, whether that bound is elided, the wildcard '_, or explicitly annotated. In particular, if the trait has a 'static bound, the trait object lifetime is effectively always 'static. This is considered unambiguous even if there are other lifetime bounds (in contrast with type bounds).
Therefore, when a trait has a 'static bound, irregardless of anything else
The default is effectively 'static (even when technically inferred or another lifetime parameter)
From here on, we assume any trait bounds are non-'static.
When there are trait bounds, they usually apply fully. The exception is function signatures, which we'll cover separately.
When trait bounds are present and apply fully:
Type bounds do not apply
The wildcard '_ usually restores the "normal" elision rules
Exception: In expressions, '_ acts like full elision
The trait bounds still imply bounds on the anonymous lifetime
If there is 1 lifetime bound, 'a
The default lifetime is 'a
If there are multiple lifetime bounds
The default is considered ambiguous and must be explicitly annotated instead
In function signatures when trait bounds are present, trait bounds may apply fully, partially, or not at all (falling back to type bounds). I've written up the detailed behavior in #47078; in summary:
If any bounding parameter is explicitly 'static, the default lifetime is 'static
If exactly one bounding parameter is early-bound, the default lifetime is that lifetime
Including if it is in multiple positions, such as dyn Double<'a, 'a> for trait Double<'a, b>: 'a + 'b
If more than one bounding parameter is early-bound, the default lifetime is ambiguous
If no bounding parameters are early-bound, the type bounds apply instead
When trait bounds partially apply, interaction with the inferred bounds from the trait (which are always in effect) can create surprising behavior, as explored in the linked issue.
Other behavior of note:
Bounds or lack of bounds on type aliases take precedence
Bounds on associated types and GATs have no effect
&dyn Trait default lifetime can be inferred in expressions
I can confirm that “[…] the default must be inferred” is true by looking at the debug log of rustc (RUSTC_LOG=debug):
&dyn Trait is indeed treated as &(dyn Trait + '_) in that code: astconv::object_safety trait_object_type: dyn [Binder(Trait(Trait), [])] + '?1 (where '?1 is a region inference variable)
?'1 is later constrained to be 'static due to the coercion point: make_eqregion: unifying '?1 with ReStatic, '?1, opportunistically resolved to ReStatic
Reason: In resolve_object_lifetime_default, we can't extract the object region from ObjectLifetimeDefault since the anonymous lifetime of the reference wasn't resolved to a ResolvedArg, it's None (that might happen with all anon lifetimes?). Therefore the hir::Lifetime corresp. to the OLD (object lifetime default) gets resolved during astconv instead which maps those "unresolved" lifetimes to a new region var in function re_infer
I wrote a lot about this here, but I will recreate the demonstrations in this issue.
Some familiarity with how type bounds work with the default lifetime is important to understand this issue. In particular, references do have the type bound.
Factual corrections
&dyn Trait
default lifetime can be inferred in expressionsThe reference says the default is only inferred in expressions if there are no type bounds and the default outside of expressions is
'static
, but this is not true.Example
This example compiles. Playground.
I believe it is due to this change (accepted without FCP).
Note, however, that if you annotate a named lifetime for the generic type (here the reference lifetime), then the default is that named lifetime. This is the behavior the reference currently says always happens.
Example
This example does not compile. Playground.
The default lifetime for types with multiple bounds can be inferred in expressions
The reference says that for types with multiple bounds, an explicit bound must be specified. However, this is not true in expressions.
Example
This example compiles. Playground.
Trait bounds can override type bounds
According to the reference, trait bounds aren't considered for the default lifetime if the type has bounds. That is, according to the reference, type bounds override trait bounds. However, this is not true: trait bounds can override type bounds.
(When exactly this happens is complicated; see further below.)
Examples
This example compiles. Playground.
This example compiles, but fails with the commented change. Playground.
'static
trait bounds always override type boundsAs a special case of trait bounds overriding type bounds, a
'static
trait bound always applies.Example
This example compiles. Playground.
This is true even if there are multiple lifetime bounds and one of them is
'static
(in contrast with the analogous case for type bounds, which remains ambiguous).A single trait bound is not always the default
According to the reference, if the type has no lifetime bound and the trait has a single lifetime bound, the default trait object lifetime is the bound on the trait. This is true if the bound is
'static
, as covered above. However, it is not always true for a non-'static
bound.(Again, when it is or isn't true -- when trait bounds "apply" or not -- is complicated).
Example
This example compiles. Playground.
The default depends on lifetimes being early or late bound
According to the reference, the default lifetime only depends on the presence or absence of bounds on type and traits. However, the presence or absence of bounds on function lifetime parameters also plays a role. Namely, early-bound lifetime parameters (lifetimes involved in a where clause) act differently than late-bound lifetime parameters.
Example
This example does not compile. Playground. However, the only change from the last is making the lifetime parameter
'a
early-bound.This behavior has been known about for some time. Note that the linked issue was cited when the current reference material was written; I don't know why the information wasn't included in the reference at that time.
The default lifetime can override the wildcard
'_
notationAccording to the reference, using
'_
in place of elision restores "the usual elision rules". However, this is not always true: trait bounds override both type bounds and the wildcard'_
lifetime in function bodies.Examples
This example does not compile. Playground.
This example does not compile. Playground.
This example does not compile. Playground. The only difference from the last example is making the reference lifetime explicit. (As was noted above, this changes the default lifetime when there is no trait bound; this demonstrates that the trait bound still overrides the type bound in this scenario.)
Other underdocumented behavior of note
I don't think the current reference material contradicts these observations, but it doesn't point them out either.
Trait bounds introduce implied bounds on the trait object lifetime
Similar to how
&'a &'b ()
introduces an implied'b: 'a
bound,trait Trait<'b>: 'b
introduces an implied'b: 'a
bound ondyn Trait<'b> + 'a
. (The bound is always present, e.g. even if the trait object lifetime is elided.)Examples
This example compiles. Playground.
This example does not compile. Playground.
Type bounds of aliases take precedence
That's to say, if you have an alias without the lifetime bound, it will act like
Box<T>
(default is often'static
) even if the underlying type had the bound. And if you have an alias with the lifetime bound, it will act like&T
(default is often the bound lifetime) even if the underlying type did not have the bound.Examples
This example compiles. Playground.
This example does not compile. Playground.
See issue #100270.
Bounds on associated types and GATs don't change the default
This is true when the bounds are placed on the associated type or GAT itself.
Example
This example does not compile. Playground.
However, it is also true for GATs with bound type parameters.
Example
This example does not compile. Playground.
This latter concern is tracked in #115379 (which is also where the example is from).
Overview of the actual behavior
Type bounds always apply if there are no trait bounds, but also in other circumstances we'll cover below.
When type bounds apply:
'_
always restores the "normal" elision rules'static
everywhere elseBox<T>
'a
'a
is explicitly annotated in an expression, the default is'a
'a
everywhere else&T
,Ref<'a, T>
'static
)When there are trait bounds, the bounds always implicitly apply to the trait object lifetime, whether that bound is elided, the wildcard
'_
, or explicitly annotated. In particular, if the trait has a'static
bound, the trait object lifetime is effectively always'static
. This is considered unambiguous even if there are other lifetime bounds (in contrast with type bounds).Therefore, when a trait has a
'static
bound, irregardless of anything else'static
(even when technically inferred or another lifetime parameter)From here on, we assume any trait bounds are non-
'static
.When there are trait bounds, they usually apply fully. The exception is function signatures, which we'll cover separately.
When trait bounds are present and apply fully:
'_
usually restores the "normal" elision rules'_
acts like full elision'a
'a
In function signatures when trait bounds are present, trait bounds may apply fully, partially, or not at all (falling back to type bounds). I've written up the detailed behavior in #47078; in summary:
'static
, the default lifetime is'static
dyn Double<'a, 'a>
for traitDouble<'a, b>: 'a + 'b
When trait bounds partially apply, interaction with the inferred bounds from the trait (which are always in effect) can create surprising behavior, as explored in the linked issue.
Other behavior of note:
The text was updated successfully, but these errors were encountered: