8

This question is about how to read the Rust documentation and improve my understanding of Rust so as to understand how to address this specific compiler error.

I've read the tokio docs and experimented with many of the examples. In writing my own code, I frequently run into compiler errors that I don't understand and often found I can fix the code, but don't understand why specific syntax is needed.

I reproduced with a very simple example based on tokio's hello world:

use futures::Future;
use tokio::net::TcpStream;
use tokio::prelude::*;

fn main() {
  let addr = "127.0.0.1:6142".parse().unwrap();

  let client = TcpStream::connect(&addr).and_then(|stream| {
      println!("created stream");
      // Process stream here.

      // Ok(())
  });

}

The above code is incorrect, requiring the commented out Ok(). I know that this is true, but not exactly why. This is perhaps the other half of a prior question How do I interpret the signature of read_until and what is AsyncRead + BufRead in Tokio? -- now I understand closures better, but can't quite parse the docs to understand the expected return value.

When I attempt to compile the incorrect code above, I get the following error:

error[E0277]: the trait bound `(): futures::future::Future` is not satisfied
 --> tokio-chat-client/src/main.rs:8:42
  |
8 |   let client = TcpStream::connect(&addr).and_then(|stream| {
  |                                          ^^^^^^^^ the trait `futures::future::Future` is not implemented for `()`
  |
  = note: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`

There are two parts to my question:

  1. What is the error message trying to tell me?
  2. How would I use the docs for and_then to understand the expected return value?
Ultrasaurus
  • 3,031
  • 2
  • 33
  • 52
  • 1
    I had a similar issue with `for_each()` on a `Stream` object. After reading the responses, I was able to fix it by returning `futures_util::future::ready(())` at the end of the passed in closure. – MKroehnert May 30 '21 at 08:04

2 Answers2

7

The docs for and_then state that:

fn and_then<F, B>(self, f: F) -> AndThen<Self, B, F> where
    F: FnOnce(Self::Item) -> B,
    B: IntoFuture<Error = Self::Error>,
    Self: Sized, 

This means that:

  • Your closure must accept an argument of type Self::Item and return some type B
  • The type B returned by your closure must be convertible into a future.
  • And if that future returns an error, then that error must have type Self::Error.

Moreover, if you look at the doc for IntoFuture, you will see that it is implemented for Result, so it works for Ok(()), but that it is not implemented for (), so it doesn't work if your closure returns nothing.

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • Thank you for this good explanation! The key missing piece for me was that IntoFuture is implemented for Result. Another part that I forgot (and can't find anywhere in docs) is that returning "nothing" in Rust, returns `()` – Ultrasaurus Aug 30 '19 at 14:18
2

Basically the closure you pass to and_then has the wrong type. It expects:

F: FnOnce(Self::Item) -> B

But you give it a closure of unit type, i.e. returns no value. Hence the error.

That said, the rustc error message isn't optimal here. It would be much better if it reads:

let client = TcpStream::connect(&addr).and_then(|stream| {
    println!("created stream");
    // error: mismatched types: expected `IntoFuture` but found `()`
});

The rust-lang project has this ticket to track the progress on said diagnostics issue.

edwardw
  • 12,652
  • 3
  • 40
  • 51
  • thanks for link to the ticket -- I added a suggestion https://github.com/rust-lang/rust/issues/54771#issuecomment-526631724 – Ultrasaurus Aug 30 '19 at 14:52