Could not find main in async_std

Another day, another basic Rust thing that I can't figure out on my own. So sorry to keep posting here so frequently.

In Cargo.toml:

async-std = "1.9.0"
futures = "0.3.12"

In src/main.rs:

use async_std::fs::{File};
use async_std::io::{BufReader, Error, Result};
use async_std::prelude::*;

async fn sum_file(file_path: &str) -> Result<i32, io::Error> {
    let f = File::open(path)?;
    let reader = BufReader::new(f);
    let mut sum = 0.0;
    for line in reader.lines() {
        let n : f64 = line.parse()?;
        println!("n = {}", n);
        sum += n;
    }
    Ok(sum)
}

#[async_std::main]
async fn main() {
    match sum_file("./numbers.txt").await {
        Ok(sum) => println!("sum = {}", sum),
        Err(e) => eprintln!("error = {}", e)
    }
}

Clippy output:

18 | #[async_std::main]                                                                                                        │https://eslint.org has 26 img tags
   |              ^^^^ could not find `main` in `async_std`

followed by many more errors. But why do I get this first error?

The attributes feature is required.

async-std = {version = "1.9.0", features = ["attributes"]}
1 Like

Thanks! I've lost count of how many times I've been bitten by this specific issue. It's hard to get used to the idea that saying you want to use a crate does not mean you can use all the features of the crate. This is probably an area where experience in any other programming language hurts new Rust developers because other languages don't work that way. For example, when you add a Node module to package.json you always get access to all of it. I understand the rationale for the Rust approach, but I keep forgetting this detail.

Is there are convention for feature names that I can read about somewhere?

I would compare crate features to build configurations used in C/C++ (#ifdef FEATURE_XXX...) instead of node...
I don't know of any feature naming conventions, docs.rs usually marks components that require specific features (async_std::main) which is really convenient.

1 Like

Here is my latest code. When using std instead of async_std I can use the lines method to read and iterate over the lines in a file. But I can't get that to work with async_std. I see that the type Lines is an iterator in std, but it is a Stream in async_std. But I can't find an example of working with that.

use async_std::fs::{File};
use async_std::io::{BufReader, Result};
use async_std::prelude::*;

// The return type, in this case a Result, is wrapped in a Future.
async fn sum_file(file_path: &str) -> Result<f64> {
    let f = File::open(file_path).await?;
    let reader = BufReader::new(f);
    let mut sum = 0.0;
    for line in reader.lines() { // Error is "async_std::io::Lines<...> is not an iterator"
        let n : f64 = line.parse()?;
        println!("n = {}", n);
        sum += n;
    }
    Ok(sum)
}

#[async_std::main]
async fn main() {
    match sum_file("./numbers.txt").await {
        Ok(sum) => println!("sum = {}", sum),
        Err(e) => eprintln!("error = {}", e)
    }
}

Since Lines is an async Stream, it must yield to the executor - and requires the await keyword between iterations (otherwise, it wouldn't be async...)

I think this should work:

let mut lines = reader.lines();
// instead of matching Some(Ok(..)) you may want to handle errors inside the loop.
while let Some(Ok(line)) = lines.next().await {
        let n : f64 = line.parse()?;
        println!("n = {}", n);
        sum += n;
}
1 Like

Thanks! I also had to change what I was doing with line.parse. This works:

use async_std::fs::{File};
use async_std::io::{BufReader, Result};
use async_std::prelude::*;

// The return type, in this case a Result, is wrapped in a Future.
async fn sum_file(file_path: &str) -> Result<f64> {
    let f = File::open(file_path).await?;
    let reader = BufReader::new(f);
    let mut sum = 0.0;
    //for line in reader.lines() { // can use this with std::io::BufReader
    let mut stream = reader.lines();
    while let Some(Ok(line)) = stream.next().await {
        if let Ok(n) = line.parse::<f64>() {
            println!("n = {}", n);
            sum += n;
        }
    }
    Ok(sum)
}

#[async_std::main]
async fn main() {
    match sum_file("./numbers.txt").await {
        Ok(sum) => println!("sum = {}", sum),
        Err(e) => eprintln!("error = {}", e)
    }
}

It seems like it would be nice if Cargo.toml supported syntax like this to opt into ALL features of a crate, making it unnecessary to investigate which specific features are needed when I'm just trying to learn how to use a new crate:

async-std* = "1.9.0"

where the * means "I want it all".

Some crates add their own 'all'/'full'. But making it standard could be problematic. Some features might be mutual exclusive (like selecting a backend for something).
The 'default' feature exists for most cases, but the async_std authors decided to exclude attributes from the default features - probably because it may increase compile times

1 Like

I guess in the end this is a documentation issue. For example, where does async-std document how it should be added as a dependency? I needed this:

async-std = {version = "1.9.0", features = ["attributes"]}
futures = "0.3.12"

But where would I look to discover that? It seems like that should be directly on this page:

Here’s the explanation:

https://docs.rs/async-std/1/async_std/#features

Items marked with attributes are available only when the attributes Cargo feature is enabled:

[dependencies.async-std]
version = "1.7.0"
features = ["attributes"]

And here’s the main macro:

https://docs.rs/async-std/1/async_std/attr.main.html

Attribute Macro async_std::main

#[main]

This is supported on attributes only.

[−] Enables an async main function.


Note that

[dependencies.async-std]
version = "1.7.0"
features = ["attributes"]

Is equivalent to

[dependencies]
async-std = {version = "1.7.0", features = ["attributes"]}

in the TOML format.


Actually.. where did you find out about the async_std::main attribute macro and didn’t that place mention the attributes feature? E.g. the Examples section of the async-std docs do mention the feature as well.

1 Like

Ah, I see this now in the "Examples" section: "All examples require the "attributes" feature to be enabled." New Rust developers may not know what that means, but I guess they need to learn that. Unfortunately I found many examples on the web that show using #[async_std::main], but none that show these lines to be added in Cargo.toml:

async-std = {version = "1.9.0", features = ["attributes"]}
futures = "0.3.12"

It's also hard for the compiler to give useful error messages for these cases, because, from its point of view, the async_std::main item simply doesn't exist when the required feature is not enabled. In some cases, it doesn't even parse the code in a disabled module.

A better error message would be really useful here, though. It would be tricky, but maybe the compiler could make a best-effort attempt to track the names of items that are disabled by cfg attributes, so it can provide a useful suggestion when a feature is missing.

1 Like