How to read stdin in Tokio

How can I SIMPLY read a line from stdin and write a (different) line to a local file please? Using async Tokio. (I know it is ridiculous but I can now do it over TLS but not from stdin)

I'm assuming you're talking about stable tokio (v0.1). If so, this is how I would do it:

use std::io::BufReader;
use tokio::io;
use tokio::prelude::{Future, Stream};
use tokio::fs;

fn main() {
    // Get tokio's version of stdin, which implements AsyncRead
    let stdin = io::stdin();
    // Create a buffered wrapper, which implements BufRead
    let reader = BufReader::new(stdin);
    // Take a stream of lines from this
    let lines = io::lines(reader);

    let task = lines.take(1) // read one line
        .for_each(move |line| {
            println!("You typed: {}", line);
            // Return a future to complete before reading the next line
            // (which will never come anyway)
            fs::File::create("/tmp/out.txt")
                .and_then(move |f| {
                    let line = format!("LOG: read a line: {}\n", line);
                    io::write_all(f, line)
                })
                .map(drop) // close file when done
        })
        .map_err(drop);
    
    tokio::run(task);
}
2 Likes

Sorry, I actually meant the latest alpha version but this is helpful nonetheless, thank you. What would be different?

OK, I replaced tokio::run(task); with tokio::runtime::current_thread::Runtime::run(task);
but io::lines or anything similar no longer seems to exist and everything is getting really complicated even for such a simple task.

I'm still pretty new to new tokio, but here's an equivalent example in case it helps. (If any more experienced forum members would like to weigh in on idiomatic code, please do!)

use tokio::prelude::*;
use tokio::codec::{FramedRead, LinesCodec};
use tokio::io;
use tokio::fs::File;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let stdin = io::stdin();
    let mut reader = FramedRead::new(stdin, LinesCodec::new());
    let line = reader.next().await.transpose()?.unwrap();
    println!("You typed: {}", line);
    let mut f = File::create("/tmp/out.txt").await?;
    let line = format!("LOG: read a line: {}\n", line);
    f.write_all(line.as_bytes()).await?;
    Ok(())
}
1 Like

and if I have it within async function and not the main?

You can just simply declare a new codec like that? Well, that is a relief to know.

In general you could stick it in an async fn and await on it like anything else.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    do_the_line().await?;
    Ok(())
}

async fn do_the_line() -> Result<(), Box<dyn std::error::Error>> {
    let stdin = io::stdin();
    let mut reader = FramedRead::new(stdin, LinesCodec::new());
    let line = reader.next().await.transpose()?.unwrap();
    println!("You typed: {}", line);
    let mut f = File::create("/tmp/out.txt").await?;
    let line = format!("LOG: read a line: {}\n", line);
    f.write_all(line.as_bytes()).await?;
    Ok(())
}
1 Like

Great, that is what I wanted!! :grinning:
... but "next()" does not exist and poll_next() hinted at by the documentation does not seem to exist either

You do need the use imports I mentioned. next() is part of StreamExt, but use tokio::prelude::* brings that in for you.

1 Like

In my attempts I tried to move to Tokio alpha 6, which demanded something called
futures-core-preview = "0.3.0-alpha.19"
but that no longer presents as futures, as futures-preview did, so all my futures imports have now gone....
what a mess, I hope to sort it out.... I guess I need both now.

would you happen to know, if at least alpha 19 no longer needs
features = ["nightly","async-await"] ? - good, it does not need nightly, cargo told me, but still needs async-await. I think I am now getting somewhere...