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.
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.
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
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?
I think I have a lot of good advice to go try now. Thanks.
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...
For instance, you apparently cannot `await` in the main function without a decorator you import from, you guessed it: Tokio.
Once you stop fighting the fact that tokio = async rust for 99% of cases, things are quite smooth.
Does "use async rust everywhere now" imply "use tokio everywhere now?" Honest question.
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.
But I think OP just meant async for typical I/O, networking and DB interfaces. And yes, this usually implies the Tokio dependency.
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.
I learned the opposite, that programmers tend to make everything unnecessarly async when it's not needed causing more complexity and mental load.
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.