Julio Biason
2 years ago
1 changed files with 89 additions and 0 deletions
@ -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. |
||||||
|
|
||||||
|
<!-- more --> |
||||||
|
|
||||||
|
## 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] |
||||||
|
|
Loading…
Reference in new issue