The async/await paradigm is implemented in a variety of different popular programming languages, but it is not the same thing everywhere.
In this presentation I'll show you an overview of the basic asyncrounous programming in Rust "flavor".
We will se the why the community had decided to introduce this paradigm, and how it has matched some critical aspects with the Rust philosophy.
3. Fast Doesn’t Mean Efficient
Rust is very fast…
...but still have to wait for:
- I/O operation
- Network connections
- Heavy task to complete
4. Threads are not Enough
Treads are good to long and heavy tasks in parallel
Not enough for I/O bound workload.
You need more effective tools!
5. Rust was Late to the Party
Concurrent programming was available
just later on Rust,
for a couple of reasons…
6. Green Thread or not green Thread?
At the very beginning there was green threads in Rust.
They don’t fit well.
So they move on System Thread.
But they miss something...
8. Not Every Solution Fits Well
●
Event-Driven with callbacks: data flow and error
propagation is often hard to follow
●
Coroutines: they abstract away low level details that
are important for custom runtime implementors
●
The actor model: it doesn’t provide a full
abstraction, what about flow control and retry logic?
9. Async / Await
●
Lazy Futures
●
Async is zero-cost
●
No built-in runtime
●
Available on single and multi-thread
●
Reduced CPU and memory overhead for
I/O workload
10. The Borrow Checker Wasn’t Enough
How do you safely move a struct that
contains references?
And how about self referenced struct?
11. A Future in Rust
async {
let mut x = [0; 128];
let read_into_buf_fut = read_into_buf(&mut x);
read_into_buf_fut.await;
println!("{:?}", x);
}
struct ReadIntoBuf<'a> {
buf: &'a mut [u8], // points to `x` below
}
struct AsyncFuture {
x: [u8; 128],
read_into_buf_fut: ReadIntoBuf<'what_lifetime?>,
}
You write:
It become:
12. Just Whatch an Example: a struct
#[derive(Debug)]
struct Test {
a: String,
b: *const String,
}
impl Test {
fn new(txt: &str) -> Self {
Test {
a: String::from(txt),
b: std::ptr::null(),
}
}
fn init(&mut self) {
let self_ref: *const String = &self.a;
self.b = self_ref;
}
fn a(&self) -> &str { &self.a }
fn b(&self) -> &String {
assert!(!self.b.is_null(), "Test::b called
without Test::init being called first");
unsafe { &*(self.b) }
}
}
13. Just Whatch an Example: the main
fn main() {
let mut test1 = Test::new("test1");
test1.init();
let mut test2 = Test::new("test2");
test2.init();
println!("a: {}, b: {}", test1.a(), test1.b());
println!("a: {}, b: {}", test2.a(), test2.b());
}
$ cargo run
a: test1, b: test1
a: test2, b: test2
If you run it will print:
14. Just Whatch an Example: the main2
fn main() {
let mut test1 = Test::new("test1");
test1.init();
let mut test2 = Test::new("test2");
test2.init();
println!("a: {}, b: {}", test1.a(), test1.b());
std::mem::swap(&mut test1, &mut test2);
println!("a: {}, b: {}", test2.a(), test2.b());
}
$ cargo run
a: test1, b: test1
a: test1, b: test1
...but now:
15.
16. All Rust Types are Movable in
Memory
How to check if it is safe to move?
17. Pin, Unpin, !Unpin
Pin a value,
if the type of the value
Implements Unpin it can be move.
If it implement !Unpin it is guaranteed
that it will not move.
move in memory
18. The Future Trait
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
async fn foo() -> u8 { 5 } // this returns a future
fn bar() -> impl Future<Output = u8> {
// This `async` block results in a type that implements
// `Future<Output = u8>`.
async {
let x: u8 = foo().await; // you have to .await a future
x + 5
}
}
19. The Future STD Package
pub fn pending<T>() -> Pending<T> // It’s a future that will
// never resolve
pub fn poll_fn<T, F>(f: F) -> PollFn<F>
where
F: FnMut(&mut Context<'_>) -> Poll<T>,
pub fn ready<T>(t: T) -> Ready<T>
20. Streams
async fn sum_with_next(mut stream: Pin<&mut dyn Stream<Item = i32>>) -> i32 {
use futures::stream::StreamExt; // for `next`
let mut sum = 0;
while let Some(item) = stream.next().await {
sum += item;
}
sum
}
async fn jump_around(
mut stream: Pin<&mut dyn Stream<Item = Result<u8, io::Error>>>,
) -> Result<(), io::Error> {
use futures::stream::TryStreamExt; // for `try_for_each_concurrent`
const MAX_CONCURRENT_JUMPERS: usize = 100;
stream.try_for_each_concurrent(MAX_CONCURRENT_JUMPERS, |num| async move {
jump_n_times(num).await?;
report_n_jumps(num).await?;
Ok(())
}).await?;
Ok(())
}
21. Await in an Async Way
async fn get_book_and_music() -> (Book, Music) {
let book = get_book().await;
let music = get_music().await;
(book, music)
}
async fn get_book_and_music() -> (Book, Music) {
let book_fut = get_book();
let music_fut = get_music();
join!(book_fut, music_fut)
} A `select!` macro is
available
22. Nothing of What You Saw Until Now
Will Work in Rust
...if you don’t have a Runtime!
23. What is an Async/.await Runtime
●
Take care to poll Futures / Streams until it
resolve
●
Take care to run Futures / Streams on threads
pool
24. An Example
// normal Rust main
fn main() {
// write your code here
}
// Tokio Runtime main
#[tokio:main]
async fn main() -> Result<()> {
// write your code here
//…
let my_fut = get_a_fut();
my_fut.await
}
25. References
●
Rust book: The Rust Programming Language
– (https://doc.rust-lang.org/book/)
●
Rustonomincon
– (https://doc.rust-lang.org/nomicon/)
●
The Async Book ( It’s not complete)
⚠
– (https://rust-lang.github.io/async-book/)
●
Crossbeam
– (https://github.com/crossbeam-rs/crossbeam)
●
Tokio
– (https://tokio.rs/)
●
async-std
– (https://docs.rs/async-std/latest/async_std/ )
●
Jon Gjengset YouTube channel
– (https://www.youtube.com/c/JonGjengset)
●
Rust Playground
– (https://play.rust-lang.org/)
●
Futures
– (https://docs.rs/futures/latest/futures/)