Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> After two decades of JavaScript and decent experience with Go, this is the most significant source of frustration and friction with Rust. It’s not an insurmountable problem, but you must always be ready to deal with the async monster when it rears its head. In other languages, async is almost invisible.

I am a former C and C++ programmer who lived calling into pthread almost every week for a decade. I use async rust everywhere now.

I don’t get the hate that async gets. In my opinion, everyone should be using async for everything. Including stuff that’s seemingly single threaded “simple” stuff.



I think it is the infectiousness of it. Especially in embedded or wasm contexts, the predominant async may not be the async you want. Wasm being the author's use case would definitely have provided a different perspective.

Similarly, I find tasks that use or reuse large buffers to avoid the performance hits from allocation, often benefit from old fashioned thread pools. Bump or shard allocators can make this work ok, but in the cases where you are cpu bound on tight loops of vectorizable operations, thread pools perform better. Async is a good tool, but there are contexts it isn't optimal for.


Recently tried writing some async Rust to compare the error handling when nested async calls are made to how errors are handled in Go, and it seemed like the trivial example I was trying to write up simply couldn't be done without involving Tokio. That barrier simply doesn't exist in Go, or C#, or Typescript.

For instance, you apparently cannot `await` in the main function without a decorator you import from, you guessed it: Tokio.


You need an async runtime to run async code yes, and Rust's isn't built in. Why does that matter though? Rust has a decent package manager; add the dependency and move on.


Why are you trying to avoid Tokio lol, tokio is the defacto async runtime in rust, saying you're trying to avoid it is like saying you're trying to avoid async while writing async, somehow people act like if they merged tokio into std and instead of #[tokio::main] or whatever you had to do #[async::main] it would somehow be better.

Once you stop fighting the fact that tokio = async rust for 99% of cases, things are quite smooth.


Tokio simply doesn't meet the ise case of some people doing wasm, and many people doing embedded. That isn't a huge deal, just don't use it right? Except many otherwise usable crates seem to adopt tokio unnecessarily.


If Tokio was the final answer, https://github.com/tokio-rs/tokio-uring/ wouldn't exist.


Your "lol" is entirely inappropriate.


The problem that I am running into at the moment is that a few things like the rhai Engine aren't Send and I am trying to use them in an async closure. What GPT-4 suggested was creating a tokio Runtime inside the thread and then block_on(). I will try it tomorrow. (This is the first significant Rust project for me.)


The answer here is almost certainly one of these:

1. Fix rhai::Engine so it is Send (if !Send is unintentional)

2. Use tokio::spawn_blocking or normal threads to run the rhai::Engine bits

3. Don't hold the rhai::Engine across an await point.

Which one depends on rhai Engine details and what you want to accomplish.

Doing a block on inside a new thread seems unlikely to do anything useful (unless there's some undisclosed detail that makes it reasonable).

I encourage you to ask about this in the rhai repo in a discussion or issue.


1. Not applicable unless it is absolutely required, and not something I will consider until I have exhausted other options, since there is a reason they have not made it Send already. It will likely be quite complex and enlarge the scope.

2. I am using a normal thread but when I make it async the compiler wants Send.

3. The await point is in code that uses the engine. I am not sure there is another good option, since I need to use an API that has several libraries all of which are async.

The block_on is to allow the tokio Runtime created in that thread to execute/poll it

So, thank you for your input, I will test out the suggestion that I mentioned above, and then maybe look into spawn_blocking if that doesn't work.


That ChatGPT suggestion is dodgy. Tokio is going to complain when you create a runtime while in async runtime, and refuse block_on in an async context.

You can have multiple runtimes, but create them ahead of time, in synchronous main, and keep a Handle to them.

But you probably need: https://docs.rs/tokio/latest/tokio/task/struct.LocalSet.html


> 2. I am using a normal thread but when I make it async the compiler wants Send.

This is likely because you are using the multi-threaded scheduler, which requires futures to be Send, even if you’re running only a single thread. This is because Tokio is based on a “work stealing” runtime, so in “normal” operations, expects the futures themselves to be able to be shuffled around threads where necessary.

For you use case, try running the single threaded executor, additionally try the “local set” executors. These do not require Send as they are statically guaranteed to be confined to the thread they are spawned on. Block on will also work.

Out of curiosity though, how is the interaction with Rhai performed, are you passing around the engine, and executing the code at certain points where applicable? Do you just need certain results from it sometimes? Etc?


Right, GPT-4 explained that much about shuffling. I am trying to do all the interaction with MPSC channels but was still running into issues.

I think I have a lot of good advice to go try now. Thanks.


From the rhai Engine docs: `Currently, Engine is neither Send nor Sync. Use the sync feature to make it Send + Sync.`


According to the documentation[0], enabling the "sync" feature should make the Engine Send+Sync.

From a quick look at the source, it looks like it switches between using Rc/RefCells and Arc/RwLock.

[0] https://docs.rs/rhai/1.16.2/rhai/#enable-special-functionali...


It being !Send means its not safe to be sent lol, that's down to the way they implemented rhai engine not rust. It's just that rust catches that it's not safe to send because of the trait bounds.


You might be able to use the LocalSet (https://docs.rs/tokio/latest/tokio/task/struct.LocalSet.html) to run !Send futures on a single thread.


> In my opinion, everyone should be using async for everything. Including stuff that’s seemingly single threaded “simple” stuff.

I learned the opposite, that programmers tend to make everything unnecessarly async when it's not needed causing more complexity and mental load.


Can you please elaborate on this? What's an example of a seemingly single threaded simple task where you would turn to async?

Does "use async rust everywhere now" imply "use tokio everywhere now?" Honest question.


The way closures made it easy to encapsulate code and state in an object you can run at any time, async encapsulates code and state for ability to run and pause or cancel at any time.

This is handy for I/O that can be interleaved and cancelled. You can (ab)use it for other things like generators or various DIY multitasking operations. It can also be a state machine generator (e.g. AI of actors in a game).

But I think OP just meant async for typical networking and DB interfaces. And yes, this usually implies the Tokio dependency.




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: