Skip to content
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

Unnecessary lifetime error #109591

Closed
ViktorWb opened this issue Mar 25, 2023 · 4 comments
Closed

Unnecessary lifetime error #109591

ViktorWb opened this issue Mar 25, 2023 · 4 comments
Labels
A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. C-bug Category: This is a bug.

Comments

@ViktorWb
Copy link

The following code produces a compiler error:

pub trait ReturnedTrait {}

impl<T> ReturnedTrait for T {}

pub fn create_trait<'a, T>(_t: T) -> impl ReturnedTrait + 'a {}

pub fn outer<'a>(val: String) -> impl ReturnedTrait + 'a {
    create_trait(&val)
}
error[[E0597]](https://doc.rust-lang.org/stable/error_codes/E0597.html): `val` does not live long enough
 --> src/lib.rs:8:18
  |
7 | pub fn outer<'a>(val: String) -> impl ReturnedTrait + 'a {
  |              -- lifetime `'a` defined here
8 |     create_trait(&val)
  |     -------------^^^^-
  |     |            |
  |     |            borrowed value does not live long enough
  |     argument requires that `val` is borrowed for `'a`
9 | }
  | - `val` dropped here while still borrowed

playground

The reason, as I see it, is that according to RFC 1951, a returned impl captures generic arguments. However, in this case, I do have a lifetime bound 'a which is present on the returned impl but not on the generic T. However, the function still acts as if T has a lifetime bound of 'a.

Okay, so if the returned impl captures T, then I would expect to be able to use T in the return value. But the compiler will not let me:

pub trait ReturnedTrait {}

impl<T> ReturnedTrait for T {}

pub fn create_trait<'a, T>(t: T) -> impl ReturnedTrait + 'a {
    t
}

pub fn outer<'a>(val: String) -> impl ReturnedTrait + 'a {
    create_trait(val)
}
error[[E0309]](https://doc.rust-lang.org/stable/error_codes/E0309.html): the parameter type `T` may not live long enough
 --> src/lib.rs:6:5
  |
6 |     t
  |     ^ ...so that the type `T` will meet its required lifetime bounds
  |
help: consider adding an explicit lifetime bound...
  |
5 | pub fn create_trait<'a, T: 'a>(t: T) -> impl ReturnedTrait + 'a {
  |                          ++++

playground

The compiler will not let me use T in the returned impl, but still, it requires that T is borrowed for 'a.

I would expect one of two things to happen:

  • The returned impl captures T, which would allow me to use T in the return value.
  • The returned impl does not capture T, in which case it would be possible for T to not have lifetime 'a.

Instead what happens is that the inner function does not allow the impl to capture T, but the outer function acts as if the impl has captured T.

An additional note

I noticed that by wrapping the returned value in a Box<dyn _>, the code compiles:

pub trait ReturnedTrait {}

impl<T> ReturnedTrait for T {}

pub fn create_trait<'a, T>(_t: T) -> impl ReturnedTrait + 'a {}

pub fn outer<'a>(val: String) -> impl ReturnedTrait + 'a {
    let res = create_trait(&val);
    let res: Box<dyn ReturnedTrait + 'a> = Box::new(res);
    res
}
Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.76s

playground

This further leads me to believe that this lifetime error is unnecessary. Without any modification to the inner function, the compiler now understands that the returned impl in fact does not capture T.

Solution

Because of the 'a bound on the returned impl, the inner function does not allow the returned impl to capture T. The function outer should therefore be able to know that the parameter T does not have to live for 'a. Therefore, it should be possible to remove the lifetime error in the first code example, at this playground.

@ViktorWb ViktorWb added the C-bug Category: This is a bug. label Mar 25, 2023
@aliemjay aliemjay added the A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. label Mar 25, 2023
@aliemjay
Copy link
Member

aliemjay commented Mar 26, 2023

This is a known unfortunate limitation of impl Trait design. See #82171.

The solution you proposed is sadly not applicable if the hidden type contains associated type projections. Consider this example where the impl Trait captures the generic paramter T (in the sense that the hidden type <T as Trait>::Assoc is generic over T) while not requiring T: 'static bound:

trait Trait {
    type Assoc: 'static;
}

fn get<T: Trait>(val: T::Assoc) -> impl std::any::Any + 'static {
    val
}

Consider closing it in favor of #82171.

@ViktorWb
Copy link
Author

ViktorWb commented Mar 26, 2023

Okay! Thank you!

Any idea why creating a Box< dyn ReturnedTrait + 'a> makes the code compile?

@aliemjay
Copy link
Member

Any idea why creating a Box<dyn ReturnedTrait + 'a> makes the code compile?

Continuing from the previous example, using dyn Any erases all the compile-time information about the underlying hidden type, while impl Any + 'static needs to be generic over the parameter type T because its the hidden type must be known at compile-time. This is the core difference between static and dynamic dispatch. If we were to spell out the full types:

type DynTrait = dyn Any + 'static; // not generic over T.
type ImplTrait<T> = impl Any + 'static = <T as Trait>::Assoc; // generic over T.

It is the same reason that this is rejected:

use std::any::Any;

fn into_any<T: 'static>(t: T) -> impl Any {
    t
} 

fn main() {
    let _ = [into_any(0u8), into_any(0u16)];
}

While Box<dyn Any> works fine.

@ViktorWb
Copy link
Author

I see! Thank you for your time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. C-bug Category: This is a bug.
Projects
None yet
Development

No branches or pull requests

2 participants