Impl Trait in return position in struct versus trait lifetime capturing

Hi everyone,

I recently tried to use impl Trait in the return position inside traits (stable branch) and encountered a behavior I'm curious about whether it's like this by design or if it is a bug. Here is an example I made to demonstrate it:

use std::fmt::Display;

trait MyTrait {
    fn trait_fn(&self) -> impl Display;
}

struct MyStruct;

impl MyStruct {
    fn struct_fn(&self) -> impl Display {
        "struct message".to_owned()
    }
}

impl MyTrait for MyStruct {
    fn trait_fn(&self) -> impl Display {
        "trait message".to_owned()
    }
}

fn factory() -> impl Display {
    let ms = MyStruct;
    ms.struct_fn()
}

fn main() {
    let message = factory();

    println!("Message: {message}")
}

The above example works just fine, but if I change the factory function to the following (calling trait_fn):

fn factory() -> impl Display {
    let ms = MyStruct;
    ms.trait_fn()
}

It does not compile anymore, saying that the lifetime of ms is not static. My current thoughts are that because of the trait, it captures all the function context as it can't figure out what's inside the function. But then I have another question—is there a way to decouple the result from params? I played with manual lifetimes, but nothing worked for me so far.

1 Like

It is by design, but not due to compiler limitations. The difference is more an artifact of langauge development history. In edition 2024, the non-trait method will act like the trait method. async fn also acts like trait methods today.

Here's an RFC where you can read more about RPIT capturing. And here's an in-progress RFC that will allow inline tuning of the capturing. My guess is that will be the first "complete" workaround to hit stable.

Another workaround is to define and associated type or GAT that captures less. The "define it to be impl Display" part of the playground is still unstable too, but if you're ok giving up the flexibility,[1] you can use concrete types on stable in this case (since the type is nameable).

The non-trait analogue to using an associated type or GAT will be TAIT. (Playground.)

(These unstable TAIT/GAT workarounds were intended to be the workaround, but probably aren't going to land soon enough and/or be ergonomic enough for the changes to RPIT capturing; hence RFC 3617.)


  1. changing an associated type is a breaking change ↩︎

2 Likes

@quinedot Thanks a lot for the answer! I was hoping it's a work in progress that lends to the stable soon. 2024 doesn't sound too bad.
I am looking forward to it and I'd love to test it in the nightly once it is available.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.