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
{{ message }}
Repository navigation
impl Trait + 'static is not 'static if returned from a generic function #76882
I expected that rustc will compile this code. But it doesn't. Instead, I've got this error:
error[E0310]: the parameter type `T` may not live long enough
--> src/lib.rs:11:5
|
10 | fn test<T>(_: T) {
| - help: consider adding an explicit lifetime bound...: `T: 'static`
11 | assert_static(get_trait::<T>());
| ^^^^^^^^^^^^^ ...so that the type `impl Trait` will meet its required lifetime bounds
The error seems weird because the return type of get_trait is explicitly marked as 'static and T/I is not used anywhere at all.
This may be related to #49431 though I'm not entirely sure.
Meta
Checked on 1.46.0 stable and 1.48.0-nightly (2020-09-17 f3c923a)
The core reason for this behaviour is the variance behaviour of impl Trait in return position: the returned impl Trait is always variant over all generic input parameters, even if not technically used. This is done so that if you change the internal implementation of a public API returning impl Trait you don't have to worry about introducing an additional variance param to the API. This could break downstream code and is thus not desireable for Rust's semver system.
That makes some sense, but in my opinion, this should be only 'behaviour by default'. If the lifetime is explicitly set (+ 'a/+ 'static) then the programmer that has set it is responsible to set it right and do not break API in the future.
Also, note that with -> impl Trait + 'static you can't return non static generic params, so there is no way to use them without changing signature (and so the limitation discussed doesn't really help with semver):
fnerase<I:Trait>(x:I) -> implTrait + 'static{
x
}
error[E0310]: the parameter type `I` may not live long enough
--> src/lib.rs:4:29
|
4 | fn erase<I: Trait>(x: I) -> impl Trait + 'static {
| -- ^^^^^^^^^^^^^^^^^^^^ ...so that the type `I` will meet its required lifetime bounds
| |
| help: consider adding an explicit lifetime bound...: `I: 'static +`
Note that this behaviour is also inconsistent with lifetimes which are not captured by default:
traitTrait{}implTraitfor&(){}fnget_trait<I>(a:&()) -> implTrait{
a
}
error[E0759]: `a` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
--> src/lib.rs:5:5
|
4 | fn get_trait<I>(a: &()) -> impl Trait {
| --- this data with an anonymous lifetime `'_`...
5 | a
| ^ ...is captured here...
|
note: ...and is required to live as long as `'static` here
--> src/lib.rs:4:28
|
4 | fn get_trait<I>(a: &()) -> impl Trait {
| ^^^^^^^^^^
help: to declare that the `impl Trait` captures data from argument `a`, you can add an explicit `'_` lifetime bound
|
4 | fn get_trait<I>(a: &()) -> impl Trait + '_ {
| ^^^^
It seems that the current behaviour of capturing lifetimes from generic arguments as lifetimes in the impl Trait has been only partially implemented, and produces some strange consequences in practice. (I noticed the issue while answering this StackOverflow question, which was wondering why the return value of this sort of function couldn't be returned as an impl Trait + 'static but could be coerced to a dyn Trait + 'static inside a Box.)
Here's an example of some very strange behaviour caused by this sort of capture:
#![feature(type_alias_impl_trait)]#![feature(unsize)]use core::marker::Unsize;use core::fmt::Debug;pubfnreturns_unrelated_impl<T>() -> implDebug + 'static{()}// doesn't compile: the compiler thinks the `impl Debug` should capture `'a`pubfntest_1<'a>() -> implDebug{returns_unrelated_impl::<&'a()>()}// doesn't compile: the compiler thinks `ImplUnsizeStatic` should capture `'a`pubfntest_2a<'a>() -> implDebug{typeImplUnsizeStatic = implUnsize<dynDebug> + 'static;let rv1:ImplUnsizeStatic = returns_unrelated_impl::<&'a()>();let rv2:Box<dynDebug> = Box::new(rv1);
rv2
}// does compile, because we aren't assigning an `impl` to another `impl`pubfntest_2b<'a>() -> implDebug{fnverify_impl_unsize_static<T:Unsize<dynDebug> + 'static>(x:&T){}let rv1 = returns_unrelated_impl::<&'a()>();verify_impl_unsize_static(&rv1);let rv2:Box<dynDebug> = Box::new(rv1);
rv2
}
The check that the return value of returns_unrelated_impl captures the generic argument T seems only to be made when the impl Debug it returns is coerced to another impl Trait type. test_2a shows that the return value of returns_unrelated_impl is not usable as an impl Unsize<dyn Debug> + 'static. Meanwhile, test_2b shows that the return value does in fact implement Unsize<dyn Debug> + 'static (a fact that makes it possible to box the return value as a Box<dyn Debug> + 'static and then return it as an impl Debug). It is very astonishing behaviour for these two cases to be considered to be different by the compiler.
This makes me concerned that if "an impl Trait return type captures the generic type arguments of the function it's returned from" is intended behaviour, that restriction has not actually been implemented correctly, and thus return-position impl Trait has been accidentally stabilised in a state that permits more code than it's supposed to. If the capturing of generic arguments were required for soundness, this would likely be a soundness hole: however, it doesn't obviously appear to be required for soundness, so there may be simpler fixes (e.g. to not capture the arguments at all, which is what most programmers including me seem to expect will happen).
(here Foo: 'static so other bound are not needed, but I've included both for completeness)
Here default behavior stands in the way of me creating an API, that would be possible without rpitit - you could change return type of Trait::foo to FooImpl or use associated type Trait::TraitsFoo and it will compile. But now I can't do it, because it captures mutable lifetime, even if it is clear that impl Foo is 'static, so it is not either & or &mut.
changed the title [-]impl Trait + 'static is not static if returned from generic function[/-][+]`impl Trait + 'static` is not `'static` if returned from a generic function[/+]on Sep 15, 2023
Activity
est31 commentedon Sep 19, 2020
You need to add a
:'staticrequirement to the generic T. This works:The core reason for this behaviour is the variance behaviour of impl Trait in return position: the returned impl Trait is always variant over all generic input parameters, even if not technically used. This is done so that if you change the internal implementation of a public API returning
impl Traityou don't have to worry about introducing an additional variance param to the API. This could break downstream code and is thus not desireable for Rust's semver system.WaffleLapkin commentedon Sep 19, 2020
That makes some sense, but in my opinion, this should be only 'behaviour by default'. If the lifetime is explicitly set (
+ 'a/+ 'static) then the programmer that has set it is responsible to set it right and do not break API in the future.Also, note that with
-> impl Trait + 'staticyou can't return non static generic params, so there is no way to use them without changing signature (and so the limitation discussed doesn't really help with semver):(playground)
WaffleLapkin commentedon Dec 11, 2020
Note that this behaviour is also inconsistent with lifetimes which are not captured by default:
(playground)
ais523 commentedon Mar 27, 2023
It seems that the current behaviour of capturing lifetimes from generic arguments as lifetimes in the
impl Traithas been only partially implemented, and produces some strange consequences in practice. (I noticed the issue while answering this StackOverflow question, which was wondering why the return value of this sort of function couldn't be returned as animpl Trait + 'staticbut could be coerced to adyn Trait + 'staticinside aBox.)Here's an example of some very strange behaviour caused by this sort of capture:
(playground)
The check that the return value of
returns_unrelated_implcaptures the generic argumentTseems only to be made when theimpl Debugit returns is coerced to anotherimpl Traittype.test_2ashows that the return value ofreturns_unrelated_implis not usable as animpl Unsize<dyn Debug> + 'static. Meanwhile,test_2bshows that the return value does in fact implementUnsize<dyn Debug> + 'static(a fact that makes it possible to box the return value as aBox<dyn Debug> + 'staticand then return it as animpl Debug). It is very astonishing behaviour for these two cases to be considered to be different by the compiler.This makes me concerned that if "an
impl Traitreturn type captures the generic type arguments of the function it's returned from" is intended behaviour, that restriction has not actually been implemented correctly, and thus return-positionimpl Traithas been accidentally stabilised in a state that permits more code than it's supposed to. If the capturing of generic arguments were required for soundness, this would likely be a soundness hole: however, it doesn't obviously appear to be required for soundness, so there may be simpler fixes (e.g. to not capture the arguments at all, which is what most programmers including me seem to expect will happen).Ddystopia commentedon Sep 11, 2023
Another example, when that behavior is inappropriate
(playground)
(here
Foo: 'staticso other bound are not needed, but I've included both for completeness)Here default behavior stands in the way of me creating an API, that would be possible without rpitit - you could change return type of
Trait::footoFooImplor use associated typeTrait::TraitsFooand it will compile. But now I can't do it, because it captures mutable lifetime, even if it is clear thatimpl Foois'static, so it is not either&or&mut.[-]impl Trait + 'static is not static if returned from generic function[/-][+]`impl Trait + 'static` is not `'static` if returned from a generic function[/+]compiler-errors commentedon Dec 31, 2024
Fixed by #95474.
WaffleLapkin commentedon Dec 31, 2024
nice