Type alias impl trait cycle

I am trying to use #![feature(type_alias_impl_trait)] and I am getting cycle detected when computing type of discovery::ConnFuture::{opaque#0}`

I do not understand why does Rust need to analyze type of where alias is used (Discovery). Isn't all information needed in the type which generated the opaque type (Connect)?

And broader question is, how do I make low-level (poll) based approach working together with high-level (async) based?

Playground: Rust Playground

#![feature(type_alias_impl_trait)]
#[tokio::main]
async fn main() {
}

mod connection {
    pub struct Connect {}

    impl Connect {
        pub async fn connect(&mut self) -> Self { todo!() }
    }
}

mod discovery {
    use std::future::Future;
    use std::pin::Pin;
    use std::task::{Context, Poll};
    use crate::connection::Connect;

    type ConnFuture = impl Future<Output = Connect>;

    pub struct Discovery {
        connecting: Option<ConnFuture>
    }

    impl Future for Discovery {
        type Output = Connect;

        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            if self.connecting.is_none() {
                self.connecting = Some(Connect{}.connect())
            }
            todo!()
        }
    }
}
error[E0391]: cycle detected when computing type of `discovery::ConnFuture::{opaque#0}`
  --> src/bin/tool/main.rs:20:23
   |
20 |     type ConnFuture = impl Future<Output = Connect>;
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: ...which requires type-checking `discovery::<impl at src/bin/tool/main.rs:26:5: 26:30>::poll`...
  --> src/bin/tool/main.rs:31:17
   |
31 |                 self.connecting = Some(Connect{}.connect())
   |                 ^^^^
   = note: ...which requires evaluating trait selection obligation `core::pin::Pin<&'a mut discovery::Discovery>: core::ops::deref::DerefMut`...
   = note: ...which again requires computing type of `discovery::ConnFuture::{opaque#0}`, completing the cycle
note: cycle used when checking item types in module `discovery`
  --> src/bin/tool/main.rs:14:1
   |
14 | mod discovery {
   | ^^^^^^^^^^^^^

Ok, I've figured out that first, type impl must be bound to some concrete type, and it is looking for this binding everywhere, that's why it is looking in the Discovery type. So, I have to somehow tell it that it is bound to return type of async fn connect(&mut self) -> Self, but how?

On mobile so sorry for no code here, but I think it's more than that.

Create a defining use by having a function that takes a &mut Connect and returns a ConnFuture. You'll find you need a lifetime parameter that infects Discovery.

But the error will remain, and it does seem rooted in assigning self.connecting (replace that with a todo!() and it compiles).

That's as far as I got (and I didn't comment before as I didn't think it'd be useful).

This is actually a bad error message, resulting from TAIT needing a sufficiently well-formed program to be able to resolve the type. The real problem is that you cannot access self.connecting given only Pin<&mut Self>. However, in order to figure that out, I had to rewrite the program so that it could avoid the bad error message. To do that, I introduced a function conn_future() dedicated to the purpose of defining what ConnFuture is — and it also seemed important to put that in the module connection, not discovery.

mod connection {
    use std::future::Future;

    pub struct Connect {}

    impl Connect {
        pub async fn connect(self) -> Self { todo!() }    // <--- also needs to not be `&mut `self`
    }
    pub(crate) type ConnFuture = impl Future<Output = Connect>;
    pub(crate) fn conn_future() -> ConnFuture {
        Connect{}.connect()
    }
}

With this change I get:

error[E0594]: cannot assign to data in dereference of `Pin<&mut Discovery>`
  --> src/main.rs:36:17
   |
36 |                 self.connecting = Some(conn_future())
   |                 ^^^^^^^^^^^^^^^ cannot assign
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<&mut Discovery>`

Now we see the problem: mutating through Pin requires special considerations. I'm not sure what the best option for this particular case of setting an Option is — the usual go-to- pin_project doesn't solve it, and Pin::get_unchecked_mut can but is unsafe. The program compiles this way:

unsafe { 
    self.get_unchecked_mut().connecting = Some(conn_future());
}

but I'd recommend looking for alternatives — I just don't know what they are.

Playground link

2 Likes

Hmm, ok. I've got to the same "pin" problem by modifying Connection, but it looks slightly hackish.
I'll work on Pin and will see what happen to type impl

mod connection {
    use std::future::Future;

    pub struct Connect {}
    pub type ConnFuture = impl Future<Output = Connect>;

    impl Connect {
        pub fn connect(&mut self) -> ConnFuture {
            async {
                todo!()
            }
        }
    }
}