What returns a Future - using only std

I thought I understood that every async function returns a Future. So I don't understand this error message: " std::result::Result<std::fs::File, std::io::Error> is not a future
the trait futures::Future is not implemented for std::result::Result<std::fs::File, std::io::Error>
required by futures::Future::pollrustcE0277".
I get this error for the File::open line ad the stream.next line in the code snippet below.

use std::{
    fs::File,
    io::{BufReader, Result},
    task,
};
// This is needed to call the "lines" method on a std::io::BufReader.
use std::io::prelude::*;

async fn sum_file_async(file_path: &str) -> Result<f64> {
    let f = File::open(file_path).await?;
    let reader = BufReader::new(f);
    let mut sum = 0.0;
    let mut stream = reader.lines();
    while let Some(Ok(line)) = stream.next().await {
        if let Ok(n) = line.parse::<f64>() {
            println!("{}", n);
            sum += n;
        }
    }
    Ok(sum)
}

All I have to do to get rid of the error is change the first line to use async_std::{. But when I look at the documentation for the open function in std::fs::File and async_std::fs::File I don't see any difference that would explain why I get the error.

Basically I'm trying to learn what I can do with async/await using only what is in std.

Please post full error messages.

The full code is here: https://github.com/mvolkmann/rust-futures/blob/070c7776b409bb803168779cbc3b92b3ebe8c45e/src/main.rs#L28

The error message is:

error[E0277]: `std::result::Result<std::fs::File, std::io::Error>` is not a future
  --> src/main.rs:28:13
   |
28 |     let f = File::open(file_path).await?;
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `std::result::Result<std::fs::File, std::io::Error>` is not a future
   |
   = help: the trait `futures::Future` is not implemented for `std::result::Result<std::fs::File, std::io::Error>`
   = note: required by `futures::Future::poll`

error[E0277]: `Option<std::result::Result<String, std::io::Error>>` is not a future
  --> src/main.rs:32:32
   |
32 |     while let Some(Ok(line)) = stream.next().await {
   |                                ^^^^^^^^^^^^^^^^^^^ `Option<std::result::Result<String, std::io::Error>>` is not a future
   |
   = help: the trait `futures::Future` is not implemented for `Option<std::result::Result<String, std::io::Error>>`
   = note: required by `futures::Future::poll`

The first error is because std::fs::File::open is not an async fn, so it does not return a future. The second error is because using the std buf-reader utilities provides an iterator, whose next method is not async, and which does not return a future.

3 Likes

Is in the case that there is no way to make those operations asynchronous using only what is in std and that I need to use either async_std or tokio to do that? If so, that leads to me trying to understand what I can do with async/await using only what std provides.

Correct, the standard library does not provide any way to asynchronously open a file.

1 Like

I'm pretty sure that the only future-returning-methods in the standard library are std::future::pending and std::future::ready.

3 Likes

The signature of std::fs::File::open is:

pub fn open<P: AsRef<Path>>(path: P) -> Result<File>

while the signature of async_std::fs::File::open is:

pub async fn open<P: AsRef<Path>>(path: P) -> Result<File>

There's one difference, one is async and the other isn't. In this context that's a big difference! It means that only the async_std version returns a Future and can be awaited.

1 Like

Is it fair to say that an additional crate such as async-std or tokio must be used in order to do anything useful with async/await?

Yes.

3 Likes

Well, you can write your own futures executor, you can write your own low-level routines which will return futures and wake them after some hardware event, but in practice you'll almost always need some external library, yes.

1 Like

Yes, for now. It's worth remembering that crates are easy to add to a Rust project, and can be maintained as well as or better than the standard library (e.g. the tokio maintainers are now working at that level having reached tokio 1.0).

The standard library is very, very difficult to change due to Rust's backwards compatibility promises. As a result, Rust tends to only have things in std if one of two things apply:

  1. It's required to allow certain compiler features to work at all (e.g. std::future::Future is needed to make async and await work. In this case, the minimal form is added to std, and crates such as futures and tokio are expected to extend it into something useful.
  2. It's so stable that it's unlikely to ever need changes, just additions in the future (e.g. std::vec::Vec is a well-understood basic interface that'll never need things removed or changed incompatibly).

In the case of futures-based I/O, async_std and tokio are exploring different parts of the solution space still - once they've settled down to agree on what futures-based I/O looks like (which may still take years - and may involve completely changing the async I/O traits involved), then async I/O is ready to be considered for the standard library.

And note that there are recent OS level developments that may force a change; io_uring is a new Linux facility to do async I/O efficiently, and while it'd be nice if everyone has got things right to exploit it to the full, it's possible that big changes will be needed. If it does force big changes, then it is better for the Rust ecosystem if we don't have an inefficient form of async I/O in std, and still be telling everyone "if you care, use tokio or async_std - the stuff in std is slow".

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.