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:

replay january 14

The Replay (1/14/26): Deterministic agents, Angular v21, and more

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the January 14th issue.

Matt MacCormack
Jan 14, 2026 ⋅ 33 sec read
deterministic AI alexandra spalato

How to build deterministic agentic AI with state machines in n8n

This tutorial explores how to build a robust, state-machine-driven lead qualification system using n8n, a persistent data layer (n8n data tables), and an external CRM (GoHighLevel).

Alexandra Spalato
Jan 14, 2026 ⋅ 5 min read

6 fast (native) alternatives for VSCode

VSCode has architectural performance limits. Compare six fast, native code editors built for lower resource usage.

Shalitha Suranga
Jan 9, 2026 ⋅ 10 min read

Moving beyond RxJS: A guide to TanStack Pacer

Build a React infinite scroll gallery with TanStack Pacer. Learn debouncing, throttling, batching, and rate limiting without RxJS complexity.

Emmanuel John
Jan 9, 2026 ⋅ 8 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