From eb3bc0bcc81bc3c55e293ddbaf93af276c5e19f6 Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Fri, 29 Jul 2022 17:21:09 -0300 Subject: [PATCH] WIP: Async mental model --- .../a-mental-models-for-async-rust.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 content/thoughts/a-mental-models-for-async-rust.md diff --git a/content/thoughts/a-mental-models-for-async-rust.md b/content/thoughts/a-mental-models-for-async-rust.md new file mode 100644 index 0000000..2a65bf9 --- /dev/null +++ b/content/thoughts/a-mental-models-for-async-rust.md @@ -0,0 +1,89 @@ ++++ +title = "A Mental Model for Async Rust" +date = 2022-07-29 +draft = true + +[taxonomies] +tags = ["rust", "async"] ++++ + +When I tried to do async Rust, I got a bunch of errors from the borrow checker +that, to me, it didn't make sense -- and wouldn't be an issue if I was using +threads. + +It took me awhile to figure out a mental model for doing it right. + + + +## A problem with naming + +I think my initial problem started with naming. The concept of async/await is +quite recent, but for a long time we've been talking about "greenthreads" and +"light-weight threads" -- "threads" that are managed by the application and not +the OS. While there are some differences between greenthreads and async things, +the naming stuck with me (and I *think* I saw some posts linking the two). + +Still on naming, [Tokio](https://tokio.rs/), the most popular async framework +in Rust, uses `task::spawn` to spawn a new task, which is pretty close to the +thread call, `thread::spawn` -- and both return a structure called +`JoinHandle` -- so this mixture of "tasks/greenthreads are threads" got pretty +ingrained to me. + +{% note() %} +Yeah, yeah, other languages avoid this by using their own words, but my contact +with async was with Rust, so... +{% end %} + +## A problem with structure + +So you get this "async is thread" mentality due aproximation. And then you try +to build something async using the same model. + +For example, a producer/consumer in Rust would be something like: + +```rust +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + let self_tx = tx.clone(); + + let consumer = thread::spawn(move || { + while let Ok(msg) = rx.recv() { + println!("Message: {}", msg); + + if msg > 1000 { + // actually, we just need to drop self_tx, otherwise the consumer will keep waiting + // for inputs from it, even when tx was already dropped when the producer ended. + // the problem with a direct drop is that rustc can't see that it won't be used + // anymore. + break; + } else if msg % 2 == 0 { + if self_tx.send(msg * 2).is_err() { + println!("Failed to push new value to consumer"); + break; + }; + } + } + }); + + let producer = thread::spawn(move || { + for i in 1..12 { + if tx.send(i).is_err() { + println!("Failed to send {}, ending producer", i); + break; + } + } + // tx.send(0); + }); + + producer.join().unwrap(); + consumer.join().unwrap(); +} +``` + +(Yeah, I did all in a single file. Sue me.) + +[async is channel, not spawn] +