merged with latest changes and made some additional corrections
This commit is contained in:
@@ -157,7 +157,7 @@
|
||||
<li>See first hand why we need <code>Pin</code></li>
|
||||
<li>Understand what makes Rusts async model very memory efficient</li>
|
||||
</ul>
|
||||
<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
|
||||
<p>The motivation for <code>Generator</code>s 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>
|
||||
</blockquote>
|
||||
@@ -182,7 +182,7 @@ handle concurrency:</p>
|
||||
so we won't repeat that here. We'll concentrate on the variants of stackless
|
||||
coroutines which Rust uses today.</p>
|
||||
<h3><a class="header" href="#combinators" id="combinators">Combinators</a></h3>
|
||||
<p><code>Futures 0.1</code> used combinators. If you've worked with <code>Promises</code> in JavaScript,
|
||||
<p><code>Futures 0.1</code> used combinators. If you've worked with Promises in JavaScript,
|
||||
you already know combinators. In Rust they look like this:</p>
|
||||
<pre><code class="language-rust noplaypen ignore">let future = Connection::connect(conn_str).and_then(|conn| {
|
||||
conn.query("somerequest").map(|row|{
|
||||
@@ -227,7 +227,7 @@ async/await as keywords (it can even be done using a macro).</li>
|
||||
</code></pre>
|
||||
<p>Async in Rust is implemented using Generators. So to understand how async really
|
||||
works we need to understand generators first. Generators in Rust are implemented
|
||||
as state machines. </p>
|
||||
as state machines.</p>
|
||||
<p>The memory footprint of a chain of computations is defined by <em>the largest footprint
|
||||
that a single step requires</em>.</p>
|
||||
<p>That means that adding steps to a chain of computations might not require any
|
||||
@@ -330,7 +330,7 @@ impl Generator for GeneratorA {
|
||||
</blockquote>
|
||||
<p>Now that you know that the <code>yield</code> keyword in reality rewrites your code to become a state machine,
|
||||
you'll also know the basics of how <code>await</code> works. It's very similar.</p>
|
||||
<p>Now, there are some limitations in our naive state machine above. What happens when you have a
|
||||
<p>Now, there are some limitations in our naive state machine above. What happens when you have a
|
||||
<code>borrow</code> across a <code>yield</code> point?</p>
|
||||
<p>We could forbid that, but <strong>one of the major design goals for the async/await syntax has been
|
||||
to allow this</strong>. These kinds of borrows were not possible using <code>Futures 0.1</code> so we can't let this
|
||||
@@ -363,10 +363,10 @@ Just keep this in the back of your head as we move forward.</p>
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
# enum GeneratorState<Y, R> {
|
||||
# Yielded(Y),
|
||||
# Yielded(Y),
|
||||
# Complete(R),
|
||||
# }
|
||||
#
|
||||
#
|
||||
# trait Generator {
|
||||
# type Yield;
|
||||
# type Return;
|
||||
@@ -426,8 +426,8 @@ into itself.</p>
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
enum GeneratorState<Y, R> {
|
||||
Yielded(Y),
|
||||
Complete(R),
|
||||
Yielded(Y),
|
||||
Complete(R),
|
||||
}
|
||||
|
||||
trait Generator {
|
||||
@@ -460,12 +460,12 @@ impl Generator for GeneratorA {
|
||||
let borrowed = &to_borrow;
|
||||
let res = borrowed.len();
|
||||
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
||||
|
||||
|
||||
// NB! And we set the pointer to reference the to_borrow string here
|
||||
if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
|
||||
*borrowed = to_borrow;
|
||||
}
|
||||
|
||||
|
||||
GeneratorState::Yielded(res)
|
||||
}
|
||||
|
||||
@@ -507,16 +507,16 @@ does what we'd expect. But there is still one huge problem with this:</p>
|
||||
};
|
||||
}
|
||||
# enum GeneratorState<Y, R> {
|
||||
# Yielded(Y),
|
||||
# Complete(R),
|
||||
# Yielded(Y),
|
||||
# Complete(R),
|
||||
# }
|
||||
#
|
||||
#
|
||||
# trait Generator {
|
||||
# type Yield;
|
||||
# type Return;
|
||||
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
# }
|
||||
#
|
||||
#
|
||||
# enum GeneratorA {
|
||||
# Enter,
|
||||
# Yield1 {
|
||||
@@ -525,7 +525,7 @@ does what we'd expect. But there is still one huge problem with this:</p>
|
||||
# },
|
||||
# Exit,
|
||||
# }
|
||||
#
|
||||
#
|
||||
# impl GeneratorA {
|
||||
# fn start() -> Self {
|
||||
# GeneratorA::Enter
|
||||
@@ -541,15 +541,15 @@ does what we'd expect. But there is still one huge problem with this:</p>
|
||||
# let borrowed = &to_borrow;
|
||||
# let res = borrowed.len();
|
||||
# *self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
||||
#
|
||||
#
|
||||
# // We set the self-reference here
|
||||
# if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
|
||||
# *borrowed = to_borrow;
|
||||
# }
|
||||
#
|
||||
#
|
||||
# GeneratorState::Yielded(res)
|
||||
# }
|
||||
#
|
||||
#
|
||||
# GeneratorA::Yield1 {borrowed, ..} => {
|
||||
# let borrowed: &String = unsafe {&**borrowed};
|
||||
# println!("{} world", borrowed);
|
||||
@@ -585,16 +585,16 @@ pub fn main() {
|
||||
};
|
||||
}
|
||||
# enum GeneratorState<Y, R> {
|
||||
# Yielded(Y),
|
||||
# Complete(R),
|
||||
# Yielded(Y),
|
||||
# Complete(R),
|
||||
# }
|
||||
#
|
||||
#
|
||||
# trait Generator {
|
||||
# type Yield;
|
||||
# type Return;
|
||||
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
# }
|
||||
#
|
||||
#
|
||||
# enum GeneratorA {
|
||||
# Enter,
|
||||
# Yield1 {
|
||||
@@ -603,7 +603,7 @@ pub fn main() {
|
||||
# },
|
||||
# Exit,
|
||||
# }
|
||||
#
|
||||
#
|
||||
# impl GeneratorA {
|
||||
# fn start() -> Self {
|
||||
# GeneratorA::Enter
|
||||
@@ -619,15 +619,15 @@ pub fn main() {
|
||||
# let borrowed = &to_borrow;
|
||||
# let res = borrowed.len();
|
||||
# *self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
||||
#
|
||||
#
|
||||
# // We set the self-reference here
|
||||
# if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
|
||||
# *borrowed = to_borrow;
|
||||
# }
|
||||
#
|
||||
#
|
||||
# GeneratorState::Yielded(res)
|
||||
# }
|
||||
#
|
||||
#
|
||||
# GeneratorA::Yield1 {borrowed, ..} => {
|
||||
# let borrowed: &String = unsafe {&**borrowed};
|
||||
# println!("{} world", borrowed);
|
||||
@@ -646,7 +646,7 @@ while using just safe Rust. This is a big problem!</p>
|
||||
<blockquote>
|
||||
<p>I've actually forced the code above to use the nightly version of the compiler.
|
||||
If you run <a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5cbe9897c0e23a502afd2740c7e78b98">the example above on the playground</a>,
|
||||
you'll see that it runs without panicing on the current stable (1.42.0) but
|
||||
you'll see that it runs without panicking on the current stable (1.42.0) but
|
||||
panics on the current nightly (1.44.0). Scary!</p>
|
||||
</blockquote>
|
||||
<p>We'll explain exactly what happened here using a slightly simpler example in the next
|
||||
@@ -707,7 +707,7 @@ pub fn main() {
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
|
||||
|
||||
let gen2 = static || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
@@ -721,7 +721,7 @@ pub fn main() {
|
||||
if let GeneratorState::Yielded(n) = pinned1.as_mut().resume(()) {
|
||||
println!("Gen1 got value {}", n);
|
||||
}
|
||||
|
||||
|
||||
if let GeneratorState::Yielded(n) = pinned2.as_mut().resume(()) {
|
||||
println!("Gen2 got value {}", n);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user