2

I'm trying to understand asynchronous I/O in Rust. The following code is based on a snippet from Katharina Fey's Jan 2019 talk which works for me:

use futures::future::Future;
use std::io::BufReader;
use tokio::io::*;

fn main() {
    let reader = BufReader::new(tokio::io::stdin());
    let buffer = Vec::new();

    println!("Type something:");
    let fut = tokio::io::read_until(reader, b'\n', buffer)
        .and_then(move |(stdin, buffer)| {
            tokio::io::stdout()
                .write_all(&buffer)
                .map_err(|e| panic!(e))
        })
        .map_err(|e| panic!(e));

    tokio::run(fut);
}

Before finding that code, I attempted to figure it out from the read_until documentation.

How do I interpret the signature of read_until to use it in a code sample like the one above?

pub fn read_until<A>(a: A, byte: u8, buf: Vec<u8>) -> ReadUntil<A> 
where
    A: AsyncRead + BufRead, 

Specifically, how can I know from reading the documentation, what are the parameters passed into the and_then closure and the expected result?

Ultrasaurus
  • 3,031
  • 2
  • 33
  • 52

1 Answers1

4

Parameters to and_then

Unfortunately the standard layout of the Rust documentation makes futures quite hard to follow.

Starting from the read_until documentation you linked, I can see that it returns ReadUntil<A>. I'll click on that to go to the ReadUntil documentation.

This return value is described as:

A future which can be used to easily read the contents of a stream into a vector until the delimiter is reached.

I would expect it to implement the Future trait — and I can see that it does. I would also assume that the Item that the future resolves to is some sort of vector, but I don't know exactly what, so I keep digging:

  1. First I look under "Trait implementations" and find impl<A> Future for ReadUntil<A>
  2. I click the [+] expander

Finally I see the associated type Item = (A, Vec<u8>). This means it's a Future that's going to return a pair of values: the A, so it is presumably giving me back the original reader that I passed in, plus a vector of bytes.

When the future resolves to this tuple, I want to attach some additional processing with and_then. This is part of the Future trait, so I can scroll down further to find that function.

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,

The function and_then is documented as taking two parameters, but self is passed implicitly by the compiler when using dot syntax to chain functions, which tells us that we can write read_until(A, '\n', buffer).and_then(...). The second parameter in the documentation, f: F, becomes the first argument passed to and_then in our code.

I can see that f is a closure because the type F is shown as FnOnce(Self::Item) -> B (which if I click through links to the Rust book closure chapter.

The closure f that is passed in takes Self::Item as the parameter. I just found out that Item is (A, Vec<u8>), so I expect to write something like .and_then(|(reader, buffer)| { /* ... /* })

AsyncRead + BufRead

This is putting constraints on what type of reader can be read from. The created BufReader implements BufRead.

Helpfully, Tokio provides an implementation of AsyncRead for BufReader so we don't have to worry about it, we can just go ahead and use the BufReader.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Tom K
  • 430
  • 5
  • 22
  • So helpful! The breadcrumb trail helped me see [+] and follow the parameter reference syntax. I've suggested an edit to clarify for future readers, and also test my understanding of the closure syntax -- thank you! – Ultrasaurus Jun 02 '19 at 16:40