Conclusion and exercises
Congratulations. Good job! If you got this far, you must have stayed with me all the way. I hope you enjoyed the ride!
Remember that you can always leave feedback, suggest improvements or ask questions in the issue_tracker for this book. I'll try my best to respond to each one of them.
I'll leave you with some suggestions for exercises if you want to explore a little further below.
Until next time!
Reader exercises
So our implementation has taken some obvious shortcuts and could use some improvement. Actually, digging into the code and trying things yourself is a good way to learn. Here are some good exercises if you want to explore more:
Avoid wrapping the whole Reactor
in a mutex and pass it around
First of all, protecting the whole Reactor
and passing it around is overkill. We're only
interested in synchronizing some parts of the information it contains. Try to refactor that
out and only synchronize access to what's really needed.
I'd encourage you to have a look at how async_std solves this with a global runtime which includes the reactor and how tokio's rutime solves the same thing in a slightly different way to get some inspiration.
- Do you want to pass around a reference to this information using an
Arc
? - Do you want to make a global
Reactor
so it can be accessed from anywhere?
Building a better executor
Right now, we can only run one and one future only. Most runtimes have a spawn
function which let's you start off a future and await
it later so you
can run multiple futures concurrently.
As I suggested in the start of this book, visiting @stjepan'sblog series about implementing your own executors is the place I would start and take it from there. You could further examine the source code of smol - A small and fast async runtime which is a good project to learn from.
Create a unique Id for each task
As we discussed at the end of the main example. What happens if the user pass in the same Id for both events?
let future1 = Task::new(reactor.clone(), 1, 1);
let future2 = Task::new(reactor.clone(), 2, 1);
Right now, it will deadlock since our poll
method thinks the first poll of
future2
is future1
getting polled again and swaps out the waker with the
one from future2
. This waker never gets called since the task is never
registered.
It's probably a bad idea to expose the user to this behavior, so we should have a unique Id for each task which we use internally. There are many ways to solve this. Below is two suggestions to get going:
- Let the reactor have a
usize
which is incremented on every task creation - Use a crate like
Guid
to generate an unique Id for each task
Further reading
There are many great resources. In addition to the RFCs and articles I've already linked to in the book, here are some of my suggestions:
smol - a small and fast async runtime
Aron Turon: Designing futures for Rust
Steve Klabnik's presentation: Rust's journey to Async/Await
Stjepan's blog with a series where he implements an Executor
Jon Gjengset's video on The Why, What and How of Pinning in Rust