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:

How to build agentic AI when your data can’t leave the network

Large hosted LLMs aren’t always an option. Learn how to build agentic AI with small, local models that preserve privacy and scale.

Rosario De Chiara
Dec 23, 2025 ⋅ 5 min read
frontend wrapped top stories of 2025

Frontend Wrapped 2025: The 10 storylines that defined the year

What storylines defined 2025 in frontend development? We power rank them all, from AI advancements to supply chain attacks and framework breakthroughs.

Chizaram Ken
Dec 23, 2025 ⋅ 6 min read
Getting Started With NativeWind: Tailwind For React Native

Getting started with NativeWind: Tailwind for React Native

Learn how to style React Native apps with Tailwind using NativeWind v4.

Chinwike Maduabuchi
Dec 22, 2025 ⋅ 14 min read
The 10 Best React Native Component Libraries You Should Know

The 10 best React Native UI libraries of 2026

A practical guide to the best React Native UI libraries in 2026, with comparisons across performance, theming, accessibility, and Expo compatibility.

Aman Mittal
Dec 22, 2025 ⋅ 12 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

Hey there, want to help make our blog better?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now