From 9f2dd2af47737dbe4476d59c068077d6ed5b2d03 Mon Sep 17 00:00:00 2001 From: Carl Fredrik Samson Date: Tue, 11 Feb 2020 21:30:35 +0100 Subject: [PATCH] contd --- src/1_background_information.md | 111 ++++++++++++------ ...{2_trait_objects.md => 2_waker_context.md} | 10 +- src/SUMMARY.md | 2 +- src/introduction.md | 20 ++-- 4 files changed, 95 insertions(+), 48 deletions(-) rename src/{2_trait_objects.md => 2_waker_context.md} (96%) diff --git a/src/1_background_information.md b/src/1_background_information.md index 6d03fe0..3bfba29 100644 --- a/src/1_background_information.md +++ b/src/1_background_information.md @@ -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 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 Let's get some of the common roadblocks out of the way first. @@ -44,7 +116,7 @@ of non-blocking I/O, how these tasks are created or how they're run. A runtime, often just referred to as an `Executor`. -There are mainly two such runtimes in wide use in the community today +There are mainly two such runtimes in wide use in the community today [async_std][async_std] and [tokio][tokio]. Executors, accepts one or more asynchronous tasks (`Futures`) and takes @@ -55,43 +127,10 @@ waiting for I/O and resume them when they can make progress. and an `Reactor` (also referred to as a `Driver`) as if they're well defined 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 -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 -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 +## Bonus section 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 diff --git a/src/2_trait_objects.md b/src/2_waker_context.md similarity index 96% rename from src/2_trait_objects.md rename to src/2_waker_context.md index 61da56e..d976e28 100644 --- a/src/2_trait_objects.md +++ b/src/2_waker_context.md @@ -1,12 +1,14 @@ -# Trait objects and fat pointers +# Waker and Context > **Relevant for:** > > - Understanding how the Waker object is constructed -> - Getting a basic feel for "type erased" objects and what they are -> - Learning the basics of dynamic dispatch +> - Learning how the runtime know when a leaf-future can resume +> - 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` is how we implement a `Waker` . Creating a `Waker` involves creating a `vtable` diff --git a/src/SUMMARY.md b/src/SUMMARY.md index af9bbb7..4ff2152 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -3,7 +3,7 @@ [Introduction](./introduction.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) - [Pin](./4_pin.md) - [Futures - our main example](./6_future_example.md) diff --git a/src/introduction.md b/src/introduction.md index 35efc08..6154f2a 100644 --- a/src/introduction.md +++ b/src/introduction.md @@ -2,15 +2,21 @@ 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 -`Reactor`, a very simple `Executor` and our own `Futures`. +The goal is to get a better understanding of "async" in Rust by creating a toy +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 -deferring some of the details about what's special about futures in Rust we -try to tackle that head on first. We'll be as brief as possible, but as thorough -as needed. This way, most questions will be answered and explored up front. +We'll start off a bit differently than most other explanations. Instead of +deferring some of the details about what `Futures` are and how they're +implemented, we tackle that head on first. -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 of the most glaring omissions and shortcuts we took and create a slightly better