fixed build
This commit is contained in:
@@ -247,6 +247,21 @@ really do is to stub out a <code>Reactor</code>, and <code>Executor</code> and i
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -146,6 +146,171 @@
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#trait-objects-and-fat-pointers" id="trait-objects-and-fat-pointers">Trait objects and fat pointers</a></h1>
|
||||
<h2><a class="header" href="#trait-objects-and-dynamic-dispatch" id="trait-objects-and-dynamic-dispatch">Trait objects and dynamic dispatch</a></h2>
|
||||
<p>The single most confusing topic we encounter when implementing our own <code>Futures</code>
|
||||
is how we implement a <code>Waker</code> . Creating a <code>Waker</code> involves creating a <code>vtable</code>
|
||||
which allows using dynamic dispatch to call methods on a <em>type erased</em> trait
|
||||
object we construct our selves.</p>
|
||||
<p>If you want to know more about dynamic dispatch in Rust I can recommend this article:</p>
|
||||
<p>https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/</p>
|
||||
<p>Let's explain this a bit more in detail.</p>
|
||||
<h2><a class="header" href="#fat-pointers-in-rust" id="fat-pointers-in-rust">Fat pointers in Rust</a></h2>
|
||||
<p>Let's take a look at the size of some different pointer types in Rust. If we
|
||||
run the following code:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::mem::size_of;
|
||||
trait SomeTrait { }
|
||||
|
||||
fn main() {
|
||||
println!("Size of Box<i32>: {}", size_of::<Box<i32>>());
|
||||
println!("Size of &i32: {}", size_of::<&i32>());
|
||||
println!("Size of &Box<i32>: {}", size_of::<&Box<i32>>());
|
||||
println!("Size of Box<Trait>: {}", size_of::<Box<SomeTrait>>());
|
||||
println!("Size of &dyn Trait: {}", size_of::<&dyn SomeTrait>());
|
||||
println!("Size of &[i32]: {}", size_of::<&[i32]>());
|
||||
println!("Size of &[&dyn Trait]: {}", size_of::<&[&dyn SomeTrait]>());
|
||||
println!("Size of [i32; 10]: {}", size_of::<[i32; 10]>());
|
||||
println!("Size of [&dyn Trait; 10]: {}", size_of::<[&dyn SomeTrait; 10]>());
|
||||
}
|
||||
</code></pre></pre>
|
||||
<p>As you see from the output after running this, the sizes of the references varies.
|
||||
Most are 8 bytes (which is a pointer size on 64 bit systems), but some are 16
|
||||
bytes.</p>
|
||||
<p>The 16 byte sized pointers are called "fat pointers" since they carry more extra
|
||||
information.</p>
|
||||
<p><strong>In the case of <code>&[i32]</code> :</strong> </p>
|
||||
<ul>
|
||||
<li>The first 8 bytes is the actual pointer to the first element in the array</li>
|
||||
</ul>
|
||||
<p>(or part of an array the slice refers to)</p>
|
||||
<ul>
|
||||
<li>The second 8 bytes is the length of the slice.</li>
|
||||
</ul>
|
||||
<p>The one we'll concern ourselves about is the references to traits, or
|
||||
<em>trait objects</em> as they're called in Rust.</p>
|
||||
<p><code>&dyn SomeTrait</code> is an example of a <em>trait object</em> </p>
|
||||
<p>The layout for a pointer to a <em>trait object</em> looks like this: </p>
|
||||
<ul>
|
||||
<li>The first 8 bytes points to the <code>data</code> for the trait object</li>
|
||||
<li>The second 8 bytes points to the <code>vtable</code> for the trait object</li>
|
||||
</ul>
|
||||
<p>The reason for this is to allow us to refer to an object we know nothing about
|
||||
except that it implements the methods defined by our trait. To allow this we use
|
||||
dynamic dispatch.</p>
|
||||
<p>Let's explain this in code instead of words by implementing our own trait
|
||||
object from these parts:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr)
|
||||
trait Test {
|
||||
fn add(&self) -> i32;
|
||||
fn sub(&self) -> i32;
|
||||
fn mul(&self) -> i32;
|
||||
}
|
||||
|
||||
// This will represent our home brewn fat pointer to a trait object
|
||||
#[repr(C)]
|
||||
struct FatPointer<'a> {
|
||||
/// A reference is a pointer to an instantiated `Data` instance
|
||||
data: &'a mut Data,
|
||||
/// Since we need to pass in literal values like length and alignment it's
|
||||
/// easiest for us to convert pointers to usize-integers instead of the other way around.
|
||||
vtable: *const usize,
|
||||
}
|
||||
|
||||
// This is the data in our trait object. It's just two numbers we want to operate on.
|
||||
struct Data {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
// ====== function definitions ======
|
||||
fn add(s: &Data) -> i32 {
|
||||
s.a + s.b
|
||||
}
|
||||
fn sub(s: &Data) -> i32 {
|
||||
s.a - s.b
|
||||
}
|
||||
fn mul(s: &Data) -> i32 {
|
||||
s.a * s.b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut data = Data {a: 3, b: 2};
|
||||
// vtable is like special purpose array of pointer-length types with a fixed
|
||||
// format where the three first values has a special meaning like the
|
||||
// length of the array is encoded in the array itself as the second value.
|
||||
let vtable = vec![
|
||||
0, // pointer to `Drop` (which we're not implementing here)
|
||||
6, // lenght of vtable
|
||||
8, // alignment
|
||||
// we need to make sure we add these in the same order as defined in the Trait.
|
||||
// Try changing the order of add and sub and see what happens.
|
||||
add as usize, // function pointer
|
||||
sub as usize, // function pointer
|
||||
mul as usize, // function pointer
|
||||
];
|
||||
|
||||
let fat_pointer = FatPointer { data: &mut data, vtable: vtable.as_ptr()};
|
||||
let test = unsafe { std::mem::transmute::<FatPointer, &dyn Test>(fat_pointer) };
|
||||
|
||||
// And voalá, it's now a trait object we can call methods on
|
||||
println!("Add: 3 + 2 = {}", test.add());
|
||||
println!("Sub: 3 - 2 = {}", test.sub());
|
||||
println!("Mul: 3 * 2 = {}", test.mul());
|
||||
}
|
||||
|
||||
</code></pre></pre>
|
||||
<p>If you run this code by pressing the "play" button at the top you'll se it
|
||||
outputs just what we expect.</p>
|
||||
<p>This code example is editable so you can change it
|
||||
and run it to see what happens.</p>
|
||||
<p>The reason we go through this will be clear later on when we implement our own
|
||||
<code>Waker</code> we'll actually set up a <code>vtable</code> like we do here to and knowing what
|
||||
it is will make this much less mysterious.</p>
|
||||
<h2><a class="header" href="#reactorexecutor-pattern" id="reactorexecutor-pattern">Reactor/Executor pattern</a></h2>
|
||||
<p>If you don't know what this is, you should take a few minutes and read about
|
||||
it. You will encounter the term <code>Reactor</code> and <code>Executor</code> a lot when working
|
||||
with async code in Rust.</p>
|
||||
<p>I have written a quick introduction explaining this pattern before which you
|
||||
can take a look at here:</p>
|
||||
<p><a href="https://cfsamsonbooks.gitbook.io/epoll-kqueue-iocp-explained/appendix-1/reactor-executor-pattern"><img src="./assets/reactorexecutor.png" alt="homepage" /></a></p>
|
||||
<div style="text-align:center">
|
||||
<a href="https://cfsamsonbooks.gitbook.io/epoll-kqueue-iocp-explained/appendix-1/reactor-executor-pattern">Epoll, Kqueue and IOCP Explained - The Reactor-Executor Pattern</a>
|
||||
</div>
|
||||
<p>I'll re-iterate the most important parts here.</p>
|
||||
<p>This pattern consists of at least 2 parts:</p>
|
||||
<ol>
|
||||
<li>A reactor
|
||||
<ul>
|
||||
<li>handles some kind of event queue</li>
|
||||
<li>has the responsibility of respoonding to events</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>An executor
|
||||
<ul>
|
||||
<li>Often has a scheduler</li>
|
||||
<li>Holds a set of suspended tasks, and has the responsibility of resuming
|
||||
them when an event has occurred</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>The concept of a task
|
||||
<ul>
|
||||
<li>A set of operations that can be stopped half way and resumed later on</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
<p>This is a pattern not only used in Rust, but it's very popular in Rust due to
|
||||
how well it separates concerns between handling and scheduling tasks, and queing
|
||||
and responding to I/O events.</p>
|
||||
<p>The only thing Rust as a language defines is the <em>task</em>. In Rust we call an
|
||||
incorruptible task a <code>Future</code>. Futures has a well defined interface, which means
|
||||
they can be used across the entire ecosystem.</p>
|
||||
<p>In addition, Rust provides a way for the Reactor and Executor to communicate
|
||||
through the <code>Waker</code>. We'll get to know these in the following chapters.</p>
|
||||
<p>Providing these pieces let's Rust take care a lot of the ergonomic "friction"
|
||||
programmers meet when faced with async code, and still not dictate any
|
||||
preferred runtime to actually do the scheduling and I/O queues.</p>
|
||||
<p>It's important to know that Rust doesn't provide a runtime, so you have to choose
|
||||
one. <a href="https://github.com/async-rs/async-std">async std</a> and <a href="https://github.com/tokio-rs/tokio">tokio</a> are two popular ones.</p>
|
||||
<p>With that out of the way, let's move on to our main example.</p>
|
||||
|
||||
</main>
|
||||
|
||||
@@ -185,6 +350,21 @@
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -146,6 +146,179 @@
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#generators-and-pin" id="generators-and-pin">Generators and Pin</a></h1>
|
||||
<p>So the second difficult part that there seems to be a lot of questions about
|
||||
is Generators and the <code>Pin</code> type.</p>
|
||||
<h2><a class="header" href="#generators" id="generators">Generators</a></h2>
|
||||
<pre><code>**Relevant for:**
|
||||
|
||||
- Understanding how the async/await syntax works
|
||||
- Why we need `Pin`
|
||||
- Why Rusts async model is extremely efficient
|
||||
</code></pre>
|
||||
<p>The motivation for <code>Generators</code> can be found in <a href="https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md">RFC#2033</a>. It's very
|
||||
well written and I can recommend reading through it (it talks as much about
|
||||
async/await as it does about generators).</p>
|
||||
<p>Basically, there were three main options that were discussed when Rust was
|
||||
desiging how the language would handle concurrency:</p>
|
||||
<ol>
|
||||
<li>Stackful coroutines, better known as green threads.</li>
|
||||
<li>Using combinators.</li>
|
||||
<li>Stackless coroutines, better known as generators.</li>
|
||||
</ol>
|
||||
<h3><a class="header" href="#stackful-coroutinesgreen-threads" id="stackful-coroutinesgreen-threads">Stackful coroutines/green threads</a></h3>
|
||||
<p>I've written about green threads before. Go check out
|
||||
<a href="https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/">Green Threads Explained in 200 lines of Rust</a> if you're interested.</p>
|
||||
<p>Green threads uses the same mechanisms as an OS does by creating a thread for
|
||||
each task, setting up a stack and forcing the CPU to save it's state and jump
|
||||
from one task(thread) to another. We yield control to the scheduler which then
|
||||
continues running a different task.</p>
|
||||
<p>Rust had green threads once, but they were removed before it hit 1.0. The state
|
||||
of execution is stored in each stack so in such a solution there would be no need
|
||||
for <code>async</code>, <code>await</code>, <code>Futures</code> or <code>Pin</code>. All this would be implementation
|
||||
details for the library.</p>
|
||||
<h3><a class="header" href="#combinators" id="combinators">Combinators</a></h3>
|
||||
<p><code>Futures 1.0</code> used combinators. If you've worked with <code>Promises</code> in JavaScript,
|
||||
you already know combinators. In Rust they look like this:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
let future = Connection::connect(conn_str).and_then(|conn| {
|
||||
conn.query("somerequest").map(|row|{
|
||||
SomeStruct::from(row)
|
||||
}).collect::<Vec<SomeStruct>>()
|
||||
});
|
||||
|
||||
let rows: Result<Vec<SomeStruct>, SomeLibraryError> = block_on(future).unwrap();
|
||||
|
||||
#}</code></pre></pre>
|
||||
<p>While an effective solution there are mainly two downsides I'll focus on:</p>
|
||||
<ol>
|
||||
<li>The error messages produced could be extremely long and arcane</li>
|
||||
<li>Not optimal memory usage</li>
|
||||
</ol>
|
||||
<p>The reason for the higher than optimal memory usage is that this is basically
|
||||
a callback-based approach, where each closure stores all the data it needs
|
||||
for computation. This means that as we chain these, the memory required to store
|
||||
the needed state increases with each added step.</p>
|
||||
<h3><a class="header" href="#stackless-coroutinesgenerators" id="stackless-coroutinesgenerators">Stackless coroutines/generators</a></h3>
|
||||
<p>This is the model used in Async/Await today. It has two advantages:</p>
|
||||
<ol>
|
||||
<li>It's easy to convert normal Rust code to a stackless corotuine using using
|
||||
async/await as keywords (it can even be done using a macro).</li>
|
||||
<li>It uses memory very efficiently</li>
|
||||
</ol>
|
||||
<p>The second point is in contrast to <code>Futures 1.0</code> (well, both are efficient in
|
||||
practice but thats beside the point). Generators are implemented as state
|
||||
machines. The memory footprint of a chain of computations is only defined by
|
||||
the largest footprint any single step requires. That means that adding steps to
|
||||
a chain of computations might not require any added memory at all.</p>
|
||||
<h2><a class="header" href="#how-generators-work" id="how-generators-work">How generators work</a></h2>
|
||||
<p>In Nightly Rust today you can use the <code>yield</code> keyword. Basically using this
|
||||
keyword in a closure, converts it to a generator. A closure looking like this
|
||||
(I'm going to use the terminology that's currently in Rust):</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
let a = 4;
|
||||
let b = move || {
|
||||
println!("Hello");
|
||||
yield a * 2;
|
||||
println!("world!");
|
||||
};
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
#}</code></pre></pre>
|
||||
<p>Early on, before there was a consensus about the design of <code>Pin</code>, this
|
||||
compiled to something looking similar to this:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">fn main() {
|
||||
let mut gen = GeneratorA::start(4);
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
}
|
||||
|
||||
// If you've ever wondered why the parameters are called Y and R the naming from
|
||||
// the original rfc most likely holds the answer
|
||||
enum GeneratorState<Y, R> {
|
||||
// originally called `CoResult`
|
||||
Yielded(Y), // originally called `Yield(Y)`
|
||||
Complete(R), // originally called `Return(R)`
|
||||
}
|
||||
|
||||
trait Generator {
|
||||
type Yield;
|
||||
type Return;
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
}
|
||||
|
||||
enum GeneratorA {
|
||||
Enter(i32),
|
||||
Yield1(i32),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl GeneratorA {
|
||||
fn start(a1: i32) -> Self {
|
||||
GeneratorA::Enter(a1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator for GeneratorA {
|
||||
type Yield = i32;
|
||||
type Return = ();
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
// lets us get ownership over current state
|
||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||
GeneratorA::Enter(a1) => {
|
||||
|
||||
/*|---code before yield1---|*/
|
||||
/*|*/ println!("Hello"); /*|*/
|
||||
/*|*/ let a = a1 * 2; /*|*/
|
||||
/*|------------------------|*/
|
||||
|
||||
*self = GeneratorA::Yield1(a);
|
||||
GeneratorState::Yielded(a)
|
||||
}
|
||||
GeneratorA::Yield1(_) => {
|
||||
|
||||
/*|----code after yield1----|*/
|
||||
/*|*/ println!("world!"); /*|*/
|
||||
/*|-------------------------|*/
|
||||
|
||||
*self = GeneratorA::Exit;
|
||||
GeneratorState::Complete(())
|
||||
}
|
||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre></pre>
|
||||
<blockquote>
|
||||
<p>The <code>yield</code> keyword was discussed first in <a href="https://github.com/rust-lang/rfcs/pull/1823">RFC#1823</a> and in <a href="https://github.com/rust-lang/rfcs/pull/1832">RFC#1832</a>.</p>
|
||||
</blockquote>
|
||||
<pre><code>|| {
|
||||
let arr: Vec<i32> = (0..a).enumerate().map((i,_) i).collect();
|
||||
for n in arr {
|
||||
yield n;
|
||||
}
|
||||
println!("The sum is: {}", arr.iter().sum());
|
||||
}
|
||||
|| {
|
||||
yield a * 2;
|
||||
println!("Hello!");
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
</main>
|
||||
|
||||
@@ -185,6 +358,21 @@
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -159,171 +159,6 @@ try to give a high level overview that will make it easier to learn Rusts
|
||||
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/5_strategies_for_handling_io.html">Async Basics - Strategies for handling I/O</a></li>
|
||||
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/6_epoll_kqueue_iocp.html">Async Basics - Epoll, Kqueue and IOCP</a></li>
|
||||
</ul>
|
||||
<h2><a class="header" href="#trait-objects-and-dynamic-dispatch" id="trait-objects-and-dynamic-dispatch">Trait objects and dynamic dispatch</a></h2>
|
||||
<p>The single most confusing topic we encounter when implementing our own <code>Futures</code>
|
||||
is how we implement a <code>Waker</code> . Creating a <code>Waker</code> involves creating a <code>vtable</code>
|
||||
which allows using dynamic dispatch to call methods on a <em>type erased</em> trait
|
||||
object we construct our selves.</p>
|
||||
<p>If you want to know more about dynamic dispatch in Rust I can recommend this article:</p>
|
||||
<p>https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/</p>
|
||||
<p>Let's explain this a bit more in detail.</p>
|
||||
<h2><a class="header" href="#fat-pointers-in-rust" id="fat-pointers-in-rust">Fat pointers in Rust</a></h2>
|
||||
<p>Let's take a look at the size of some different pointer types in Rust. If we
|
||||
run the following code:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::mem::size_of;
|
||||
trait SomeTrait { }
|
||||
|
||||
fn main() {
|
||||
println!("Size of Box<i32>: {}", size_of::<Box<i32>>());
|
||||
println!("Size of &i32: {}", size_of::<&i32>());
|
||||
println!("Size of &Box<i32>: {}", size_of::<&Box<i32>>());
|
||||
println!("Size of Box<Trait>: {}", size_of::<Box<SomeTrait>>());
|
||||
println!("Size of &dyn Trait: {}", size_of::<&dyn SomeTrait>());
|
||||
println!("Size of &[i32]: {}", size_of::<&[i32]>());
|
||||
println!("Size of &[&dyn Trait]: {}", size_of::<&[&dyn SomeTrait]>());
|
||||
println!("Size of [i32; 10]: {}", size_of::<[i32; 10]>());
|
||||
println!("Size of [&dyn Trait; 10]: {}", size_of::<[&dyn SomeTrait; 10]>());
|
||||
}
|
||||
</code></pre></pre>
|
||||
<p>As you see from the output after running this, the sizes of the references varies.
|
||||
Most are 8 bytes (which is a pointer size on 64 bit systems), but some are 16
|
||||
bytes.</p>
|
||||
<p>The 16 byte sized pointers are called "fat pointers" since they carry more extra
|
||||
information.</p>
|
||||
<p><strong>In the case of <code>&[i32]</code> :</strong> </p>
|
||||
<ul>
|
||||
<li>The first 8 bytes is the actual pointer to the first element in the array</li>
|
||||
</ul>
|
||||
<p>(or part of an array the slice refers to)</p>
|
||||
<ul>
|
||||
<li>The second 8 bytes is the length of the slice.</li>
|
||||
</ul>
|
||||
<p>The one we'll concern ourselves about is the references to traits, or
|
||||
<em>trait objects</em> as they're called in Rust.</p>
|
||||
<p><code>&dyn SomeTrait</code> is an example of a <em>trait object</em> </p>
|
||||
<p>The layout for a pointer to a <em>trait object</em> looks like this: </p>
|
||||
<ul>
|
||||
<li>The first 8 bytes points to the <code>data</code> for the trait object</li>
|
||||
<li>The second 8 bytes points to the <code>vtable</code> for the trait object</li>
|
||||
</ul>
|
||||
<p>The reason for this is to allow us to refer to an object we know nothing about
|
||||
except that it implements the methods defined by our trait. To allow this we use
|
||||
dynamic dispatch.</p>
|
||||
<p>Let's explain this in code instead of words by implementing our own trait
|
||||
object from these parts:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr)
|
||||
trait Test {
|
||||
fn add(&self) -> i32;
|
||||
fn sub(&self) -> i32;
|
||||
fn mul(&self) -> i32;
|
||||
}
|
||||
|
||||
// This will represent our home brewn fat pointer to a trait object
|
||||
#[repr(C)]
|
||||
struct FatPointer<'a> {
|
||||
/// A reference is a pointer to an instantiated `Data` instance
|
||||
data: &'a mut Data,
|
||||
/// Since we need to pass in literal values like length and alignment it's
|
||||
/// easiest for us to convert pointers to usize-integers instead of the other way around.
|
||||
vtable: *const usize,
|
||||
}
|
||||
|
||||
// This is the data in our trait object. It's just two numbers we want to operate on.
|
||||
struct Data {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
// ====== function definitions ======
|
||||
fn add(s: &Data) -> i32 {
|
||||
s.a + s.b
|
||||
}
|
||||
fn sub(s: &Data) -> i32 {
|
||||
s.a - s.b
|
||||
}
|
||||
fn mul(s: &Data) -> i32 {
|
||||
s.a * s.b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut data = Data {a: 3, b: 2};
|
||||
// vtable is like special purpose array of pointer-length types with a fixed
|
||||
// format where the three first values has a special meaning like the
|
||||
// length of the array is encoded in the array itself as the second value.
|
||||
let vtable = vec![
|
||||
0, // pointer to `Drop` (which we're not implementing here)
|
||||
6, // lenght of vtable
|
||||
8, // alignment
|
||||
// we need to make sure we add these in the same order as defined in the Trait.
|
||||
// Try changing the order of add and sub and see what happens.
|
||||
add as usize, // function pointer
|
||||
sub as usize, // function pointer
|
||||
mul as usize, // function pointer
|
||||
];
|
||||
|
||||
let fat_pointer = FatPointer { data: &mut data, vtable: vtable.as_ptr()};
|
||||
let test = unsafe { std::mem::transmute::<FatPointer, &dyn Test>(fat_pointer) };
|
||||
|
||||
// And voalá, it's now a trait object we can call methods on
|
||||
println!("Add: 3 + 2 = {}", test.add());
|
||||
println!("Sub: 3 - 2 = {}", test.sub());
|
||||
println!("Mul: 3 * 2 = {}", test.mul());
|
||||
}
|
||||
|
||||
</code></pre></pre>
|
||||
<p>If you run this code by pressing the "play" button at the top you'll se it
|
||||
outputs just what we expect.</p>
|
||||
<p>This code example is editable so you can change it
|
||||
and run it to see what happens.</p>
|
||||
<p>The reason we go through this will be clear later on when we implement our own
|
||||
<code>Waker</code> we'll actually set up a <code>vtable</code> like we do here to and knowing what
|
||||
it is will make this much less mysterious.</p>
|
||||
<h2><a class="header" href="#reactorexecutor-pattern" id="reactorexecutor-pattern">Reactor/Executor pattern</a></h2>
|
||||
<p>If you don't know what this is, you should take a few minutes and read about
|
||||
it. You will encounter the term <code>Reactor</code> and <code>Executor</code> a lot when working
|
||||
with async code in Rust.</p>
|
||||
<p>I have written a quick introduction explaining this pattern before which you
|
||||
can take a look at here:</p>
|
||||
<p><a href="https://cfsamsonbooks.gitbook.io/epoll-kqueue-iocp-explained/appendix-1/reactor-executor-pattern"><img src="./assets/reactorexecutor.png" alt="homepage" /></a></p>
|
||||
<div style="text-align:center">
|
||||
<a href="https://cfsamsonbooks.gitbook.io/epoll-kqueue-iocp-explained/appendix-1/reactor-executor-pattern">Epoll, Kqueue and IOCP Explained - The Reactor-Executor Pattern</a>
|
||||
</div>
|
||||
<p>I'll re-iterate the most important parts here.</p>
|
||||
<p>This pattern consists of at least 2 parts:</p>
|
||||
<ol>
|
||||
<li>A reactor
|
||||
<ul>
|
||||
<li>handles some kind of event queue</li>
|
||||
<li>has the responsibility of respoonding to events</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>An executor
|
||||
<ul>
|
||||
<li>Often has a scheduler</li>
|
||||
<li>Holds a set of suspended tasks, and has the responsibility of resuming
|
||||
them when an event has occurred</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>The concept of a task
|
||||
<ul>
|
||||
<li>A set of operations that can be stopped half way and resumed later on</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
<p>This is a pattern not only used in Rust, but it's very popular in Rust due to
|
||||
how well it separates concerns between handling and scheduling tasks, and queing
|
||||
and responding to I/O events.</p>
|
||||
<p>The only thing Rust as a language defines is the <em>task</em>. In Rust we call an
|
||||
incorruptible task a <code>Future</code>. Futures has a well defined interface, which means
|
||||
they can be used across the entire ecosystem.</p>
|
||||
<p>In addition, Rust provides a way for the Reactor and Executor to communicate
|
||||
through the <code>Waker</code>. We'll get to know these in the following chapters.</p>
|
||||
<p>Providing these pieces let's Rust take care a lot of the ergonomic "friction"
|
||||
programmers meet when faced with async code, and still not dictate any
|
||||
preferred runtime to actually do the scheduling and I/O queues.</p>
|
||||
<p>It's important to know that Rust doesn't provide a runtime, so you have to choose
|
||||
one. <a href="https://github.com/async-rs/async-std">async std</a> and <a href="https://github.com/tokio-rs/tokio">tokio</a> are two popular ones.</p>
|
||||
<p>With that out of the way, let's move on to our main example.</p>
|
||||
|
||||
</main>
|
||||
|
||||
@@ -363,6 +198,21 @@ one. <a href="https://github.com/async-rs/async-std">async std</a> and <a href="
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -445,6 +445,21 @@ fn main() {
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -185,6 +185,21 @@
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -185,6 +185,21 @@
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -185,6 +185,21 @@
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -177,6 +177,21 @@
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -239,6 +239,21 @@ really do is to stub out a <code>Reactor</code>, and <code>Executor</code> and i
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
190
book/print.html
190
book/print.html
@@ -232,6 +232,7 @@ try to give a high level overview that will make it easier to learn Rusts
|
||||
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/5_strategies_for_handling_io.html">Async Basics - Strategies for handling I/O</a></li>
|
||||
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/6_epoll_kqueue_iocp.html">Async Basics - Epoll, Kqueue and IOCP</a></li>
|
||||
</ul>
|
||||
<h1><a class="header" href="#trait-objects-and-fat-pointers" id="trait-objects-and-fat-pointers">Trait objects and fat pointers</a></h1>
|
||||
<h2><a class="header" href="#trait-objects-and-dynamic-dispatch" id="trait-objects-and-dynamic-dispatch">Trait objects and dynamic dispatch</a></h2>
|
||||
<p>The single most confusing topic we encounter when implementing our own <code>Futures</code>
|
||||
is how we implement a <code>Waker</code> . Creating a <code>Waker</code> involves creating a <code>vtable</code>
|
||||
@@ -397,8 +398,180 @@ preferred runtime to actually do the scheduling and I/O queues.</p>
|
||||
<p>It's important to know that Rust doesn't provide a runtime, so you have to choose
|
||||
one. <a href="https://github.com/async-rs/async-std">async std</a> and <a href="https://github.com/tokio-rs/tokio">tokio</a> are two popular ones.</p>
|
||||
<p>With that out of the way, let's move on to our main example.</p>
|
||||
<h1><a class="header" href="#trait-objects-and-fat-pointers" id="trait-objects-and-fat-pointers">Trait objects and fat pointers</a></h1>
|
||||
<h1><a class="header" href="#generators-and-pin" id="generators-and-pin">Generators and Pin</a></h1>
|
||||
<p>So the second difficult part that there seems to be a lot of questions about
|
||||
is Generators and the <code>Pin</code> type.</p>
|
||||
<h2><a class="header" href="#generators" id="generators">Generators</a></h2>
|
||||
<pre><code>**Relevant for:**
|
||||
|
||||
- Understanding how the async/await syntax works
|
||||
- Why we need `Pin`
|
||||
- Why Rusts async model is extremely efficient
|
||||
</code></pre>
|
||||
<p>The motivation for <code>Generators</code> can be found in <a href="https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md">RFC#2033</a>. It's very
|
||||
well written and I can recommend reading through it (it talks as much about
|
||||
async/await as it does about generators).</p>
|
||||
<p>Basically, there were three main options that were discussed when Rust was
|
||||
desiging how the language would handle concurrency:</p>
|
||||
<ol>
|
||||
<li>Stackful coroutines, better known as green threads.</li>
|
||||
<li>Using combinators.</li>
|
||||
<li>Stackless coroutines, better known as generators.</li>
|
||||
</ol>
|
||||
<h3><a class="header" href="#stackful-coroutinesgreen-threads" id="stackful-coroutinesgreen-threads">Stackful coroutines/green threads</a></h3>
|
||||
<p>I've written about green threads before. Go check out
|
||||
<a href="https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/">Green Threads Explained in 200 lines of Rust</a> if you're interested.</p>
|
||||
<p>Green threads uses the same mechanisms as an OS does by creating a thread for
|
||||
each task, setting up a stack and forcing the CPU to save it's state and jump
|
||||
from one task(thread) to another. We yield control to the scheduler which then
|
||||
continues running a different task.</p>
|
||||
<p>Rust had green threads once, but they were removed before it hit 1.0. The state
|
||||
of execution is stored in each stack so in such a solution there would be no need
|
||||
for <code>async</code>, <code>await</code>, <code>Futures</code> or <code>Pin</code>. All this would be implementation
|
||||
details for the library.</p>
|
||||
<h3><a class="header" href="#combinators" id="combinators">Combinators</a></h3>
|
||||
<p><code>Futures 1.0</code> used combinators. If you've worked with <code>Promises</code> in JavaScript,
|
||||
you already know combinators. In Rust they look like this:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
let future = Connection::connect(conn_str).and_then(|conn| {
|
||||
conn.query("somerequest").map(|row|{
|
||||
SomeStruct::from(row)
|
||||
}).collect::<Vec<SomeStruct>>()
|
||||
});
|
||||
|
||||
let rows: Result<Vec<SomeStruct>, SomeLibraryError> = block_on(future).unwrap();
|
||||
|
||||
#}</code></pre></pre>
|
||||
<p>While an effective solution there are mainly two downsides I'll focus on:</p>
|
||||
<ol>
|
||||
<li>The error messages produced could be extremely long and arcane</li>
|
||||
<li>Not optimal memory usage</li>
|
||||
</ol>
|
||||
<p>The reason for the higher than optimal memory usage is that this is basically
|
||||
a callback-based approach, where each closure stores all the data it needs
|
||||
for computation. This means that as we chain these, the memory required to store
|
||||
the needed state increases with each added step.</p>
|
||||
<h3><a class="header" href="#stackless-coroutinesgenerators" id="stackless-coroutinesgenerators">Stackless coroutines/generators</a></h3>
|
||||
<p>This is the model used in Async/Await today. It has two advantages:</p>
|
||||
<ol>
|
||||
<li>It's easy to convert normal Rust code to a stackless corotuine using using
|
||||
async/await as keywords (it can even be done using a macro).</li>
|
||||
<li>It uses memory very efficiently</li>
|
||||
</ol>
|
||||
<p>The second point is in contrast to <code>Futures 1.0</code> (well, both are efficient in
|
||||
practice but thats beside the point). Generators are implemented as state
|
||||
machines. The memory footprint of a chain of computations is only defined by
|
||||
the largest footprint any single step requires. That means that adding steps to
|
||||
a chain of computations might not require any added memory at all.</p>
|
||||
<h2><a class="header" href="#how-generators-work" id="how-generators-work">How generators work</a></h2>
|
||||
<p>In Nightly Rust today you can use the <code>yield</code> keyword. Basically using this
|
||||
keyword in a closure, converts it to a generator. A closure looking like this
|
||||
(I'm going to use the terminology that's currently in Rust):</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
let a = 4;
|
||||
let b = move || {
|
||||
println!("Hello");
|
||||
yield a * 2;
|
||||
println!("world!");
|
||||
};
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
#}</code></pre></pre>
|
||||
<p>Early on, before there was a consensus about the design of <code>Pin</code>, this
|
||||
compiled to something looking similar to this:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">fn main() {
|
||||
let mut gen = GeneratorA::start(4);
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
}
|
||||
|
||||
// If you've ever wondered why the parameters are called Y and R the naming from
|
||||
// the original rfc most likely holds the answer
|
||||
enum GeneratorState<Y, R> {
|
||||
// originally called `CoResult`
|
||||
Yielded(Y), // originally called `Yield(Y)`
|
||||
Complete(R), // originally called `Return(R)`
|
||||
}
|
||||
|
||||
trait Generator {
|
||||
type Yield;
|
||||
type Return;
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
}
|
||||
|
||||
enum GeneratorA {
|
||||
Enter(i32),
|
||||
Yield1(i32),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl GeneratorA {
|
||||
fn start(a1: i32) -> Self {
|
||||
GeneratorA::Enter(a1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator for GeneratorA {
|
||||
type Yield = i32;
|
||||
type Return = ();
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
// lets us get ownership over current state
|
||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||
GeneratorA::Enter(a1) => {
|
||||
|
||||
/*|---code before yield1---|*/
|
||||
/*|*/ println!("Hello"); /*|*/
|
||||
/*|*/ let a = a1 * 2; /*|*/
|
||||
/*|------------------------|*/
|
||||
|
||||
*self = GeneratorA::Yield1(a);
|
||||
GeneratorState::Yielded(a)
|
||||
}
|
||||
GeneratorA::Yield1(_) => {
|
||||
|
||||
/*|----code after yield1----|*/
|
||||
/*|*/ println!("world!"); /*|*/
|
||||
/*|-------------------------|*/
|
||||
|
||||
*self = GeneratorA::Exit;
|
||||
GeneratorState::Complete(())
|
||||
}
|
||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre></pre>
|
||||
<blockquote>
|
||||
<p>The <code>yield</code> keyword was discussed first in <a href="https://github.com/rust-lang/rfcs/pull/1823">RFC#1823</a> and in <a href="https://github.com/rust-lang/rfcs/pull/1832">RFC#1832</a>.</p>
|
||||
</blockquote>
|
||||
<pre><code>|| {
|
||||
let arr: Vec<i32> = (0..a).enumerate().map((i,_) i).collect();
|
||||
for n in arr {
|
||||
yield n;
|
||||
}
|
||||
println!("The sum is: {}", arr.iter().sum());
|
||||
}
|
||||
|| {
|
||||
yield a * 2;
|
||||
println!("Hello!");
|
||||
}
|
||||
</code></pre>
|
||||
<h1><a class="header" href="#naive-example" id="naive-example">Naive example</a></h1>
|
||||
<pre><pre class="playpen"><code class="language-rust">use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
@@ -687,6 +860,21 @@ fn main() {
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("ws://localhost:3001");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload(true); // force reload from server (not from cache)
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,13 +5,12 @@ is Generators and the `Pin` type.
|
||||
|
||||
## Generators
|
||||
|
||||
```
|
||||
**Relevant for:**
|
||||
|
||||
- Understanding how the async/await syntax works
|
||||
- Why we need `Pin`
|
||||
- Why Rusts async model is extremely efficient
|
||||
```
|
||||
>**Relevant for:**
|
||||
|
||||
>- Understanding how the async/await syntax works
|
||||
>- Why we need `Pin`
|
||||
>- Why Rusts async model is extremely efficient
|
||||
|
||||
The motivation for `Generators` can be found in [RFC#2033][rfc2033]. It's very
|
||||
well written and I can recommend reading through it (it talks as much about
|
||||
@@ -44,7 +43,7 @@ details for the library.
|
||||
`Futures 1.0` used combinators. If you've worked with `Promises` in JavaScript,
|
||||
you already know combinators. In Rust they look like this:
|
||||
|
||||
```rust
|
||||
```rust,noplaypen,ignore
|
||||
let future = Connection::connect(conn_str).and_then(|conn| {
|
||||
conn.query("somerequest").map(|row|{
|
||||
SomeStruct::from(row)
|
||||
@@ -84,51 +83,113 @@ In Nightly Rust today you can use the `yield` keyword. Basically using this
|
||||
keyword in a closure, converts it to a generator. A closure looking like this
|
||||
(I'm going to use the terminology that's currently in Rust):
|
||||
|
||||
```rust,noplaypen,ignore
|
||||
let a = 4;
|
||||
let b = move || {
|
||||
println!("Hello");
|
||||
yield a * 2;
|
||||
println!("world!");
|
||||
};
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
```
|
||||
|
||||
Early on, before there was a consensus about the design of `Pin`, this
|
||||
compiled to something looking similar to this:
|
||||
|
||||
```rust
|
||||
|a: i32| {
|
||||
fn main() {
|
||||
let mut gen = GeneratorA::start(4);
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
}
|
||||
|
||||
// If you've ever wondered why the parameters are called Y and R the naming from
|
||||
// the original rfc most likely holds the answer
|
||||
enum GeneratorState<Y, R> {
|
||||
// originally called `CoResult`
|
||||
Yielded(Y), // originally called `Yield(Y)`
|
||||
Complete(R), // originally called `Return(R)`
|
||||
}
|
||||
|
||||
trait Generator {
|
||||
type Yield;
|
||||
type Return;
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
}
|
||||
|
||||
enum GeneratorA {
|
||||
Enter(i32),
|
||||
Yield1(i32),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl GeneratorA {
|
||||
fn start(a1: i32) -> Self {
|
||||
GeneratorA::Enter(a1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator for GeneratorA {
|
||||
type Yield = i32;
|
||||
type Return = ();
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
// lets us get ownership over current state
|
||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||
GeneratorA::Enter(a1) => {
|
||||
|
||||
/*|---code before yield1---|*/
|
||||
/*|*/ println!("Hello"); /*|*/
|
||||
/*|*/ let a = a1 * 2; /*|*/
|
||||
/*|------------------------|*/
|
||||
|
||||
*self = GeneratorA::Yield1(a);
|
||||
GeneratorState::Yielded(a)
|
||||
}
|
||||
GeneratorA::Yield1(_) => {
|
||||
|
||||
/*|----code after yield1----|*/
|
||||
/*|*/ println!("world!"); /*|*/
|
||||
/*|-------------------------|*/
|
||||
|
||||
*self = GeneratorA::Exit;
|
||||
GeneratorState::Complete(())
|
||||
}
|
||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
>The `yield` keyword was discussed first in [RFC#1823][rfc1823] and in [RFC#1832][rfc1832].
|
||||
|
||||
|
||||
```ignore
|
||||
|| {
|
||||
let arr: Vec<i32> = (0..a).enumerate().map((i,_) i).collect();
|
||||
for n in arr {
|
||||
yield n;
|
||||
}
|
||||
println!("The sum is: {}", arr.iter().sum());
|
||||
}
|
||||
|a: i32| {
|
||||
|| {
|
||||
yield a * 2;
|
||||
println!("Hello!");
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Compiles into something looking more like this:
|
||||
|
||||
```rust
|
||||
// If you've ever wondered why the parameters are called Y and R the naming from
|
||||
// the original rfc most likely holds the answer
|
||||
enum GeneratorState<Y, R> { // originally called `CoResult`
|
||||
Yielded(Y), // originally called `Yield(Y)`
|
||||
Complete(R), // originally called `Return(R)`
|
||||
}
|
||||
|
||||
struct GeneratorA {
|
||||
state: GeneratorState<i32, ()>,
|
||||
}
|
||||
|
||||
impl FnMut<()> for GeneratorA {
|
||||
type Output = GeneratorState<i32, ()>;
|
||||
fn call_mut(&mut self) -> Self::Output {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|a: i32| {
|
||||
CoResult {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
>The `yield` keyword was discussed first in [RFC#1823][rfc1823] and in [RFC#1832][rfc1832].
|
||||
|
||||
[rfc2033]: https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md
|
||||
[greenthreads]: https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/
|
||||
|
||||
Reference in New Issue
Block a user