without any mutex guarding the buffer, even though the reads and writes happen concurrently and share the same mutable buffer. This is possible because with async/await the concurrency is cooperative, the code precisely controls where context switches can happen (in this case this is the select! waiting for event), and the compiler can see that even though the code as a whole is concurrent, the branches of select do not run at the same time in parallel.
This is not possible to achieve with threads directly. If using blocking I/O + threads model, then you'd need to dedicate one thread for reading and one for writing and then synchronize access to the shared data structure (where using a queue/channel also counts as synchronization). Which obviously would be much harder to get right.
> Which obviously would be much harder to get right.
Not obvious to me I'm afraid. Using CSP, this is almost trivially easy. All access to the data goes through a guardian thread. Accessing the resource is just sending a message.
+1. This approach of having guardian threads/actors etc communicating by messages is just a natural for Rust, too, because it nicely deals with a whole pile of borrow checker & lifecycle issues, too.
This is great until someone calls you through with a multi-threaded task runner and you've just gone and added consistency and race conditions to your code.
But this approach already works with multithreaded runner and there are no data races or consistency problems. They would be caught by rust borrow checker anyways.
E.g. I can do:
without any mutex guarding the buffer, even though the reads and writes happen concurrently and share the same mutable buffer. This is possible because with async/await the concurrency is cooperative, the code precisely controls where context switches can happen (in this case this is the select! waiting for event), and the compiler can see that even though the code as a whole is concurrent, the branches of select do not run at the same time in parallel.This is not possible to achieve with threads directly. If using blocking I/O + threads model, then you'd need to dedicate one thread for reading and one for writing and then synchronize access to the shared data structure (where using a queue/channel also counts as synchronization). Which obviously would be much harder to get right.