This commit is contained in:
Carl Fredrik Samson
2020-02-11 21:30:35 +01:00
parent 526921f5a6
commit 9f2dd2af47
4 changed files with 95 additions and 48 deletions

View File

@@ -13,6 +13,78 @@ information that will help demystify some of the concepts we encounter.
Actually, after going through these concepts, implementing futures will seem Actually, after going through these concepts, implementing futures will seem
pretty simple. I promise. pretty simple. I promise.
## Futures
So what is a future?
A future is a representation of some operation which will complete in the
future.
Now, when we talk about futures I find it useful to make a distinction between
**non-leaf** futures and **leaf** futures early on because in practice they're
pretty different from one another.
### Leaf futures
Runtimes create _leaf futures_ which represents a resource like a socket.
```rust, ignore, noplaypen
// stream is a **leaf-future**
let mut stream = tokio::net::TcpStream::connect("127.0.0.1:3000");
```
Operations on these resources, like a `Read` on a socket, will be non-blocking
and return a future which we call a leaf future since it's the future which
we're actually waiting on.
It's unlikely that you'll implement a leaf future yourself unless you're writing
a runtime, but we'll go through how they're constructed in this book as well.
It's also unlikely that you'll pass a leaf-future to a runtime and run it to
completion alone as you'll understand by reading the next paragraph.
### Non-leaf-futures
Non-leaf-futures is the kind of futures we as _users_ of a runtime writes
ourselves using the `async` keyword to create a **task** which can be run on the
executor.
This is an important distinction since these futures represents a
_set of operations_. Often, such a task will `await` a leaf future as one of
many operations to complete the task.
```rust
// Non-leaf-future
let non_leaf = async {
let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap();// <- yield
println!("connected!");
let result = stream.write(b"hello world\n").await; // <- yield
println!("message sent!");
// ...
};
```
The key to these tasks is that they're able to yield control to the runtime's
scheduler and then resume execution again where it left off at a later point.
In contrast to leaf futures, these kind of futures does not themselves represent
an I/O resource. When we poll these futures we either run some code or we yield
to the scheduler while waiting for some resource to signal us that it's ready so
we can resume where we left off.
### Runtimes
Quite a bit of complexity attributed to `Futures` are actually complexity rooted
in runtimes. Creating an efficient runtime is hard.
Learning how to use one correctly can require quite a bit of effort as well, but
you'll see that there are several similarities between these kind of runtimes so
learning one makes learning the next much easier.
The difference between Rust and other languages is that you have to make an
active choice when it comes to picking a runtime. Most often you'll just use
the one provided for you.
## Async in Rust ## Async in Rust
Let's get some of the common roadblocks out of the way first. Let's get some of the common roadblocks out of the way first.
@@ -57,41 +129,8 @@ concepts you need to know about. This is not true. In practice today you'll only
interface with the runtime, which provides leaf futures which actually wait for interface with the runtime, which provides leaf futures which actually wait for
some I/O operation, and the executor where some I/O operation, and the executor where
## Futures
Now, when we talk about futures I find it useful to make a distinction between ## Bonus section
non-leaf futures and **leaf** futures.
Runtimes create leaf futures which represents a resource like a socket.
Operations on these resources, like a `Read` on a socket, will be non-blocking
and instead return a future which we call a leaf future since it's the future
which we're actually waiting on.
It's unlikely that you'll implement a leaf future yourself unless you're writing
a runtime, but we'll go through how they're constructed in this book as well.
Non-leaf futures is the kind of futures we as users of a runtime writes
ourselves using the `async` keyword to create a task which can be run on the
executor. Often, such a task will `await` a leaf future as one of many
operations to complete the task.
The key to these tasks is that they're able to yield control to the runtime's
scheduler and then resume execution again where it left off at a later point.
In contrast to leaf futures, these kind of futures is not themselves
waiting on some I/O resource.
Quite a bit of complexity attributed to `Futures` are actually complexity rooted
in runtimes. Creating an efficient runtime is hard.
Learning how to use one correctly can require quite a bit of effort as well, but you'll see that there are several similarities between these kind of runtimes so
learning one makes learning the next much easier.
The difference between Rust and other languages is that you have to make an
active choice when it comes to picking a runtime. Most often you'll just use
the one provided for you.
## Before we move on
If you find the concepts of concurrency and async programming confusing in If you find the concepts of concurrency and async programming confusing in
general, I know where you're coming from and I have written some resources to general, I know where you're coming from and I have written some resources to

View File

@@ -1,12 +1,14 @@
# Trait objects and fat pointers # Waker and Context
> **Relevant for:** > **Relevant for:**
> >
> - Understanding how the Waker object is constructed > - Understanding how the Waker object is constructed
> - Getting a basic feel for "type erased" objects and what they are > - Learning how the runtime know when a leaf-future can resume
> - Learning the basics of dynamic dispatch > - Learning the basics of dynamic dispatch and trait objects
## Trait objects and dynamic dispatch ## The Waker
The `Waker` trait is an interface where a
One of the most confusing things we encounter when implementing our own `Futures` One of the most confusing things we encounter when implementing our own `Futures`
is how we implement a `Waker` . Creating a `Waker` involves creating a `vtable` is how we implement a `Waker` . Creating a `Waker` involves creating a `vtable`

View File

@@ -3,7 +3,7 @@
[Introduction](./introduction.md) [Introduction](./introduction.md)
- [Some background information](./1_background_information.md) - [Some background information](./1_background_information.md)
- [Trait objects and fat pointers](./2_trait_objects.md) - [Waker and Context](./2_waker_context.md)
- [Generators](./3_generators_pin.md) - [Generators](./3_generators_pin.md)
- [Pin](./4_pin.md) - [Pin](./4_pin.md)
- [Futures - our main example](./6_future_example.md) - [Futures - our main example](./6_future_example.md)

View File

@@ -2,15 +2,21 @@
This book aims to explain `Futures` in Rust using an example driven approach. This book aims to explain `Futures` in Rust using an example driven approach.
The goal is to get a better understanding of `Futures` by implementing a toy The goal is to get a better understanding of "async" in Rust by creating a toy
`Reactor`, a very simple `Executor` and our own `Futures`. runtime consisting of a `Reactor` and an `Executor`, and our own futures which
we can run concurrently.
We'll start off a bit differently than most other explanations. Instead of We'll start off a bit differently than most other explanations. Instead of
deferring some of the details about what's special about futures in Rust we deferring some of the details about what `Futures` are and how they're
try to tackle that head on first. We'll be as brief as possible, but as thorough implemented, we tackle that head on first.
as needed. This way, most questions will be answered and explored up front.
We'll end up with futures that can run an any executor like `tokio` and `async_str`. I learn best when I can take basic understandable concepts and build piece by
piece of these basic building blocks until everything is understood. This way,
most questions will be answered and explored up front and the conclusions later
on seems natural.
I've limited myself to a 200 line main example so that we need keep
this fairly brief.
In the end I've made some reader exercises you can do if you want to fix some In the end I've made some reader exercises you can do if you want to fix some
of the most glaring omissions and shortcuts we took and create a slightly better of the most glaring omissions and shortcuts we took and create a slightly better