2020-06-25
2974
#rust
Carl Fredrik Samson
20761
Jun 25, 2020 ⋅ 10 min read

A practical guide to async in Rust

Carl Fredrik Samson Programmer located in Norway with an interest in concurrent systems. I'm curious about how things really work, whether it's computers or other areas of interest.

Recent posts:

Build Chatbots in Langchain and RAG LogRocket Article

How to build smarter frontend chatbots with RAG and LangChain.js

Build smarter frontend chatbots with RAG and LangChain.js. Learn how to add context, improve accuracy, and cut costs with a practical tutorial.

Nelson Michael
Sep 18, 2025 ⋅ 8 min read
alexandra spalato ai hallucination quote

How to stop your AI agents from hallucinating: A guide to n8n’s Eval Node

Walk through a practical example of n8n’s Eval feature, which helps developers reduce hallucinations and increase reliability of AI products.

Alexandra Spalato
Sep 17, 2025 ⋅ 6 min read

Secure your AI-generated projects with these security practices

Secure AI-generated code with proactive prompting, automated guardrails, and contextual auditing. A practical playbook for safe AI-assisted development.

Ikeh Akinyemi
Sep 16, 2025 ⋅ 5 min read

Let’s kill vibe coding and bring back prompt engineering

Explore the vibe coding hype cycle, the risks of casual “vibe-driven” development, and why prompt engineering deserves a comeback as a critical skill for building better, more reliable AI applications.

Oscar Jite-Orimiono
Sep 16, 2025 ⋅ 11 min read
View all posts

23 Replies to "A practical guide to async in Rust"

  1. 3. Spawning blocking or CPU-intensive tasks
    uses `use tokio::runtime::task;` which causes an error. The example before that uses `use tokio::task;`.
    Is the former a mistake?

    1. Starting the runtime
    with `#[tokio::main]` is there implicitly `app()` called?
    This doesn’t seem to work for me.

  2. You’re right. It should be `use tokio::task;` as in the earlier example. I’ll check if I can get that corrected. Thanks for catching.

    with `#[tokio::main]` is there implicitly `app()` called?

    No, in the first example `app` becomes your “asynchronous main”, in the second example the macro turns you “main” into your “asynchronous main”. What happens behind the scenes is pretty much the same though but you lose access to your “synchronous main” in the latter example.

  3. Looks like the “shorter version” should be
    “`
    #[tokio::main]
    async fn main() {
    app().await;
    }
    “`

  4. I had to change
    “`
    let res = future::ok(“Hello world”.to_string()).await?;
    “`
    to
    “`
    let res = future::ok::<String,Box>(“Hello world”.to_string()).await.unwrap();
    “`
    to get rid of compile errors

  5. “CPU-intensive tasks”
    `let res= task::spawn_blocking(move ||analyze(txt)).await?;`
    is missing a `&`
    `let res= task::spawn_blocking(move ||analyze(&txt)).await?;`

  6. I guess the issues were meant as an exercise for the reader 😉
    Thanks for posting. Very helpful.

  7. You could write that but if you `cargo expand` (cargo install cargo-expand) the example I wrote you get something like this:
    “`
    fn main() {
    tokio::runtime::Builder::new()
    .basic_scheduler()
    .threaded_scheduler()
    .enable_all()
    .build()
    .unwrap()
    .block_on(async {
    {
    {
    todo!();
    }
    }
    })
    }
    “`

    As you see the “app” part is wrapped in an async block and passed to the runtime so “main” would in essence function like the “app” in the example above.

  8. Yeah, you’re right. I was trying to avoid these in this article but it seems I inadvertently introduced it in those examples. I think it’s better to change it to:

    “‘
    async fn our_async_program() -> Result {
    future::ok(“Hello world”.to_string()).await
    }
    “‘
    Since the compiler can infer the rest in this case. Thanks for pointing it out.

  9. Yes, you’re right. analyze took a “String” in an earlier draft but I changed that without catching this one. Should be fixed soon.

  10. Glad you enjoyed it. Well, I couldn’t make it too easy 🙂 Seriously, thanks for posting. The next person testing all the code should have a slightly easier time, though.

  11. Hi Philip. I see where the confusion lies now. You see, `#[tokio::main]`is a macro that rewrites `fn main()` in a way that the code ends up looking like the first example.

    The difference is that the code you write in a main function with `#[tokio::main]` is wrapped in an async block instead of put in a function called `app` but the end result is pretty similar.

    I posted the output from the macro in another answer below but it doesn’t look pretty in the comments section here. If you want to check it out for yourself install `cargo install cargo-expand` and run `cargo expand` in the root of a project with the example code in `main.rs`. You’ll see what it expands into.

  12. > // Returning errors using `?` in iterators can be a bit difficult. Using a
    // simple for loop to inspect and work with our results can often be more
    // ergonomic

    What’s wrong with `try_for_each`?

  13. Hi Daniel.

    That’s a good suggestion, but I feel the example gets harder to understand using Iterators since we can’t simply unwrap using `?` as we do in the rest of the examples.

    `results` is of type: `Vec<Result<Result<(u64, u64), Box, JoinError>>` which is a nested result making the iterator version pretty difficult to understand for people not intimately familiar with functional style programming.

    We might as well use `try_fold` instead of `try_for_each` if we were to do this operation using a functional style so the code would look something like this:

    “`
    let (total_ones, total_zeros) =
    results
    .into_iter()
    .flatten()
    .try_fold((0, 0), |(total_ones, total_zeros), res| {
    res.map(|(ones, zeros)| (total_ones + ones, total_zeros + zeros))
    })?;
    “`

    I feel it makes things more complicated than it needs to be in an example where the main focus is on keeping the code easy to read for a wide audience without really giving a lot of benefit in return 🙂

  14. Thank you soo much Carl for this amazing article, it has helped me a lot to polish my understanding of async Rust.

  15. The slowwly service seems broken or more “sloww” than intended, so the examples do not really work right now.
    You can work around that by requesting e.g. `http://example.com` and sleeping in the appropriate place using `tokio::time::sleep(Duration::from_secs(1)).await;`. Otherwise good introduction!

Leave a Reply