rustrust-async-std

Rust compiler does not unify types that both `impl Future<Output=()>` in branches of `if let` expression


While trying to create a Future conditionally, for later use with select_biased!:

            let mut heartbeat_timer = if let ServerState::Leader(_, _, _) = server_state {
                // if we're the Leader, we want to send out AppendEntries RPC at a
                // minimum interval as required to maintain our leadership
                task::sleep(HEARTBEAT_DURATION)
            } else {
                // if we're not the leader, no need to send those
                pending::<()>()
            };

// ...

            select_biased! {
                h = heartbeat_timer => self.heartbeat().await,
                // ... some others declared in priority order
                default => task::yield_now().await,
            }

I am having difficulty understanding why I get a compilation error:

error[E0308]: `if` and `else` have incompatible types
   --> src/raft.rs:185:17
    |
182 |               let heartbeat_timer = if let ServerState::Leader(_, _, _) = server_state {
    |  ___________________________________-
183 | |                 task::sleep(HEARTBEAT_DURATION)
    | |                 ------------------------------- expected because of this
184 | |             } else {
185 | |                 pending::<()>()
    | |                 ^^^^^^^^^^^^^^^ expected future, found `Pending<()>`
186 | |             };
    | |_____________- `if` and `else` have incompatible types
    |
    = note: expected opaque type `impl futures::Future<Output = ()>`
                    found struct `futures::future::Pending<()>`

Clicking (in my IDE) through pending shows me that its type is Pending<T>, and I can further see that Pending<T> implements Future<Output=T>:

impl<T> Future for Pending<T> {
    type Output = T;

    fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<T> {
        Poll::Pending
    }
}

For some reason I can't yet understand, the compiler fails to follow the same breadcrumbs. What secret does the compiler know that am I missing? Is there some straightforward transformation of my code that would satisfy the compiler? What should I be thinking that would have led me to supply what the compiler needs?

rustup --version includes the output:

info: The currently active `rustc` version is `rustc 1.69.0 (84c898d65 2023-04-16)`

Solution

  • impl return types don't work that way, unfortunately. They aren't dynamic dispatch; an impl return type must still be a single concrete type; it's just that you don't have to specify the type in your code. If you have two (or more) branch arms that return different types, then the impl return will reject it. If you want to do that, you have to use full dynamic dispatch. That means a dyn trait object and a Box or other indirection mechanism.

    In your case, I might recommend replacing impl Future<Output=()> with Box<dyn Future<Output=()>>. On each of the branches, you'll have to do an explicit Box::new around the desired return value to add the layer of indirection.