4 ms·
I recently started working with Rust async. The main issue I am currently facing is code duplication: I have to duplicate every function that I want to support
by conaclos 5mo ago
I recently started working with Rust async. The main issue I am currently facing is code duplication: I have to duplicate every function that I want to support both asynchronous and blocking APIs. This could be great to have a `maybe-async`. I took a look at the available crates to work around this (maybe-async, bisync), but they all have issues or hard limitations.
- albertzeyer 5mo agoThe classic function coloring problem. https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
- small_scombrus 5mo agoIt'll depend immensely on what you're actually doing, but if it's simple enough you may be able to make a macro that subs out the types & awaits
- conaclos 4mo agoOne of the issue I face is a blocking function that takes a generic constrained by a `trait` and its async version takes a generic constrained by an `async trait`.
- K0nserv 5mo agoThere is work happening on keyword generics[0], which would let a function be generic over keywords like `async` and `const`. For now the best option to write code that wants to live in both worlds is sans-io. Thomas Eizinger at Fireguard has written a good article about this[1] pattern. Not only does it nicely solve the sync/async issue, but it also makes testing easier and opens the door to techniques like DST[2] I have my own writing on the topic[3], which highlights that the problem is wider than just async vs sync due to different executors. 0: https://github.com/rust-lang/effects-initiative https://github.com/rust-lang/effects-initiative 1: https://www.firezone.dev/blog/sans-io https://www.firezone.dev/blog/sans-io 2: https://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html https://notes.eatonphil.com/2024-08-20-deterministic-simulat... 3: https://hugotunius.se/2024/03/08/on-async-rust.html https://hugotunius.se/2024/03/08/on-async-rust.html
- conaclos 4mo ago> For now the best option to write code that wants to live in both worlds is sans-io Thanks for sharing! Reading the articles, it looks at me, it is a kind of manual reimplementation of the state machine generated by async? This also makes the code harder to reason with. I am unsure if it is worth the complexity.
- ignoreusernames 5mo agoI may have missed something, but how does “sans-io” deal with CPU heavy code? For example, if there’s some heavy decoding/encoding required on the data? Does the event loop only drive the network side and the heavy part is done after the loop is finished?
- K0nserv 5mo agoThis is a great question and there isn't a definitive answer provided in the sources I linked. Broadly I think there are three approaches: 1. For frequent and small CPU heavy tasks, just run them on the IO threads. As long as you don't leave too long between `.await` points (~10ms) it seems to work okay. 2. Run your sans-io code on a dedicated CPU thread and do IO from an async runtime. This introduces overhead that needs to be weighed against the amount of CPU work. 3. Have the sans-io code output something like `Output::DoHeavyCompute { .. }` and later feed the result back as `Input::HeavyComputeResult { .. }`, in the middle run the work on a thread pool.
- K0nserv 4mo agoRealised all of these examples are for async, but they apply equally for sync.
- selfmodruntime 5mo agoYou won't get any benefits using async with CPU heavy code. Quite the opposite really.
- paavohtl 5mo agoConsidering the latest commits and issues in effects-initiative are about 2 years old, the keyword generics initiative seems effectively dead.
- fluffybucktsnek 4mo agoIn my perspective, an "async" function is already an "maybe-async". The distinction between a a `fn -> void` and `fn -> Future<void>` is that the former executes till its end immediately, whereas the other may only finish at another time. If you want to run an async fn in a blocking manner, you would use a blocking executor.