Skip to content

impl-trait return type is bounded by all input type parameters, even when unnecessary #42940

@Arnavion

Description

@Arnavion
rustc 1.20.0-nightly (f590a44ce 2017-06-27)
binary: rustc
commit-hash: f590a44ce61888c78b9044817d8b798db5cd2ffd
commit-date: 2017-06-27
host: x86_64-pc-windows-msvc
release: 1.20.0-nightly
LLVM version: 4.0
trait Future {}

impl<F> Future for Box<F> where F: Future + ?Sized {}

struct SomeFuture<'a>(&'a Client);
impl<'a> Future for SomeFuture<'a> {}

struct Client;
impl Client {
    fn post<'a, B>(&'a self, _body: &B) -> impl Future + 'a /* (1) */ {
        SomeFuture(self)
    }
}

fn login<'a>(client: &'a Client, username: &str) -> impl Future + 'a {
    client.post(&[username])
}

fn main() {
    let client = Client;
    let _f = {
        let username = "foo".to_string();
        login(&client, &username)
    };
}

Since SomeFuture borrows 'a Client, I'd expect impl Future + 'a to be the correct return type, but it gives this error:

error[E0700]: hidden type for `impl Future + 'a` captures lifetime that does not appear in bounds
  --> src/main.rs:16:5
   |
15 | fn login<'a>(client: &'a Client, username: &str) -> impl Future + 'a {
   |                                            ----     ---------------- opaque type defined here
   |                                            |
   |                                            hidden type `impl Future + 'a` captures the anonymous lifetime defined here
16 |     client.post(&[username])
   |     ^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: add a `use<...>` bound to explicitly capture `'_`
   |
15 | fn login<'a>(client: &'a Client, username: &str) -> impl Future + 'a + use<'a, '_> {
   |                                                                      +++++++++++++

  • Changing _body to have an explicit lifetime like _body: &'b B where 'b is independent of 'a or where 'a: 'b does not change the error. This and the original error make it seem that returning an impl trait is somehow causing the _body parameter to get the 'a lifetime, even though it's clearly unused, let alone used in a way that it would require the 'a lifetime.

  • Changing (1) from impl Future + 'a to SomeFuture<'a> fixes it.

  • Changing (1) from impl Future + 'a to Box<Future + 'a> and returning Box::new(SomeFuture(self)) fixes it.

Activity

added
A-impl-traitArea: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch.
on Jun 28, 2017
Arnavion

Arnavion commented on Oct 10, 2017

@Arnavion
Author

From some experimentation, it seems to be because fn foo<T>(_: T) -> impl Bar compiles to something like fn foo<T>(_: T) -> impl Bar + 't, where 't is the lifetime of T. (I don't think this relationship can be expressed in regular Rust code, though Ralith on IRC suggested one could consider the anonymous type to contain PhantomData<T>)

Thus in the original code fn post<'a, B>(&'a self, _body: &B) -> impl Future + 'a effectively forces the return type to be bounded by B's lifetime in addition to 'a. This is why changing _body: &B to _body: B does not change anything, nor does annotating the parameter as _body: &'b B for an unconstrained 'b


In light of that, here's a smaller repro:

#![feature(conservative_impl_trait)]

trait Tr { }

struct S;
impl Tr for S { }

fn foo<T>(_t: T) -> impl Tr {
    S
}

struct S2;

fn main() {
    let _bar = {
        let s2 = S2;
        foo(&s2)
    };
}

which complains that s2 is dropped while still borrowed because the compiler thinks it needs to live as long as _bar.

changed the title [-]impl trait with lifetime bound seems to apply the bound to other unbounded lifetimes[/-] [+]impl-trait return type is bounded by all input type parameters, even when unnecessary[/+] on Oct 10, 2017
cramertj

cramertj commented on Jan 5, 2018

@cramertj
Member

This is an intentional restriction. See RFC 1951 for the reasoning.

Arnavion

Arnavion commented on Jan 5, 2018

@Arnavion
Author

Sure. So can there be a way that I can convince the compiler that Client::post's and foo's results don't depend on all the inputs? Maybe it can be made to not override explicit lifetimes like it does in the Client::post example? (I recognize this won't help for the foo example since there is no way to ascribe a lifetime to T.)

The reasoning in the RFC is that eventually there will be syntax that provides control over which lifetime are and are not part of the returned existential ("Assumption 1"). But for the OP example where there already is an explicit lifetime and bound so it could be made to work today.

cramertj

cramertj commented on Jan 5, 2018

@cramertj
Member

Oh, I understand what you're saying now. Yes, if the return type contains an explicit lifetime bound, the compiler should be able to understand that the returned type outlives that lifetime bound. Currently, it cannot do that if there are type parameters involved. This should be fixed. Thanks for the report!

Arnavion

Arnavion commented on Aug 6, 2018

@Arnavion
Author

This is fixed by existential_type

The fact that existential types intentionally don't have the "generic lifetime inheriting" behavior that impl trait has is documented here.


Updated 2023-10-09 for current syntax:

59 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-impl-traitArea: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch.A-lifetimesArea: Lifetimes / regionsC-bugCategory: This is a bug.F-precise_capturing`#![feature(precise_capturing)]`T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.WG-asyncWorking group: Async & await

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Participants

      @alexreg@kornelski@SimonAdameit@Twey@nikomatsakis

      Issue actions

        impl-trait return type is bounded by all input type parameters, even when unnecessary · Issue #42940 · rust-lang/rust