version3
This commit is contained in:
@@ -227,11 +227,15 @@ async library.</p>
|
|||||||
that they implement a way to do multitasking by having a "userland"
|
that they implement a way to do multitasking by having a "userland"
|
||||||
runtime:</p>
|
runtime:</p>
|
||||||
<h2><a class="header" href="#green-threads" id="green-threads">Green threads</a></h2>
|
<h2><a class="header" href="#green-threads" id="green-threads">Green threads</a></h2>
|
||||||
<p>Green threads has been popularized by GO in the recent years. Green threads
|
<p>Green threads uses the same mechanism as an OS does by creating a thread for
|
||||||
uses the same basic technique as operating systems does to handle concurrency.</p>
|
each task, setting up a stack, save the CPU's state and jump from one
|
||||||
<p>Green threads are implemented by setting up a stack for each task you want to
|
task(thread) to another by doing a "context switch".</p>
|
||||||
execute and make the CPU "jump" from one stack to another to switch between
|
<p>We yield control to the scheduler (which is a central part of the runtime in
|
||||||
tasks.</p>
|
such a system) 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 async, await, Futures or Pin. All this would be implementation details
|
||||||
|
for the library.</p>
|
||||||
<p>The typical flow will be like this:</p>
|
<p>The typical flow will be like this:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Run som non-blocking code</li>
|
<li>Run som non-blocking code</li>
|
||||||
@@ -240,7 +244,7 @@ tasks.</p>
|
|||||||
"jumps" to that stack</li>
|
"jumps" to that stack</li>
|
||||||
<li>Run some non-blocking code on the new thread until a new blocking call or the
|
<li>Run some non-blocking code on the new thread until a new blocking call or the
|
||||||
task is finished</li>
|
task is finished</li>
|
||||||
<li>"jumps" back to the "main" thread and so on</li>
|
<li>"jumps" back to the "main" thread, schedule a new thread to run and jump to that</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>These "jumps" are know as context switches. Your OS is doing it many times each
|
<p>These "jumps" are know as context switches. Your OS is doing it many times each
|
||||||
second as you read this.</p>
|
second as you read this.</p>
|
||||||
@@ -638,7 +642,7 @@ syntax where we now can write our last example like this:</p>
|
|||||||
<p>You can consider the <code>run</code> function a <em>pausable</em> task consisting of several
|
<p>You can consider the <code>run</code> function a <em>pausable</em> task consisting of several
|
||||||
sub-tasks. On each "await" point it yields control to the scheduler (in this
|
sub-tasks. On each "await" point it yields control to the scheduler (in this
|
||||||
case it's the well known Javascript event loop). Once one of the sub-tasks changes
|
case it's the well known Javascript event loop). Once one of the sub-tasks changes
|
||||||
state to either <code>fulfilled</code> or <code>rejected</code> the task is sheduled to continue to
|
state to either <code>fulfilled</code> or <code>rejected</code> the task is scheduled to continue to
|
||||||
the next step.</p>
|
the next step.</p>
|
||||||
<p>Syntactically, Rusts Futures 1.0 was a lot like the promises example above and
|
<p>Syntactically, Rusts Futures 1.0 was a lot like the promises example above and
|
||||||
Rusts Futures 3.0 is a lot like async/await in our last example.</p>
|
Rusts Futures 3.0 is a lot like async/await in our last example.</p>
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ learning one makes learning the next much easier.</p>
|
|||||||
<p>The difference between Rust and other languages is that you have to make an
|
<p>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, in other languages
|
active choice when it comes to picking a runtime. Most often, in other languages
|
||||||
you'll just use the one provided for you.</p>
|
you'll just use the one provided for you.</p>
|
||||||
<p>An async runtime can be divided into two parts:</p>
|
<p><strong>An async runtime can be divided into two parts:</strong></p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>The Executor</li>
|
<li>The Executor</li>
|
||||||
<li>The Reactor</li>
|
<li>The Reactor</li>
|
||||||
|
|||||||
@@ -151,7 +151,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<h1><a class="header" href="#waker-and-context" id="waker-and-context">Waker and Context</a></h1>
|
<h1><a class="header" href="#waker-and-context" id="waker-and-context">Waker and Context</a></h1>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p><strong>Relevant for:</strong></p>
|
<p><strong>Overview:</strong></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Understanding how the Waker object is constructed</li>
|
<li>Understanding how the Waker object is constructed</li>
|
||||||
<li>Learning how the runtime know when a leaf-future can resume</li>
|
<li>Learning how the runtime know when a leaf-future can resume</li>
|
||||||
@@ -235,7 +235,7 @@ object from these parts:</p>
|
|||||||
and try to run it. If you want to go back, press the undo symbol. Keep an eye
|
and try to run it. If you want to go back, press the undo symbol. Keep an eye
|
||||||
out for these as we go forward. Many examples will be editable.</p>
|
out for these as we go forward. Many examples will be editable.</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<pre><pre class="playpen"><code class="language-rust editable">// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr)
|
<pre><pre class="playpen"><code class="language-rust">// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr)
|
||||||
trait Test {
|
trait Test {
|
||||||
fn add(&self) -> i32;
|
fn add(&self) -> i32;
|
||||||
fn sub(&self) -> i32;
|
fn sub(&self) -> i32;
|
||||||
@@ -293,7 +293,6 @@ fn main() {
|
|||||||
println!("Sub: 3 - 2 = {}", test.sub());
|
println!("Sub: 3 - 2 = {}", test.sub());
|
||||||
println!("Mul: 3 * 2 = {}", test.mul());
|
println!("Mul: 3 * 2 = {}", test.mul());
|
||||||
}
|
}
|
||||||
|
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
<p>Now that you know this you also know why how we implement the <code>Waker</code> type
|
<p>Now that you know this you also know why how we implement the <code>Waker</code> type
|
||||||
in Rust.</p>
|
in Rust.</p>
|
||||||
|
|||||||
@@ -151,11 +151,11 @@
|
|||||||
<main>
|
<main>
|
||||||
<h1><a class="header" href="#generators" id="generators">Generators</a></h1>
|
<h1><a class="header" href="#generators" id="generators">Generators</a></h1>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p><strong>Relevant for:</strong></p>
|
<p><strong>Overview:</strong></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Understanding how the async/await syntax works since it's how <code>await</code> is implemented</li>
|
<li>Understandi how the async/await syntax works since it's how <code>await</code> is implemented</li>
|
||||||
<li>Knowing why we need <code>Pin</code></li>
|
<li>Know why we need <code>Pin</code></li>
|
||||||
<li>Understanding why Rusts async model is very efficient</li>
|
<li>Understand why Rusts async model is very efficient</li>
|
||||||
</ul>
|
</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>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
|
well written and I can recommend reading through it (it talks as much about
|
||||||
@@ -172,17 +172,9 @@ handle concurrency:</p>
|
|||||||
<li>Using combinators.</li>
|
<li>Using combinators.</li>
|
||||||
<li>Stackless coroutines, better known as generators.</li>
|
<li>Stackless coroutines, better known as generators.</li>
|
||||||
</ol>
|
</ol>
|
||||||
<h3><a class="header" href="#stackful-coroutinesgreen-threads" id="stackful-coroutinesgreen-threads">Stackful coroutines/green threads</a></h3>
|
<p>We covered <a href="0_background_information.html#green-threads">green threads in the background information</a>
|
||||||
<p>I've written about green threads before. Go check out
|
so we won't repeat that here. We'll concentrate on the variants of stackless
|
||||||
<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>
|
coroutines which Rust uses today.</p>
|
||||||
<p>Green threads uses the same mechanism as an OS does by creating a thread for
|
|
||||||
each task, setting up a stack, save the CPU's state and jump from one
|
|
||||||
task(thread) to another by doing a "context switch".</p>
|
|
||||||
<p>We yield control to the scheduler (which is a central part of the runtime in
|
|
||||||
such a system) 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>
|
<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,
|
<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>
|
you already know combinators. In Rust they look like this:</p>
|
||||||
@@ -227,10 +219,13 @@ async/await as keywords (it can even be done using a macro).</li>
|
|||||||
println!("{}", borrowed);
|
println!("{}", borrowed);
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Generators in Rust are implemented as state machines. The memory footprint of a
|
<p>Async in Rust is implemented using Generators. So to understand how Async really
|
||||||
chain of computations is only defined by the largest footprint of any single
|
works we need to understand generators first. Generators in Rust are implemented
|
||||||
step require. That means that adding steps to a chain of computations might not
|
as state machines. The memory footprint of a chain of computations is only
|
||||||
require any increased memory at all.</p>
|
defined by the largest footprint of what the largest step require. </p>
|
||||||
|
<p>That means that adding steps to a chain of computations might not require any
|
||||||
|
increased memory at all and it's one of the reasons why Futures and Async in
|
||||||
|
Rust has very little overhead.</p>
|
||||||
<h2><a class="header" href="#how-generators-work" id="how-generators-work">How generators work</a></h2>
|
<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
|
<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 could look like this
|
keyword in a closure, converts it to a generator. A closure could look like this
|
||||||
@@ -302,19 +297,17 @@ impl Generator for GeneratorA {
|
|||||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||||
GeneratorA::Enter(a1) => {
|
GeneratorA::Enter(a1) => {
|
||||||
|
|
||||||
/*|---code before yield---|*/
|
/*----code before yield----*/
|
||||||
/*|*/ println!("Hello"); /*|*/
|
println!("Hello");
|
||||||
/*|*/ let a = a1 * 2; /*|*/
|
let a = a1 * 2;
|
||||||
/*|------------------------|*/
|
|
||||||
|
|
||||||
*self = GeneratorA::Yield1(a);
|
*self = GeneratorA::Yield1(a);
|
||||||
GeneratorState::Yielded(a)
|
GeneratorState::Yielded(a)
|
||||||
}
|
}
|
||||||
GeneratorA::Yield1(_) => {
|
|
||||||
|
|
||||||
/*|----code after yield----|*/
|
GeneratorA::Yield1(_) => {
|
||||||
/*|*/ println!("world!"); /*|*/
|
/*-----code after yield-----*/
|
||||||
/*|-------------------------|*/
|
println!("world!");
|
||||||
|
|
||||||
*self = GeneratorA::Exit;
|
*self = GeneratorA::Exit;
|
||||||
GeneratorState::Complete(())
|
GeneratorState::Complete(())
|
||||||
@@ -340,23 +333,34 @@ limitation just slip and call it a day yet.</p>
|
|||||||
<p>We'll use the optimized version of the state machines which is used in Rust today. For a more
|
<p>We'll use the optimized version of the state machines which is used in Rust today. For a more
|
||||||
in depth explanation see <a href="https://tmandry.gitlab.io/blog/posts/optimizing-await-1/">Tyler Mandry's excellent article: How Rust optimizes async/await</a></p>
|
in depth explanation see <a href="https://tmandry.gitlab.io/blog/posts/optimizing-await-1/">Tyler Mandry's excellent article: How Rust optimizes async/await</a></p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<pre><code class="language-rust noplaypen ignore">let mut gen = move || {
|
<pre><code class="language-rust noplaypen ignore">let mut generator = move || {
|
||||||
let to_borrow = String::from("Hello");
|
let to_borrow = String::from("Hello");
|
||||||
let borrowed = &to_borrow;
|
let borrowed = &to_borrow;
|
||||||
yield borrowed.len();
|
yield borrowed.len();
|
||||||
println!("{} world!", borrowed);
|
println!("{} world!", borrowed);
|
||||||
};
|
};
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
<p>We'll be hand-coding some versions of a state-machines representing a state
|
||||||
|
machine for the generator defined aboce.</p>
|
||||||
|
<p>We step through each step "manually" in every example, so it looks pretty
|
||||||
|
unfamiliar. We could add some syntactic sugar like implementing the <code>Iterator</code>
|
||||||
|
trait for our generators which would let us do this:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust ingore">
|
||||||
|
# #![allow(unused_variables)]
|
||||||
|
#fn main() {
|
||||||
|
for val in generator {
|
||||||
|
println!("{}", val);
|
||||||
|
}
|
||||||
|
#}</code></pre></pre>
|
||||||
|
<p>It's a pretty trivial change to make, but this chapter is already getting long.
|
||||||
|
Just keep this in the back of your head as we move forward.</p>
|
||||||
<p>Now what does our rewritten state machine look like with this example?</p>
|
<p>Now what does our rewritten state machine look like with this example?</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust compile_fail">
|
<pre><pre class="playpen"><code class="language-rust compile_fail">
|
||||||
# #![allow(unused_variables)]
|
# #![allow(unused_variables)]
|
||||||
#fn main() {
|
#fn main() {
|
||||||
# // 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> {
|
# enum GeneratorState<Y, R> {
|
||||||
# // originally called `CoResult`
|
# Yielded(Y),
|
||||||
# Yielded(Y), // originally called `Yield(Y)`
|
# Complete(R),
|
||||||
# Complete(R), // originally called `Return(R)`
|
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
# trait Generator {
|
# trait Generator {
|
||||||
@@ -388,7 +392,7 @@ impl Generator for GeneratorA {
|
|||||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||||
GeneratorA::Enter => {
|
GeneratorA::Enter => {
|
||||||
let to_borrow = String::from("Hello");
|
let to_borrow = String::from("Hello");
|
||||||
let borrowed = &to_borrow;
|
let borrowed = &to_borrow; // <--- NB!
|
||||||
let res = borrowed.len();
|
let res = borrowed.len();
|
||||||
|
|
||||||
*self = GeneratorA::Yield1 {to_borrow, borrowed};
|
*self = GeneratorA::Yield1 {to_borrow, borrowed};
|
||||||
@@ -414,30 +418,9 @@ to make this work, we'll have to let the compiler know that <em>we</em> control
|
|||||||
see we end up in a <em>self referential struct</em>. A struct which holds references
|
see we end up in a <em>self referential struct</em>. A struct which holds references
|
||||||
into itself.</p>
|
into itself.</p>
|
||||||
<p>As you'll notice, this compiles just fine!</p>
|
<p>As you'll notice, this compiles just fine!</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust editable">pub fn main() {
|
<pre><code class="language-rust ignore">enum GeneratorState<Y, R> {
|
||||||
let mut gen = GeneratorA::start();
|
Yielded(Y),
|
||||||
let mut gen2 = GeneratorA::start();
|
Complete(R),
|
||||||
|
|
||||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
|
||||||
println!("Got value {}", n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you uncomment this, very bad things can happen. This is why we need `Pin`
|
|
||||||
// std::mem::swap(&mut gen, &mut gen2);
|
|
||||||
|
|
||||||
if let GeneratorState::Yielded(n) = gen2.resume() {
|
|
||||||
println!("Got value {}", n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if you uncomment `mem::swap`.. this should now start gen2.
|
|
||||||
if let GeneratorState::Complete(()) = gen.resume() {
|
|
||||||
()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GeneratorState<Y, R> {
|
|
||||||
Yielded(Y), // originally called `Yield(Y)`
|
|
||||||
Complete(R), // originally called `Return(R)`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Generator {
|
trait Generator {
|
||||||
@@ -450,7 +433,7 @@ enum GeneratorA {
|
|||||||
Enter,
|
Enter,
|
||||||
Yield1 {
|
Yield1 {
|
||||||
to_borrow: String,
|
to_borrow: String,
|
||||||
borrowed: *const String, // Normally you'll see `std::ptr::NonNull` used instead of *ptr
|
borrowed: *const String,
|
||||||
},
|
},
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
@@ -464,20 +447,18 @@ impl Generator for GeneratorA {
|
|||||||
type Yield = usize;
|
type Yield = usize;
|
||||||
type Return = ();
|
type Return = ();
|
||||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||||
// lets us get ownership over current state
|
|
||||||
match self {
|
match self {
|
||||||
GeneratorA::Enter => {
|
GeneratorA::Enter => {
|
||||||
let to_borrow = String::from("Hello");
|
let to_borrow = String::from("Hello");
|
||||||
let borrowed = &to_borrow;
|
let borrowed = &to_borrow;
|
||||||
let res = borrowed.len();
|
let res = borrowed.len();
|
||||||
|
|
||||||
// Trick to actually get a self reference
|
|
||||||
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
||||||
match self {
|
|
||||||
GeneratorA::Yield1{to_borrow, borrowed} => *borrowed = to_borrow,
|
// We set the self-reference here
|
||||||
_ => unreachable!(),
|
if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
|
||||||
};
|
*borrowed = to_borrow;
|
||||||
|
}
|
||||||
|
|
||||||
GeneratorState::Yielded(res)
|
GeneratorState::Yielded(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,137 +472,172 @@ impl Generator for GeneratorA {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</code></pre></pre>
|
</code></pre>
|
||||||
<blockquote>
|
<p>Remember that our example is the generator we crated which looked like this:</p>
|
||||||
<p>Try to uncomment the line with <code>mem::swap</code> and see the results.</p>
|
<pre><code class="language-rust noplaypen ignore">let mut gen = move || {
|
||||||
</blockquote>
|
let to_borrow = String::from("Hello");
|
||||||
<p>While the example above compiles just fine, we expose consumers of this this API
|
let borrowed = &to_borrow;
|
||||||
to both possible undefined behavior and other memory errors while using just safe
|
yield borrowed.len();
|
||||||
Rust. This is a big problem!</p>
|
println!("{} world!", borrowed);
|
||||||
<p>But now, let's prevent this problem using <code>Pin</code>. We'll discuss
|
|
||||||
<code>Pin</code> more in the next chapter, but you'll get an introduction here by just
|
|
||||||
reading the comments.</p>
|
|
||||||
<pre><pre class="playpen"><code class="language-rust editable">#![feature(optin_builtin_traits)] // needed to implement `!Unpin`
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let gen1 = GeneratorA::start();
|
|
||||||
let gen2 = GeneratorA::start();
|
|
||||||
// Before we pin the pointers, this is safe to do
|
|
||||||
// std::mem::swap(&mut gen, &mut gen2);
|
|
||||||
|
|
||||||
// constructing a `Pin::new()` on a type which does not implement `Unpin` is unsafe.
|
|
||||||
// However, as you'll see in the start of the next chapter value pinned to
|
|
||||||
// heap can be constructed while staying in safe Rust so we can use
|
|
||||||
// that to avoid unsafe. You can also use crates like `pin_utils` to do
|
|
||||||
// this safely, just remember that they use unsafe under the hood so it's
|
|
||||||
// like using an already-reviewed unsafe implementation.
|
|
||||||
|
|
||||||
let mut pinned1 = Box::pin(gen1);
|
|
||||||
let mut pinned2 = Box::pin(gen2);
|
|
||||||
// Uncomment these if you think it's safe to pin the values to the stack instead
|
|
||||||
// (it is in this case). Remember to comment out the two previous lines first.
|
|
||||||
//let mut pinned1 = unsafe { Pin::new_unchecked(&mut gen1) };
|
|
||||||
//let mut pinned2 = unsafe { Pin::new_unchecked(&mut gen2) };
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
</code></pre>
|
||||||
|
<p>Below is an example of how we could run this state-machine. But there is still
|
||||||
|
one huge problem with this:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">pub fn main() {
|
||||||
|
let mut gen = GeneratorA::start();
|
||||||
|
let mut gen2 = GeneratorA::start();
|
||||||
|
|
||||||
// This won't work
|
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||||
// std::mem::swap(&mut gen, &mut gen2);
|
println!("Got value {}", n);
|
||||||
// This will work but will just swap the pointers. Nothing inherently bad happens here.
|
|
||||||
// std::mem::swap(&mut pinned1, &mut pinned2);
|
|
||||||
|
|
||||||
let _ = pinned1.as_mut().resume();
|
|
||||||
let _ = pinned2.as_mut().resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
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(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return>;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GeneratorA {
|
|
||||||
Enter,
|
|
||||||
Yield1 {
|
|
||||||
to_borrow: String,
|
|
||||||
borrowed: *const String, // Normally you'll see `std::ptr::NonNull` used instead of *ptr
|
|
||||||
},
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GeneratorA {
|
|
||||||
fn start() -> Self {
|
|
||||||
GeneratorA::Enter
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// This tells us that the underlying pointer is not safe to move after pinning. In this case,
|
if let GeneratorState::Yielded(n) = gen2.resume() {
|
||||||
// only we as implementors "feel" this, however, if someone is relying on our Pinned pointer
|
println!("Got value {}", n);
|
||||||
// this will prevent them from moving it. You need to enable the feature flag
|
|
||||||
// `#![feature(optin_builtin_traits)]` and use the nightly compiler to implement `!Unpin`.
|
|
||||||
// Normally, you would use `std::marker::PhantomPinned` to indicate that the
|
|
||||||
// struct is `!Unpin`.
|
|
||||||
impl !Unpin for GeneratorA { }
|
|
||||||
|
|
||||||
impl Generator for GeneratorA {
|
|
||||||
type Yield = usize;
|
|
||||||
type Return = ();
|
|
||||||
fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return> {
|
|
||||||
// lets us get ownership over current state
|
|
||||||
let this = unsafe { self.get_unchecked_mut() };
|
|
||||||
match this {
|
|
||||||
GeneratorA::Enter => {
|
|
||||||
let to_borrow = String::from("Hello");
|
|
||||||
let borrowed = &to_borrow;
|
|
||||||
let res = borrowed.len();
|
|
||||||
|
|
||||||
// Trick to actually get a self reference. We can't reference
|
|
||||||
// the `String` earlier since these references will point to the
|
|
||||||
// location in this stack frame which will not be valid anymore
|
|
||||||
// when this function returns.
|
|
||||||
*this = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
|
||||||
match this {
|
|
||||||
GeneratorA::Yield1{to_borrow, borrowed} => *borrowed = to_borrow,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
GeneratorState::Yielded(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
GeneratorA::Yield1 {borrowed, ..} => {
|
|
||||||
let borrowed: &String = unsafe {&**borrowed};
|
|
||||||
println!("{} world", borrowed);
|
|
||||||
*this = GeneratorA::Exit;
|
|
||||||
GeneratorState::Complete(())
|
|
||||||
}
|
|
||||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let GeneratorState::Complete(()) = gen.resume() {
|
||||||
|
()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
# enum GeneratorState<Y, R> {
|
||||||
|
# Yielded(Y),
|
||||||
|
# Complete(R),
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# trait Generator {
|
||||||
|
# type Yield;
|
||||||
|
# type Return;
|
||||||
|
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# enum GeneratorA {
|
||||||
|
# Enter,
|
||||||
|
# Yield1 {
|
||||||
|
# to_borrow: String,
|
||||||
|
# borrowed: *const String,
|
||||||
|
# },
|
||||||
|
# Exit,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl GeneratorA {
|
||||||
|
# fn start() -> Self {
|
||||||
|
# GeneratorA::Enter
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# impl Generator for GeneratorA {
|
||||||
|
# type Yield = usize;
|
||||||
|
# type Return = ();
|
||||||
|
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||||
|
# match self {
|
||||||
|
# GeneratorA::Enter => {
|
||||||
|
# let to_borrow = String::from("Hello");
|
||||||
|
# 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);
|
||||||
|
# *self = GeneratorA::Exit;
|
||||||
|
# GeneratorState::Complete(())
|
||||||
|
# }
|
||||||
|
# GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
<p>Now, as you see, the consumer of this API must either:</p>
|
<p>The problem however is that in safe Rust we can still do this:</p>
|
||||||
<ol>
|
<p><em>Run the code and compare the results. Do you see the problem?</em></p>
|
||||||
<li>Box the value and thereby allocating it on the heap</li>
|
<pre><pre class="playpen"><code class="language-rust">pub fn main() {
|
||||||
<li>Use <code>unsafe</code> and pin the value to the stack. The user knows that if they move
|
let mut gen = GeneratorA::start();
|
||||||
the value afterwards it will violate the guarantee they promise to uphold when
|
let mut gen2 = GeneratorA::start();
|
||||||
they did their unsafe implementation.</li>
|
|
||||||
</ol>
|
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||||
<p>Hopefully, after this you'll have an idea of what happens when you use the
|
println!("Got value {}", n);
|
||||||
<code>yield</code> or <code>await</code> keywords inside an async function, and why we need <code>Pin</code> if
|
}
|
||||||
we want to be able to safely borrow across <code>yield/await</code> points.</p>
|
|
||||||
|
std::mem::swap(&mut gen, &mut gen2); // <--- Big problem!
|
||||||
|
|
||||||
|
if let GeneratorState::Yielded(n) = gen2.resume() {
|
||||||
|
println!("Got value {}", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This would now start gen2 since we swapped them.
|
||||||
|
if let GeneratorState::Complete(()) = gen.resume() {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
# enum GeneratorState<Y, R> {
|
||||||
|
# Yielded(Y),
|
||||||
|
# Complete(R),
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# trait Generator {
|
||||||
|
# type Yield;
|
||||||
|
# type Return;
|
||||||
|
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# enum GeneratorA {
|
||||||
|
# Enter,
|
||||||
|
# Yield1 {
|
||||||
|
# to_borrow: String,
|
||||||
|
# borrowed: *const String,
|
||||||
|
# },
|
||||||
|
# Exit,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl GeneratorA {
|
||||||
|
# fn start() -> Self {
|
||||||
|
# GeneratorA::Enter
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# impl Generator for GeneratorA {
|
||||||
|
# type Yield = usize;
|
||||||
|
# type Return = ();
|
||||||
|
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||||
|
# match self {
|
||||||
|
# GeneratorA::Enter => {
|
||||||
|
# let to_borrow = String::from("Hello");
|
||||||
|
# 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);
|
||||||
|
# *self = GeneratorA::Exit;
|
||||||
|
# GeneratorState::Complete(())
|
||||||
|
# }
|
||||||
|
# GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
</code></pre></pre>
|
||||||
|
<p>Wait? What happened to "Hello"?</p>
|
||||||
|
<p>Turns out that while the example above compiles
|
||||||
|
just fine, we expose consumers of this this API to both possible undefined
|
||||||
|
behavior and other memory errors while using just safe Rust. This is a big
|
||||||
|
problem!</p>
|
||||||
|
<p>We'll explain exactly what happened using a slightly simpler example in the next
|
||||||
|
chapter and we'll fix our generator using <code>Pin</code> so join me as we explore
|
||||||
|
the last topic before we implement our main Futures example.</p>
|
||||||
<h2><a class="header" href="#bonus-section---self-referential-generators-in-rust-today" id="bonus-section---self-referential-generators-in-rust-today">Bonus section - self referential generators in Rust today</a></h2>
|
<h2><a class="header" href="#bonus-section---self-referential-generators-in-rust-today" id="bonus-section---self-referential-generators-in-rust-today">Bonus section - self referential generators in Rust today</a></h2>
|
||||||
<p>Thanks to <a href="https://github.com/rust-lang/rust/pull/45337/files">PR#45337</a> you can actually run code like the one in our
|
<p>Thanks to <a href="https://github.com/rust-lang/rust/pull/45337/files">PR#45337</a> you can actually run code like the one in our
|
||||||
example in Rust today using the <code>static</code> keyword on nightly. Try it for
|
example in Rust today using the <code>static</code> keyword on nightly. Try it for
|
||||||
|
|||||||
585
book/4_pin.html
585
book/4_pin.html
@@ -151,99 +151,53 @@
|
|||||||
<main>
|
<main>
|
||||||
<h1><a class="header" href="#pin" id="pin">Pin</a></h1>
|
<h1><a class="header" href="#pin" id="pin">Pin</a></h1>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p><strong>Relevant for</strong></p>
|
<p><strong>Overview</strong></p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Understanding <code>Generators</code> and <code>Futures</code></li>
|
<li>Learn how to use <code>Pin</code> and why it's required when implementing your own <code>Future</code></li>
|
||||||
<li>Knowing how to use <code>Pin</code> is required when implementing your own <code>Future</code></li>
|
<li>Understand how to make self-referential types safe to use in Rust</li>
|
||||||
<li>Understanding how to make self-referential types safe to use in Rust</li>
|
<li>Learn how borrowing across <code>await</code> points is accomplished</li>
|
||||||
<li>Learning how borrowing across <code>await</code> points is accomplished</li>
|
<li>Get a set of practical rules to help you work with <code>Pin</code></li>
|
||||||
</ol>
|
</ol>
|
||||||
<p><code>Pin</code> was suggested in <a href="https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md">RFC#2349</a></p>
|
<p><code>Pin</code> was suggested in <a href="https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md">RFC#2349</a></p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<p>We already got a brief introduction of <code>Pin</code> in the previous chapters, so we'll
|
<p>Let's jump strait to it. Pinning is one of those subjects which is hard to wrap
|
||||||
start off without any further introduction.</p>
|
your head around in the start, but once you unlock a mental model for it
|
||||||
<p>Let's jump strait to some definitions and then create 10 rules to remember when
|
it gets significantly easier to reason about.</p>
|
||||||
we work with <code>Pin</code>.</p>
|
|
||||||
<h2><a class="header" href="#definitions" id="definitions">Definitions</a></h2>
|
<h2><a class="header" href="#definitions" id="definitions">Definitions</a></h2>
|
||||||
|
<p>Pin is only relevant for pointers. A reference to an object is a pointer.</p>
|
||||||
<p>Pin consists of the <code>Pin</code> type and the <code>Unpin</code> marker. Pin's purpose in life is
|
<p>Pin consists of the <code>Pin</code> type and the <code>Unpin</code> marker. Pin's purpose in life is
|
||||||
to govern the rules that need to apply for types which implement <code>!Unpin</code>.</p>
|
to govern the rules that need to apply for types which implement <code>!Unpin</code>.</p>
|
||||||
<p>Pin is only relevant for pointers. A reference to an object is a pointer.</p>
|
|
||||||
<p>Yep, you're right, that's double negation right there. <code>!Unpin</code> means
|
<p>Yep, you're right, that's double negation right there. <code>!Unpin</code> means
|
||||||
"not-un-pin".</p>
|
"not-un-pin".</p>
|
||||||
<p><em>This naming scheme is Rust deliberately testing if you're too tired to safely implement a type with this marker. If you're starting to get confused by
|
|
||||||
<code>!Unpin</code> it's a good sign that it's time to lay down the work and start over
|
|
||||||
tomorrow with a fresh mind.</em></p>
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>On a more serious note, I feel obliged to mention that there are valid reasons for the names
|
<p><em>This naming scheme is one of Rusts safety features where it deliberately
|
||||||
that were chosen. If you want to you can read a bit of the discussion from the
|
tests if you're too tired to safely implement a type with this marker. If
|
||||||
<a href="https://internals.rust-lang.org/t/naming-pin-anchor-move/6864/12">internals thread</a>. One of the best takeaways from there in my eyes
|
you're starting to get confused, or even angry, by <code>!Unpin</code> it's a good sign
|
||||||
is this quote from <code>tmandry</code>:</p>
|
that it's time to lay down the work and start over tomorrow with a fresh mind.</em></p>
|
||||||
<p><em>Think of taking a thumbtack out of a cork board so you can tweak how a flyer looks. For Unpin types, this unpinning is directly supported by the type; you can do this implicitly. You can even swap out the object with another before you put the pin back. For other types, you must be much more careful.</em></p>
|
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<p>For the next paragraph we'll rename these markers to:</p>
|
<p>On a more serious note, I feel obliged to mention that there are valid reasons
|
||||||
|
for the names that were chosen. Naming is not easy, and I considered renaming
|
||||||
|
<code>Unpin</code> and <code>!Unpin</code> in this book to make them easier to reason about. </p>
|
||||||
|
<p>However, an experienced member of the Rust community convinced me that that there
|
||||||
|
is just too many nuances and edge-cases to consider which is easily overlooked when
|
||||||
|
naively giving these markers different names, and I'm convinced that we'll
|
||||||
|
just have to get used to them and use them as is.</p>
|
||||||
|
<p>If you want to you can read a bit of the discussion from the
|
||||||
|
<a href="https://internals.rust-lang.org/t/naming-pin-anchor-move/6864/12">internals thread</a>. One of the best takeaways from there in my
|
||||||
|
eyes is this quote from <code>tmandry</code>:</p>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p><code>!Unpin</code> = <code>MustStay</code> and <code>Unpin</code> = <code>CanMove</code></p>
|
<p><em>Think of taking a thumbtack out of a cork board so you can tweak how a flyer
|
||||||
|
looks. For Unpin types, this unpinning is directly supported by the type; you
|
||||||
|
can do this implicitly. You can even swap out the object with another before you
|
||||||
|
put the pin back. For other types, you must be much more careful.</em></p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<p>It just makes it much easier to talk about them.</p>
|
<h2><a class="header" href="#pinning-and-self-referential-structs" id="pinning-and-self-referential-structs">Pinning and self-referential structs</a></h2>
|
||||||
<h2><a class="header" href="#rules-to-remember" id="rules-to-remember">Rules to remember</a></h2>
|
<p>Let's start where we left off in the last chapter by making the problem we
|
||||||
<ol>
|
saw using a self-referential struct in our generator a lot simpler by making
|
||||||
<li>
|
some self-referential structs that are easier to reason about than our
|
||||||
<p>If <code>T: CanMove</code> (which is the default), then <code>Pin<'a, T></code> is entirely equivalent to <code>&'a mut T</code>. in other words: <code>CanMove</code> means it's OK for this type to be moved even when pinned, so <code>Pin</code> will have no effect on such a type.</p>
|
state machines:</p>
|
||||||
</li>
|
<p>For now our example will look like this:</p>
|
||||||
<li>
|
<pre><code class="language-rust ignore">use std::pin::Pin;
|
||||||
<p>Getting a <code>&mut T</code> to a pinned pointer requires unsafe if <code>T: MustStay</code>. In other words: requiring a pinned pointer to a type which is <code>MustStay</code> prevents the <em>user</em> of that API from moving that value unless it choses to write <code>unsafe</code> code.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Pinning does nothing special with memory allocation like putting it into some "read only" memory or anything fancy. It only tells the compiler that some operations on this value should be forbidden.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Most standard library types implement <code>CanMove</code>. The same goes for most
|
|
||||||
"normal" types you encounter in Rust. <code>Futures</code> and <code>Generators</code> are two
|
|
||||||
exceptions.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>The main use case for <code>Pin</code> is to allow self referential types, the whole
|
|
||||||
justification for stabilizing them was to allow that. There are still corner
|
|
||||||
cases in the API which are being explored.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>The implementation behind objects that are <code>MustStay</code> is most likely unsafe.
|
|
||||||
Moving such a type can cause the universe to crash. As of the time of writing
|
|
||||||
this book, creating and reading fields of a self referential struct still requires <code>unsafe</code>.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can add a <code>MustStay</code> bound on a type on nightly with a feature flag, or
|
|
||||||
by adding <code>std::marker::PhantomPinned</code> to your type on stable.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>You can either pin a value to memory on the stack or on the heap.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Pinning a <code>MustStay</code> pointer to the stack requires <code>unsafe</code></p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Pinning a <code>MustStay</code> pointer to the heap does not require <code>unsafe</code>. There is a shortcut for doing this using <code>Box::pin</code>.</p>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<blockquote>
|
|
||||||
<p>Unsafe code does not mean it's literally "unsafe", it only relieves the
|
|
||||||
guarantees you normally get from the compiler. An <code>unsafe</code> implementation can
|
|
||||||
be perfectly safe to do, but you have no safety net.</p>
|
|
||||||
</blockquote>
|
|
||||||
<p>Let's take a look at an example:</p>
|
|
||||||
<pre><pre class="playpen"><code class="language-rust editable">use std::pin::Pin;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut test1 = Test::new("test1");
|
|
||||||
test1.init();
|
|
||||||
let mut test2 = Test::new("test2");
|
|
||||||
test2.init();
|
|
||||||
|
|
||||||
println!("a: {}, b: {}", test1.a(), test1.b());
|
|
||||||
std::mem::swap(&mut test1, &mut test2); // try commenting out this line
|
|
||||||
println!("a: {}, b: {}", test2.a(), test2.b());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Test {
|
struct Test {
|
||||||
@@ -259,7 +213,7 @@ impl Test {
|
|||||||
b: std::ptr::null(),
|
b: std::ptr::null(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self) {
|
fn init(&mut self) {
|
||||||
let self_ref: *const String = &self.a;
|
let self_ref: *const String = &self.a;
|
||||||
self.b = self_ref;
|
self.b = self_ref;
|
||||||
@@ -273,7 +227,7 @@ impl Test {
|
|||||||
unsafe {&*(self.b)}
|
unsafe {&*(self.b)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</code></pre></pre>
|
</code></pre>
|
||||||
<p>Let's walk through this example since we'll be using it the rest of this chapter.</p>
|
<p>Let's walk through this example since we'll be using it the rest of this chapter.</p>
|
||||||
<p>We have a self-referential struct <code>Test</code>. <code>Test</code> needs an <code>init</code> method to be
|
<p>We have a self-referential struct <code>Test</code>. <code>Test</code> needs an <code>init</code> method to be
|
||||||
created which is strange but we'll need that to keep this example as short as
|
created which is strange but we'll need that to keep this example as short as
|
||||||
@@ -281,25 +235,165 @@ possible.</p>
|
|||||||
<p><code>Test</code> provides two methods to get a reference to the value of the fields
|
<p><code>Test</code> provides two methods to get a reference to the value of the fields
|
||||||
<code>a</code> and <code>b</code>. Since <code>b</code> is a reference to <code>a</code> we store it as a pointer since
|
<code>a</code> and <code>b</code>. Since <code>b</code> is a reference to <code>a</code> we store it as a pointer since
|
||||||
the borrowing rules of Rust doesn't allow us to define this lifetime.</p>
|
the borrowing rules of Rust doesn't allow us to define this lifetime.</p>
|
||||||
|
<p>Now, let's use this example to explain the problem we encounter in detail. As
|
||||||
|
you see, this works as expected:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">fn main() {
|
||||||
|
let mut test1 = Test::new("test1");
|
||||||
|
test1.init();
|
||||||
|
let mut test2 = Test::new("test2");
|
||||||
|
test2.init();
|
||||||
|
|
||||||
|
println!("a: {}, b: {}", test1.a(), test1.b());
|
||||||
|
println!("a: {}, b: {}", test2.a(), test2.b());
|
||||||
|
|
||||||
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# // We need an `init` method to actually set our self-reference
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ref: *const String = &self.a;
|
||||||
|
# self.b = self_ref;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a(&self) -> &str {
|
||||||
|
# &self.a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b(&self) -> &String {
|
||||||
|
# unsafe {&*(self.b)}
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
</code></pre></pre>
|
||||||
<p>In our main method we first instantiate two instances of <code>Test</code> and print out
|
<p>In our main method we first instantiate two instances of <code>Test</code> and print out
|
||||||
the value of the fields on <code>test1</code>. We get:</p>
|
the value of the fields on <code>test1</code>. We get what we'd expect:</p>
|
||||||
<pre><code class="language-rust ignore">a: test1, b: test1
|
<pre><code class="language-rust ignore">a: test1, b: test1
|
||||||
|
a: test2, b: test2
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Next we swap the data stored at the memory location which <code>test1</code> is pointing to
|
<p>Let's see what happens if we swap the data stored at the memory location
|
||||||
with the data stored at the memory location <code>test2</code> is pointing to and vice a versa.</p>
|
which <code>test1</code> is pointing to with the data stored at the memory location
|
||||||
<p>We should expect that printing the fields of <code>test2</code> should display the same as
|
<code>test2</code> is pointing to and vice a versa.</p>
|
||||||
<code>test1</code> (since the object we printed before the swap has moved there now).</p>
|
<pre><pre class="playpen"><code class="language-rust">fn main() {
|
||||||
<pre><code class="language-rust ignore">a: test1, b: test2
|
let mut test1 = Test::new("test1");
|
||||||
|
test1.init();
|
||||||
|
let mut test2 = Test::new("test2");
|
||||||
|
test2.init();
|
||||||
|
|
||||||
|
println!("a: {}, b: {}", test1.a(), test1.b());
|
||||||
|
std::mem::swap(&mut test1, &mut test2);
|
||||||
|
println!("a: {}, b: {}", test2.a(), test2.b());
|
||||||
|
|
||||||
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ref: *const String = &self.a;
|
||||||
|
# self.b = self_ref;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a(&self) -> &str {
|
||||||
|
# &self.a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b(&self) -> &String {
|
||||||
|
# unsafe {&*(self.b)}
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
</code></pre></pre>
|
||||||
|
<p>Naively, we could think that what we should get a debug print of <code>test1</code> two
|
||||||
|
times like this</p>
|
||||||
|
<pre><code class="language-rust ignore">a: test1, b: test1
|
||||||
|
a: test1, b: test1
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>The pointer to <code>b</code> still points to the old location. That location is now
|
<p>But instead we get:</p>
|
||||||
occupied with the string "test2". This can be a bit hard to visualize so I made
|
<pre><code class="language-rust ignore">a: test1, b: test1
|
||||||
a figure that i hope can help.</p>
|
a: test1, b: test2
|
||||||
|
</code></pre>
|
||||||
|
<p>The pointer to <code>test2.b</code> still points to the old location which is inside <code>test1</code>
|
||||||
|
now. The struct is not self-referential anymore, it holds a pointer to a field
|
||||||
|
in a different object. That means we can't rely on the lifetime of <code>test2.b</code> to
|
||||||
|
be tied to the lifetime of <code>test2</code> anymore.</p>
|
||||||
|
<p>If your still not convinced, this should at least convince you:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">fn main() {
|
||||||
|
let mut test1 = Test::new("test1");
|
||||||
|
test1.init();
|
||||||
|
let mut test2 = Test::new("test2");
|
||||||
|
test2.init();
|
||||||
|
|
||||||
|
println!("a: {}, b: {}", test1.a(), test1.b());
|
||||||
|
std::mem::swap(&mut test1, &mut test2);
|
||||||
|
test1.a = "I've totally changed now!".to_string();
|
||||||
|
println!("a: {}, b: {}", test2.a(), test2.b());
|
||||||
|
|
||||||
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ref: *const String = &self.a;
|
||||||
|
# self.b = self_ref;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a(&self) -> &str {
|
||||||
|
# &self.a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b(&self) -> &String {
|
||||||
|
# unsafe {&*(self.b)}
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
</code></pre></pre>
|
||||||
|
<p>That shouldn't happen. There is no serious error yet, but as you can imagine
|
||||||
|
it's easy to create serious bugs using this code.</p>
|
||||||
|
<p>I created a diagram to help visualize what's going on:</p>
|
||||||
<p><strong>Fig 1: Before and after swap</strong>
|
<p><strong>Fig 1: Before and after swap</strong>
|
||||||
<img src="./assets/swap_problem.jpg" alt="swap_problem" /></p>
|
<img src="./assets/swap_problem.jpg" alt="swap_problem" /></p>
|
||||||
<p>As you can see this results in unwanted behavior. It's easy to get this to
|
<p>As you can see this results in unwanted behavior. It's easy to get this to
|
||||||
segfault, show UB and fail in other spectacular ways as well.</p>
|
segfault, show UB and fail in other spectacular ways as well.</p>
|
||||||
<p>If we change the example to using <code>Pin</code> instead:</p>
|
<h2><a class="header" href="#pinning-to-the-stack" id="pinning-to-the-stack">Pinning to the stack</a></h2>
|
||||||
<pre><pre class="playpen"><code class="language-rust editable">use std::pin::Pin;
|
<p>Now, we can solve this problem by using <code>Pin</code> instead. Let's take a look at what
|
||||||
|
our example would look like then:</p>
|
||||||
|
<pre><code class="language-rust ignore">use std::pin::Pin;
|
||||||
use std::marker::PhantomPinned;
|
use std::marker::PhantomPinned;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -333,8 +427,14 @@ impl Test {
|
|||||||
unsafe { &*(self.b) }
|
unsafe { &*(self.b) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</code></pre>
|
||||||
pub fn main() {
|
<p>Now, what we've done here is pinning a stack address. That will always be
|
||||||
|
<code>unsafe</code> if our type implements <code>!Unpin</code>.</p>
|
||||||
|
<p>We use the same tricks here, including requiring an <code>init</code>. If we want to fix that
|
||||||
|
and let users avoid <code>unsafe</code> we need to pin our data on the heap instead which
|
||||||
|
we'll show in a second.</p>
|
||||||
|
<p>Let's see what happens if we run our example now:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">pub fn main() {
|
||||||
let mut test1 = Test::new("test1");
|
let mut test1 = Test::new("test1");
|
||||||
test1.init();
|
test1.init();
|
||||||
let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
|
let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
|
||||||
@@ -342,33 +442,103 @@ pub fn main() {
|
|||||||
test2.init();
|
test2.init();
|
||||||
let mut test2_pin = unsafe { Pin::new_unchecked(&mut test2) };
|
let mut test2_pin = unsafe { Pin::new_unchecked(&mut test2) };
|
||||||
|
|
||||||
println!(
|
println!("a: {}, b: {}", Test::a(test1_pin.as_ref()), Test::b(test1_pin.as_ref()));
|
||||||
"a: {}, b: {}",
|
println!("a: {}, b: {}", Test::a(test2_pin.as_ref()), Test::b(test2_pin.as_ref()));
|
||||||
Test::a(test1_pin.as_ref()),
|
|
||||||
Test::b(test1_pin.as_ref())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Try to uncomment this and see what happens
|
|
||||||
// std::mem::swap(test1_pin.as_mut(), test2_pin.as_mut());
|
|
||||||
println!(
|
|
||||||
"a: {}, b: {}",
|
|
||||||
Test::a(test2_pin.as_ref()),
|
|
||||||
Test::b(test2_pin.as_ref())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# use std::marker::PhantomPinned;
|
||||||
|
#
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# _marker: PhantomPinned,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# // This makes our type `!Unpin`
|
||||||
|
# _marker: PhantomPinned,
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ptr: *const String = &self.a;
|
||||||
|
# self.b = self_ptr;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a<'a>(self: Pin<&'a Self>) -> &'a str {
|
||||||
|
# &self.get_ref().a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b<'a>(self: Pin<&'a Self>) -> &'a String {
|
||||||
|
# unsafe { &*(self.b) }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
</code></pre></pre>
|
||||||
|
<p>Now, if we try to pull the same trick which got us in to trouble the last time
|
||||||
|
you'll get a compilation error. So t</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust compile_fail">pub fn main() {
|
||||||
|
let mut test1 = Test::new("test1");
|
||||||
|
test1.init();
|
||||||
|
let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
|
||||||
|
let mut test2 = Test::new("test2");
|
||||||
|
test2.init();
|
||||||
|
let mut test2_pin = unsafe { Pin::new_unchecked(&mut test2) };
|
||||||
|
|
||||||
|
println!("a: {}, b: {}", Test::a(test1_pin.as_ref()), Test::b(test1_pin.as_ref()));
|
||||||
|
std::mem::swap(test1_pin.as_mut(), test2_pin.as_mut());
|
||||||
|
println!("a: {}, b: {}", Test::a(test2_pin.as_ref()), Test::b(test2_pin.as_ref()));
|
||||||
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# use std::marker::PhantomPinned;
|
||||||
|
#
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# _marker: PhantomPinned,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# // This makes our type `!Unpin`
|
||||||
|
# _marker: PhantomPinned,
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ptr: *const String = &self.a;
|
||||||
|
# self.b = self_ptr;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a<'a>(self: Pin<&'a Self>) -> &'a str {
|
||||||
|
# &self.get_ref().a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b<'a>(self: Pin<&'a Self>) -> &'a String {
|
||||||
|
# unsafe { &*(self.b) }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
<p>Now, what we've done here is pinning a stack address. That will always be
|
|
||||||
<code>unsafe</code> if our type implements <code>!Unpin</code> (aka <code>MustStay</code>).</p>
|
|
||||||
<p>We use some tricks here, including requiring an <code>init</code>. If we want to fix that
|
|
||||||
and let users avoid <code>unsafe</code> we need to pin our data on the heap instead.</p>
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>Stack pinning will always depend on the current stack frame we're in, so we
|
<p>It's important to note that stack pinning will always depend on the current
|
||||||
can't create a self referential object in one stack frame and return it since
|
stack frame we're in, so we can't create a self referential object in one
|
||||||
any pointers we take to "self" is invalidated.</p>
|
stack frame and return it since any pointers we take to "self" is invalidated.</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<p>The next example solves some of our friction at the cost of a heap allocation.</p>
|
<h2><a class="header" href="#pinning-to-the-heap" id="pinning-to-the-heap">Pinning to the heap</a></h2>
|
||||||
<pre><pre class="playpen"><code class="language-rust editbable">use std::pin::Pin;
|
<p>For completeness let's remove some unsafe and the need for an <code>init</code> method
|
||||||
|
at the cost of a heap allocation. Pinning to the heap is safe so the user
|
||||||
|
doesn't need to implement any unsafe code:</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">use std::pin::Pin;
|
||||||
use std::marker::PhantomPinned;
|
use std::marker::PhantomPinned;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -407,9 +577,6 @@ pub fn main() {
|
|||||||
let mut test2 = Test::new("test2");
|
let mut test2 = Test::new("test2");
|
||||||
|
|
||||||
println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());
|
println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());
|
||||||
|
|
||||||
// Try to uncomment this and see what happens
|
|
||||||
// std::mem::swap(&mut test1, &mut test2);
|
|
||||||
println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());
|
println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());
|
||||||
}
|
}
|
||||||
</code></pre></pre>
|
</code></pre></pre>
|
||||||
@@ -419,6 +586,58 @@ makes sense. Once the data is allocated on the heap it will have a stable addres
|
|||||||
that the self-referential pointer stays valid.</p>
|
that the self-referential pointer stays valid.</p>
|
||||||
<p>There are ways to safely give some guarantees on stack pinning as well, but right
|
<p>There are ways to safely give some guarantees on stack pinning as well, but right
|
||||||
now you need to use a crate like <a href="https://docs.rs/pin-project/">pin_project</a> to do that.</p>
|
now you need to use a crate like <a href="https://docs.rs/pin-project/">pin_project</a> to do that.</p>
|
||||||
|
<h2><a class="header" href="#practical-rules-for-pinning" id="practical-rules-for-pinning">Practical rules for Pinning</a></h2>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<p>If <code>T: Unpin</code> (which is the default), then <code>Pin<'a, T></code> is entirely
|
||||||
|
equivalent to <code>&'a mut T</code>. in other words: <code>Unpin</code> means it's OK for this type
|
||||||
|
to be moved even when pinned, so <code>Pin</code> will have no effect on such a type.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Getting a <code>&mut T</code> to a pinned pointer requires unsafe if <code>T: !Unpin</code>. In
|
||||||
|
other words: requiring a pinned pointer to a type which is <code>!Unpin</code> prevents
|
||||||
|
the <em>user</em> of that API from moving that value unless it choses to write <code>unsafe</code>
|
||||||
|
code.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Pinning does nothing special with memory allocation like putting it into some
|
||||||
|
"read only" memory or anything fancy. It only tells the compiler that some
|
||||||
|
operations on this value should be forbidden.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Most standard library types implement <code>Unpin</code>. The same goes for most
|
||||||
|
"normal" types you encounter in Rust. <code>Futures</code> and <code>Generators</code> are two
|
||||||
|
exceptions.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>The main use case for <code>Pin</code> is to allow self referential types, the whole
|
||||||
|
justification for stabilizing them was to allow that. There are still corner
|
||||||
|
cases in the API which are being explored.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>The implementation behind objects that are <code>!Unpin</code> is most likely unsafe.
|
||||||
|
Moving such a type can cause the universe to crash. As of the time of writing
|
||||||
|
this book, creating and reading fields of a self referential struct still requires <code>unsafe</code>.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>You can add a <code>!Unpin</code> bound on a type on nightly with a feature flag, or
|
||||||
|
by adding <code>std::marker::PhantomPinned</code> to your type on stable.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>You can either pin a value to memory on the stack or on the heap.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Pinning a <code>!Unpin</code> pointer to the stack requires <code>unsafe</code></p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Pinning a <code>!Unpin</code> pointer to the heap does not require <code>unsafe</code>. There is a shortcut for doing this using <code>Box::pin</code>.</p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<blockquote>
|
||||||
|
<p>Unsafe code does not mean it's literally "unsafe", it only relieves the
|
||||||
|
guarantees you normally get from the compiler. An <code>unsafe</code> implementation can
|
||||||
|
be perfectly safe to do, but you have no safety net.</p>
|
||||||
|
</blockquote>
|
||||||
<h3><a class="header" href="#projectionstructural-pinning" id="projectionstructural-pinning">Projection/structural pinning</a></h3>
|
<h3><a class="header" href="#projectionstructural-pinning" id="projectionstructural-pinning">Projection/structural pinning</a></h3>
|
||||||
<p>In short, projection is a programming language term. <code>mystruct.field1</code> is a
|
<p>In short, projection is a programming language term. <code>mystruct.field1</code> is a
|
||||||
projection. Structural pinning is using <code>Pin</code> on fields. This has several
|
projection. Structural pinning is using <code>Pin</code> on fields. This has several
|
||||||
@@ -431,6 +650,130 @@ extra care must be taken when implementing <code>Drop</code> for pinned types.</
|
|||||||
<h2><a class="header" href="#putting-it-all-together" id="putting-it-all-together">Putting it all together</a></h2>
|
<h2><a class="header" href="#putting-it-all-together" id="putting-it-all-together">Putting it all together</a></h2>
|
||||||
<p>This is exactly what we'll do when we implement our own <code>Futures</code> stay tuned,
|
<p>This is exactly what we'll do when we implement our own <code>Futures</code> stay tuned,
|
||||||
we're soon finished.</p>
|
we're soon finished.</p>
|
||||||
|
<h2><a class="header" href="#bonus-section-fixing-our-self-referential-generator-and-learning-more-about-pin" id="bonus-section-fixing-our-self-referential-generator-and-learning-more-about-pin">Bonus section: Fixing our self-referential generator and learning more about Pin</a></h2>
|
||||||
|
<p>But now, let's prevent this problem using <code>Pin</code>. We'll discuss
|
||||||
|
<code>Pin</code> more in the next chapter, but you'll get an introduction here by just
|
||||||
|
reading the comments.</p>
|
||||||
|
<pre><pre class="playpen"><code class="language-rust">#![feature(optin_builtin_traits)] // needed to implement `!Unpin`
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let gen1 = GeneratorA::start();
|
||||||
|
let gen2 = GeneratorA::start();
|
||||||
|
// Before we pin the pointers, this is safe to do
|
||||||
|
// std::mem::swap(&mut gen, &mut gen2);
|
||||||
|
|
||||||
|
// constructing a `Pin::new()` on a type which does not implement `Unpin` is
|
||||||
|
// unsafe. A value pinned to heap can be constructed while staying in safe
|
||||||
|
// Rust so we can use that to avoid unsafe. You can also use crates like
|
||||||
|
// `pin_utils` to pin to the stack safely, just remember that they use
|
||||||
|
// unsafe under the hood so it's like using an already-reviewed unsafe
|
||||||
|
// implementation.
|
||||||
|
|
||||||
|
let mut pinned1 = Box::pin(gen1);
|
||||||
|
let mut pinned2 = Box::pin(gen2);
|
||||||
|
|
||||||
|
// Uncomment these if you think it's safe to pin the values to the stack instead
|
||||||
|
// (it is in this case). Remember to comment out the two previous lines first.
|
||||||
|
//let mut pinned1 = unsafe { Pin::new_unchecked(&mut gen1) };
|
||||||
|
//let mut pinned2 = unsafe { Pin::new_unchecked(&mut gen2) };
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This won't work:
|
||||||
|
// std::mem::swap(&mut gen, &mut gen2);
|
||||||
|
// This will work but will just swap the pointers so nothing bad happens here:
|
||||||
|
// std::mem::swap(&mut pinned1, &mut pinned2);
|
||||||
|
|
||||||
|
let _ = pinned1.as_mut().resume();
|
||||||
|
let _ = pinned2.as_mut().resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GeneratorState<Y, R> {
|
||||||
|
Yielded(Y),
|
||||||
|
Complete(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Generator {
|
||||||
|
type Yield;
|
||||||
|
type Return;
|
||||||
|
fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return>;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GeneratorA {
|
||||||
|
Enter,
|
||||||
|
Yield1 {
|
||||||
|
to_borrow: String,
|
||||||
|
borrowed: *const String,
|
||||||
|
},
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GeneratorA {
|
||||||
|
fn start() -> Self {
|
||||||
|
GeneratorA::Enter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tells us that the underlying pointer is not safe to move after pinning.
|
||||||
|
// In this case, only we as implementors "feel" this, however, if someone is
|
||||||
|
// relying on our Pinned pointer this will prevent them from moving it. You need
|
||||||
|
// to enable the feature flag `#![feature(optin_builtin_traits)]` and use the
|
||||||
|
// nightly compiler to implement `!Unpin`. Normally, you would use
|
||||||
|
// `std::marker::PhantomPinned` to indicate that the struct is `!Unpin`.
|
||||||
|
impl !Unpin for GeneratorA { }
|
||||||
|
|
||||||
|
impl Generator for GeneratorA {
|
||||||
|
type Yield = usize;
|
||||||
|
type Return = ();
|
||||||
|
fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return> {
|
||||||
|
// lets us get ownership over current state
|
||||||
|
let this = unsafe { self.get_unchecked_mut() };
|
||||||
|
match this {
|
||||||
|
GeneratorA::Enter => {
|
||||||
|
let to_borrow = String::from("Hello");
|
||||||
|
let borrowed = &to_borrow;
|
||||||
|
let res = borrowed.len();
|
||||||
|
*this = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
||||||
|
|
||||||
|
// Trick to actually get a self reference. We can't reference
|
||||||
|
// the `String` earlier since these references will point to the
|
||||||
|
// location in this stack frame which will not be valid anymore
|
||||||
|
// when this function returns.
|
||||||
|
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);
|
||||||
|
*this = GeneratorA::Exit;
|
||||||
|
GeneratorState::Complete(())
|
||||||
|
}
|
||||||
|
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</code></pre></pre>
|
||||||
|
<p>Now, as you see, the consumer of this API must either:</p>
|
||||||
|
<ol>
|
||||||
|
<li>Box the value and thereby allocating it on the heap</li>
|
||||||
|
<li>Use <code>unsafe</code> and pin the value to the stack. The user knows that if they move
|
||||||
|
the value afterwards it will violate the guarantee they promise to uphold when
|
||||||
|
they did their unsafe implementation.</li>
|
||||||
|
</ol>
|
||||||
|
<p>Hopefully, after this you'll have an idea of what happens when you use the
|
||||||
|
<code>yield</code> or <code>await</code> keywords inside an async function, and why we need <code>Pin</code> if
|
||||||
|
we want to be able to safely borrow across <code>yield/await</code> points.</p>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
/* Atelier-Dune Comment */
|
/* Atelier-Dune Comment */
|
||||||
.hljs-comment {
|
.hljs-comment {
|
||||||
color: rgb(255, 115, 0);;
|
color: rgba(34, 0, 155, 0.5);;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.hljs-quote {
|
.hljs-quote {
|
||||||
|
|||||||
1008
book/print.html
1008
book/print.html
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -90,12 +90,17 @@ runtime:
|
|||||||
|
|
||||||
## Green threads
|
## Green threads
|
||||||
|
|
||||||
Green threads has been popularized by GO in the recent years. Green threads
|
Green threads uses the same mechanism as an OS does by creating a thread for
|
||||||
uses the same basic technique as operating systems does to handle concurrency.
|
each task, setting up a stack, save the CPU's state and jump from one
|
||||||
|
task(thread) to another by doing a "context switch".
|
||||||
|
|
||||||
Green threads are implemented by setting up a stack for each task you want to
|
We yield control to the scheduler (which is a central part of the runtime in
|
||||||
execute and make the CPU "jump" from one stack to another to switch between
|
such a system) which then continues running a different task.
|
||||||
tasks.
|
|
||||||
|
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 async, await, Futures or Pin. All this would be implementation details
|
||||||
|
for the library.
|
||||||
|
|
||||||
The typical flow will be like this:
|
The typical flow will be like this:
|
||||||
|
|
||||||
@@ -105,7 +110,7 @@ The typical flow will be like this:
|
|||||||
"jumps" to that stack
|
"jumps" to that stack
|
||||||
4. Run some non-blocking code on the new thread until a new blocking call or the
|
4. Run some non-blocking code on the new thread until a new blocking call or the
|
||||||
task is finished
|
task is finished
|
||||||
5. "jumps" back to the "main" thread and so on
|
5. "jumps" back to the "main" thread, schedule a new thread to run and jump to that
|
||||||
|
|
||||||
These "jumps" are know as context switches. Your OS is doing it many times each
|
These "jumps" are know as context switches. Your OS is doing it many times each
|
||||||
second as you read this.
|
second as you read this.
|
||||||
@@ -529,7 +534,7 @@ async function run() {
|
|||||||
You can consider the `run` function a _pausable_ task consisting of several
|
You can consider the `run` function a _pausable_ task consisting of several
|
||||||
sub-tasks. On each "await" point it yields control to the scheduler (in this
|
sub-tasks. On each "await" point it yields control to the scheduler (in this
|
||||||
case it's the well known Javascript event loop). Once one of the sub-tasks changes
|
case it's the well known Javascript event loop). Once one of the sub-tasks changes
|
||||||
state to either `fulfilled` or `rejected` the task is sheduled to continue to
|
state to either `fulfilled` or `rejected` the task is scheduled to continue to
|
||||||
the next step.
|
the next step.
|
||||||
|
|
||||||
Syntactically, Rusts Futures 1.0 was a lot like the promises example above and
|
Syntactically, Rusts Futures 1.0 was a lot like the promises example above and
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ 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, in other languages
|
active choice when it comes to picking a runtime. Most often, in other languages
|
||||||
you'll just use the one provided for you.
|
you'll just use the one provided for you.
|
||||||
|
|
||||||
An async runtime can be divided into two parts:
|
**An async runtime can be divided into two parts:**
|
||||||
|
|
||||||
1. The Executor
|
1. The Executor
|
||||||
2. The Reactor
|
2. The Reactor
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Waker and Context
|
# Waker and Context
|
||||||
|
|
||||||
> **Relevant for:**
|
> **Overview:**
|
||||||
>
|
>
|
||||||
> - Understanding how the Waker object is constructed
|
> - Understanding how the Waker object is constructed
|
||||||
> - Learning how the runtime know when a leaf-future can resume
|
> - Learning how the runtime know when a leaf-future can resume
|
||||||
@@ -101,7 +101,7 @@ object from these parts:
|
|||||||
and try to run it. If you want to go back, press the undo symbol. Keep an eye
|
and try to run it. If you want to go back, press the undo symbol. Keep an eye
|
||||||
out for these as we go forward. Many examples will be editable.
|
out for these as we go forward. Many examples will be editable.
|
||||||
|
|
||||||
```rust, editable
|
```rust
|
||||||
// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr)
|
// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr)
|
||||||
trait Test {
|
trait Test {
|
||||||
fn add(&self) -> i32;
|
fn add(&self) -> i32;
|
||||||
@@ -160,7 +160,6 @@ fn main() {
|
|||||||
println!("Sub: 3 - 2 = {}", test.sub());
|
println!("Sub: 3 - 2 = {}", test.sub());
|
||||||
println!("Mul: 3 * 2 = {}", test.mul());
|
println!("Mul: 3 * 2 = {}", test.mul());
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now that you know this you also know why how we implement the `Waker` type
|
Now that you know this you also know why how we implement the `Waker` type
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Generators
|
# Generators
|
||||||
|
|
||||||
>**Relevant for:**
|
>**Overview:**
|
||||||
>
|
>
|
||||||
>- Understanding how the async/await syntax works since it's how `await` is implemented
|
>- Understandi how the async/await syntax works since it's how `await` is implemented
|
||||||
>- Knowing why we need `Pin`
|
>- Know why we need `Pin`
|
||||||
>- Understanding why Rusts async model is very efficient
|
>- Understand why Rusts async model is very efficient
|
||||||
>
|
>
|
||||||
>The motivation for `Generators` can be found in [RFC#2033][rfc2033]. It's very
|
>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
|
>well written and I can recommend reading through it (it talks as much about
|
||||||
@@ -22,21 +22,9 @@ handle concurrency:
|
|||||||
2. Using combinators.
|
2. Using combinators.
|
||||||
3. Stackless coroutines, better known as generators.
|
3. Stackless coroutines, better known as generators.
|
||||||
|
|
||||||
### Stackful coroutines/green threads
|
We covered [green threads in the background information](0_background_information.md#green-threads)
|
||||||
|
so we won't repeat that here. We'll concentrate on the variants of stackless
|
||||||
I've written about green threads before. Go check out
|
coroutines which Rust uses today.
|
||||||
[Green Threads Explained in 200 lines of Rust][greenthreads] if you're interested.
|
|
||||||
|
|
||||||
Green threads uses the same mechanism as an OS does by creating a thread for
|
|
||||||
each task, setting up a stack, save the CPU's state and jump from one
|
|
||||||
task(thread) to another by doing a "context switch".
|
|
||||||
|
|
||||||
We yield control to the scheduler (which is a central part of the runtime in
|
|
||||||
such a system) which then continues running a different task.
|
|
||||||
|
|
||||||
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 `async`, `await`, `Futures` or `Pin`. All this would be implementation details for the library.
|
|
||||||
|
|
||||||
### Combinators
|
### Combinators
|
||||||
|
|
||||||
@@ -93,10 +81,14 @@ async fn myfn() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Generators in Rust are implemented as state machines. The memory footprint of a
|
Async in Rust is implemented using Generators. So to understand how Async really
|
||||||
chain of computations is only defined by the largest footprint of any single
|
works we need to understand generators first. Generators in Rust are implemented
|
||||||
step require. That means that adding steps to a chain of computations might not
|
as state machines. The memory footprint of a chain of computations is only
|
||||||
require any increased memory at all.
|
defined by the largest footprint of what the largest step require.
|
||||||
|
|
||||||
|
That means that adding steps to a chain of computations might not require any
|
||||||
|
increased memory at all and it's one of the reasons why Futures and Async in
|
||||||
|
Rust has very little overhead.
|
||||||
|
|
||||||
## How generators work
|
## How generators work
|
||||||
|
|
||||||
@@ -104,7 +96,6 @@ In Nightly Rust today you can use the `yield` keyword. Basically using this
|
|||||||
keyword in a closure, converts it to a generator. A closure could look like this
|
keyword in a closure, converts it to a generator. A closure could look like this
|
||||||
before we had a concept of `Pin`:
|
before we had a concept of `Pin`:
|
||||||
|
|
||||||
|
|
||||||
```rust,noplaypen,ignore
|
```rust,noplaypen,ignore
|
||||||
#![feature(generators, generator_trait)]
|
#![feature(generators, generator_trait)]
|
||||||
use std::ops::{Generator, GeneratorState};
|
use std::ops::{Generator, GeneratorState};
|
||||||
@@ -176,19 +167,17 @@ impl Generator for GeneratorA {
|
|||||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||||
GeneratorA::Enter(a1) => {
|
GeneratorA::Enter(a1) => {
|
||||||
|
|
||||||
/*|---code before yield---|*/
|
/*----code before yield----*/
|
||||||
/*|*/ println!("Hello"); /*|*/
|
println!("Hello");
|
||||||
/*|*/ let a = a1 * 2; /*|*/
|
let a = a1 * 2;
|
||||||
/*|------------------------|*/
|
|
||||||
|
|
||||||
*self = GeneratorA::Yield1(a);
|
*self = GeneratorA::Yield1(a);
|
||||||
GeneratorState::Yielded(a)
|
GeneratorState::Yielded(a)
|
||||||
}
|
}
|
||||||
GeneratorA::Yield1(_) => {
|
|
||||||
|
|
||||||
/*|----code after yield----|*/
|
GeneratorA::Yield1(_) => {
|
||||||
/*|*/ println!("world!"); /*|*/
|
/*-----code after yield-----*/
|
||||||
/*|-------------------------|*/
|
println!("world!");
|
||||||
|
|
||||||
*self = GeneratorA::Exit;
|
*self = GeneratorA::Exit;
|
||||||
GeneratorState::Complete(())
|
GeneratorState::Complete(())
|
||||||
@@ -218,7 +207,7 @@ Instead of discussing it in theory, let's look at some code.
|
|||||||
> in depth explanation see [Tyler Mandry's excellent article: How Rust optimizes async/await][optimizing-await]
|
> in depth explanation see [Tyler Mandry's excellent article: How Rust optimizes async/await][optimizing-await]
|
||||||
|
|
||||||
```rust,noplaypen,ignore
|
```rust,noplaypen,ignore
|
||||||
let mut gen = move || {
|
let mut generator = move || {
|
||||||
let to_borrow = String::from("Hello");
|
let to_borrow = String::from("Hello");
|
||||||
let borrowed = &to_borrow;
|
let borrowed = &to_borrow;
|
||||||
yield borrowed.len();
|
yield borrowed.len();
|
||||||
@@ -226,15 +215,28 @@ let mut gen = move || {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
We'll be hand-coding some versions of a state-machines representing a state
|
||||||
|
machine for the generator defined aboce.
|
||||||
|
|
||||||
|
We step through each step "manually" in every example, so it looks pretty
|
||||||
|
unfamiliar. We could add some syntactic sugar like implementing the `Iterator`
|
||||||
|
trait for our generators which would let us do this:
|
||||||
|
|
||||||
|
```rust, ingore
|
||||||
|
for val in generator {
|
||||||
|
println!("{}", val);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It's a pretty trivial change to make, but this chapter is already getting long.
|
||||||
|
Just keep this in the back of your head as we move forward.
|
||||||
|
|
||||||
Now what does our rewritten state machine look like with this example?
|
Now what does our rewritten state machine look like with this example?
|
||||||
|
|
||||||
```rust,compile_fail
|
```rust,compile_fail
|
||||||
# // 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> {
|
# enum GeneratorState<Y, R> {
|
||||||
# // originally called `CoResult`
|
# Yielded(Y),
|
||||||
# Yielded(Y), // originally called `Yield(Y)`
|
# Complete(R),
|
||||||
# Complete(R), // originally called `Return(R)`
|
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
# trait Generator {
|
# trait Generator {
|
||||||
@@ -266,7 +268,7 @@ impl Generator for GeneratorA {
|
|||||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||||
GeneratorA::Enter => {
|
GeneratorA::Enter => {
|
||||||
let to_borrow = String::from("Hello");
|
let to_borrow = String::from("Hello");
|
||||||
let borrowed = &to_borrow;
|
let borrowed = &to_borrow; // <--- NB!
|
||||||
let res = borrowed.len();
|
let res = borrowed.len();
|
||||||
|
|
||||||
*self = GeneratorA::Yield1 {to_borrow, borrowed};
|
*self = GeneratorA::Yield1 {to_borrow, borrowed};
|
||||||
@@ -298,31 +300,10 @@ into itself.
|
|||||||
|
|
||||||
As you'll notice, this compiles just fine!
|
As you'll notice, this compiles just fine!
|
||||||
|
|
||||||
```rust,editable
|
```rust, ignore
|
||||||
pub fn main() {
|
|
||||||
let mut gen = GeneratorA::start();
|
|
||||||
let mut gen2 = GeneratorA::start();
|
|
||||||
|
|
||||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
|
||||||
println!("Got value {}", n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you uncomment this, very bad things can happen. This is why we need `Pin`
|
|
||||||
// std::mem::swap(&mut gen, &mut gen2);
|
|
||||||
|
|
||||||
if let GeneratorState::Yielded(n) = gen2.resume() {
|
|
||||||
println!("Got value {}", n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if you uncomment `mem::swap`.. this should now start gen2.
|
|
||||||
if let GeneratorState::Complete(()) = gen.resume() {
|
|
||||||
()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GeneratorState<Y, R> {
|
enum GeneratorState<Y, R> {
|
||||||
Yielded(Y), // originally called `Yield(Y)`
|
Yielded(Y),
|
||||||
Complete(R), // originally called `Return(R)`
|
Complete(R),
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Generator {
|
trait Generator {
|
||||||
@@ -335,7 +316,7 @@ enum GeneratorA {
|
|||||||
Enter,
|
Enter,
|
||||||
Yield1 {
|
Yield1 {
|
||||||
to_borrow: String,
|
to_borrow: String,
|
||||||
borrowed: *const String, // Normally you'll see `std::ptr::NonNull` used instead of *ptr
|
borrowed: *const String,
|
||||||
},
|
},
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
@@ -349,20 +330,18 @@ impl Generator for GeneratorA {
|
|||||||
type Yield = usize;
|
type Yield = usize;
|
||||||
type Return = ();
|
type Return = ();
|
||||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||||
// lets us get ownership over current state
|
|
||||||
match self {
|
match self {
|
||||||
GeneratorA::Enter => {
|
GeneratorA::Enter => {
|
||||||
let to_borrow = String::from("Hello");
|
let to_borrow = String::from("Hello");
|
||||||
let borrowed = &to_borrow;
|
let borrowed = &to_borrow;
|
||||||
let res = borrowed.len();
|
let res = borrowed.len();
|
||||||
|
|
||||||
// Trick to actually get a self reference
|
|
||||||
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
||||||
match self {
|
|
||||||
GeneratorA::Yield1{to_borrow, borrowed} => *borrowed = to_borrow,
|
// We set the self-reference here
|
||||||
_ => unreachable!(),
|
if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
|
||||||
};
|
*borrowed = to_borrow;
|
||||||
|
}
|
||||||
|
|
||||||
GeneratorState::Yielded(res)
|
GeneratorState::Yielded(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,139 +357,186 @@ impl Generator for GeneratorA {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> Try to uncomment the line with `mem::swap` and see the results.
|
Remember that our example is the generator we crated which looked like this:
|
||||||
|
|
||||||
While the example above compiles just fine, we expose consumers of this this API
|
```rust,noplaypen,ignore
|
||||||
to both possible undefined behavior and other memory errors while using just safe
|
let mut gen = move || {
|
||||||
Rust. This is a big problem!
|
let to_borrow = String::from("Hello");
|
||||||
|
let borrowed = &to_borrow;
|
||||||
But now, let's prevent this problem using `Pin`. We'll discuss
|
yield borrowed.len();
|
||||||
`Pin` more in the next chapter, but you'll get an introduction here by just
|
println!("{} world!", borrowed);
|
||||||
reading the comments.
|
|
||||||
|
|
||||||
```rust,editable
|
|
||||||
#![feature(optin_builtin_traits)] // needed to implement `!Unpin`
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let gen1 = GeneratorA::start();
|
|
||||||
let gen2 = GeneratorA::start();
|
|
||||||
// Before we pin the pointers, this is safe to do
|
|
||||||
// std::mem::swap(&mut gen, &mut gen2);
|
|
||||||
|
|
||||||
// constructing a `Pin::new()` on a type which does not implement `Unpin` is unsafe.
|
|
||||||
// However, as you'll see in the start of the next chapter value pinned to
|
|
||||||
// heap can be constructed while staying in safe Rust so we can use
|
|
||||||
// that to avoid unsafe. You can also use crates like `pin_utils` to do
|
|
||||||
// this safely, just remember that they use unsafe under the hood so it's
|
|
||||||
// like using an already-reviewed unsafe implementation.
|
|
||||||
|
|
||||||
let mut pinned1 = Box::pin(gen1);
|
|
||||||
let mut pinned2 = Box::pin(gen2);
|
|
||||||
// Uncomment these if you think it's safe to pin the values to the stack instead
|
|
||||||
// (it is in this case). Remember to comment out the two previous lines first.
|
|
||||||
//let mut pinned1 = unsafe { Pin::new_unchecked(&mut gen1) };
|
|
||||||
//let mut pinned2 = unsafe { Pin::new_unchecked(&mut gen2) };
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// This won't work
|
|
||||||
// std::mem::swap(&mut gen, &mut gen2);
|
|
||||||
// This will work but will just swap the pointers. Nothing inherently bad happens here.
|
|
||||||
// std::mem::swap(&mut pinned1, &mut pinned2);
|
|
||||||
|
|
||||||
let _ = pinned1.as_mut().resume();
|
|
||||||
let _ = pinned2.as_mut().resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
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(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return>;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GeneratorA {
|
|
||||||
Enter,
|
|
||||||
Yield1 {
|
|
||||||
to_borrow: String,
|
|
||||||
borrowed: *const String, // Normally you'll see `std::ptr::NonNull` used instead of *ptr
|
|
||||||
},
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GeneratorA {
|
|
||||||
fn start() -> Self {
|
|
||||||
GeneratorA::Enter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This tells us that the underlying pointer is not safe to move after pinning. In this case,
|
|
||||||
// only we as implementors "feel" this, however, if someone is relying on our Pinned pointer
|
|
||||||
// this will prevent them from moving it. You need to enable the feature flag
|
|
||||||
// `#![feature(optin_builtin_traits)]` and use the nightly compiler to implement `!Unpin`.
|
|
||||||
// Normally, you would use `std::marker::PhantomPinned` to indicate that the
|
|
||||||
// struct is `!Unpin`.
|
|
||||||
impl !Unpin for GeneratorA { }
|
|
||||||
|
|
||||||
impl Generator for GeneratorA {
|
|
||||||
type Yield = usize;
|
|
||||||
type Return = ();
|
|
||||||
fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return> {
|
|
||||||
// lets us get ownership over current state
|
|
||||||
let this = unsafe { self.get_unchecked_mut() };
|
|
||||||
match this {
|
|
||||||
GeneratorA::Enter => {
|
|
||||||
let to_borrow = String::from("Hello");
|
|
||||||
let borrowed = &to_borrow;
|
|
||||||
let res = borrowed.len();
|
|
||||||
|
|
||||||
// Trick to actually get a self reference. We can't reference
|
|
||||||
// the `String` earlier since these references will point to the
|
|
||||||
// location in this stack frame which will not be valid anymore
|
|
||||||
// when this function returns.
|
|
||||||
*this = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
|
||||||
match this {
|
|
||||||
GeneratorA::Yield1{to_borrow, borrowed} => *borrowed = to_borrow,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
GeneratorState::Yielded(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
GeneratorA::Yield1 {borrowed, ..} => {
|
|
||||||
let borrowed: &String = unsafe {&**borrowed};
|
|
||||||
println!("{} world", borrowed);
|
|
||||||
*this = GeneratorA::Exit;
|
|
||||||
GeneratorState::Complete(())
|
|
||||||
}
|
|
||||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, as you see, the consumer of this API must either:
|
Below is an example of how we could run this state-machine. But there is still
|
||||||
|
one huge problem with this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn main() {
|
||||||
|
let mut gen = GeneratorA::start();
|
||||||
|
let mut gen2 = GeneratorA::start();
|
||||||
|
|
||||||
|
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||||
|
println!("Got value {}", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let GeneratorState::Yielded(n) = gen2.resume() {
|
||||||
|
println!("Got value {}", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let GeneratorState::Complete(()) = gen.resume() {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
# enum GeneratorState<Y, R> {
|
||||||
|
# Yielded(Y),
|
||||||
|
# Complete(R),
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# trait Generator {
|
||||||
|
# type Yield;
|
||||||
|
# type Return;
|
||||||
|
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# enum GeneratorA {
|
||||||
|
# Enter,
|
||||||
|
# Yield1 {
|
||||||
|
# to_borrow: String,
|
||||||
|
# borrowed: *const String,
|
||||||
|
# },
|
||||||
|
# Exit,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl GeneratorA {
|
||||||
|
# fn start() -> Self {
|
||||||
|
# GeneratorA::Enter
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# impl Generator for GeneratorA {
|
||||||
|
# type Yield = usize;
|
||||||
|
# type Return = ();
|
||||||
|
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||||
|
# match self {
|
||||||
|
# GeneratorA::Enter => {
|
||||||
|
# let to_borrow = String::from("Hello");
|
||||||
|
# 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);
|
||||||
|
# *self = GeneratorA::Exit;
|
||||||
|
# GeneratorState::Complete(())
|
||||||
|
# }
|
||||||
|
# GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
The problem however is that in safe Rust we can still do this:
|
||||||
|
|
||||||
|
_Run the code and compare the results. Do you see the problem?_
|
||||||
|
```rust
|
||||||
|
pub fn main() {
|
||||||
|
let mut gen = GeneratorA::start();
|
||||||
|
let mut gen2 = GeneratorA::start();
|
||||||
|
|
||||||
|
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||||
|
println!("Got value {}", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mem::swap(&mut gen, &mut gen2); // <--- Big problem!
|
||||||
|
|
||||||
|
if let GeneratorState::Yielded(n) = gen2.resume() {
|
||||||
|
println!("Got value {}", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This would now start gen2 since we swapped them.
|
||||||
|
if let GeneratorState::Complete(()) = gen.resume() {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
# enum GeneratorState<Y, R> {
|
||||||
|
# Yielded(Y),
|
||||||
|
# Complete(R),
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# trait Generator {
|
||||||
|
# type Yield;
|
||||||
|
# type Return;
|
||||||
|
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# enum GeneratorA {
|
||||||
|
# Enter,
|
||||||
|
# Yield1 {
|
||||||
|
# to_borrow: String,
|
||||||
|
# borrowed: *const String,
|
||||||
|
# },
|
||||||
|
# Exit,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl GeneratorA {
|
||||||
|
# fn start() -> Self {
|
||||||
|
# GeneratorA::Enter
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# impl Generator for GeneratorA {
|
||||||
|
# type Yield = usize;
|
||||||
|
# type Return = ();
|
||||||
|
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||||
|
# match self {
|
||||||
|
# GeneratorA::Enter => {
|
||||||
|
# let to_borrow = String::from("Hello");
|
||||||
|
# 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);
|
||||||
|
# *self = GeneratorA::Exit;
|
||||||
|
# GeneratorState::Complete(())
|
||||||
|
# }
|
||||||
|
# GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
Wait? What happened to "Hello"?
|
||||||
|
|
||||||
|
Turns out that while the example above compiles
|
||||||
|
just fine, we expose consumers of this this API to both possible undefined
|
||||||
|
behavior and other memory errors while using just safe Rust. This is a big
|
||||||
|
problem!
|
||||||
|
|
||||||
|
We'll explain exactly what happened using a slightly simpler example in the next
|
||||||
|
chapter and we'll fix our generator using `Pin` so join me as we explore
|
||||||
|
the last topic before we implement our main Futures example.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1. Box the value and thereby allocating it on the heap
|
|
||||||
2. Use `unsafe` and pin the value to the stack. The user knows that if they move
|
|
||||||
the value afterwards it will violate the guarantee they promise to uphold when
|
|
||||||
they did their unsafe implementation.
|
|
||||||
|
|
||||||
Hopefully, after this you'll have an idea of what happens when you use the
|
|
||||||
`yield` or `await` keywords inside an async function, and why we need `Pin` if
|
|
||||||
we want to be able to safely borrow across `yield/await` points.
|
|
||||||
|
|
||||||
## Bonus section - self referential generators in Rust today
|
## Bonus section - self referential generators in Rust today
|
||||||
|
|
||||||
|
|||||||
585
src/4_pin.md
585
src/4_pin.md
@@ -1,98 +1,63 @@
|
|||||||
# Pin
|
# Pin
|
||||||
|
|
||||||
> **Relevant for**
|
> **Overview**
|
||||||
>
|
>
|
||||||
> 1. Understanding `Generators` and `Futures`
|
> 1. Learn how to use `Pin` and why it's required when implementing your own `Future`
|
||||||
> 2. Knowing how to use `Pin` is required when implementing your own `Future`
|
> 2. Understand how to make self-referential types safe to use in Rust
|
||||||
> 3. Understanding how to make self-referential types safe to use in Rust
|
> 3. Learn how borrowing across `await` points is accomplished
|
||||||
> 4. Learning how borrowing across `await` points is accomplished
|
> 4. Get a set of practical rules to help you work with `Pin`
|
||||||
>
|
>
|
||||||
> `Pin` was suggested in [RFC#2349][rfc2349]
|
> `Pin` was suggested in [RFC#2349][rfc2349]
|
||||||
|
|
||||||
We already got a brief introduction of `Pin` in the previous chapters, so we'll
|
Let's jump strait to it. Pinning is one of those subjects which is hard to wrap
|
||||||
start off without any further introduction.
|
your head around in the start, but once you unlock a mental model for it
|
||||||
|
it gets significantly easier to reason about.
|
||||||
Let's jump strait to some definitions and then create 10 rules to remember when
|
|
||||||
we work with `Pin`.
|
|
||||||
|
|
||||||
## Definitions
|
## Definitions
|
||||||
|
|
||||||
|
Pin is only relevant for pointers. A reference to an object is a pointer.
|
||||||
|
|
||||||
Pin consists of the `Pin` type and the `Unpin` marker. Pin's purpose in life is
|
Pin consists of the `Pin` type and the `Unpin` marker. Pin's purpose in life is
|
||||||
to govern the rules that need to apply for types which implement `!Unpin`.
|
to govern the rules that need to apply for types which implement `!Unpin`.
|
||||||
|
|
||||||
Pin is only relevant for pointers. A reference to an object is a pointer.
|
|
||||||
|
|
||||||
Yep, you're right, that's double negation right there. `!Unpin` means
|
Yep, you're right, that's double negation right there. `!Unpin` means
|
||||||
"not-un-pin".
|
"not-un-pin".
|
||||||
|
|
||||||
_This naming scheme is Rust deliberately testing if you're too tired to safely implement a type with this marker. If you're starting to get confused by
|
> _This naming scheme is one of Rusts safety features where it deliberately
|
||||||
`!Unpin` it's a good sign that it's time to lay down the work and start over
|
> tests if you're too tired to safely implement a type with this marker. If
|
||||||
tomorrow with a fresh mind._
|
> you're starting to get confused, or even angry, by `!Unpin` it's a good sign
|
||||||
|
> that it's time to lay down the work and start over tomorrow with a fresh mind._
|
||||||
|
|
||||||
> On a more serious note, I feel obliged to mention that there are valid reasons for the names
|
On a more serious note, I feel obliged to mention that there are valid reasons
|
||||||
> that were chosen. If you want to you can read a bit of the discussion from the
|
for the names that were chosen. Naming is not easy, and I considered renaming
|
||||||
> [internals thread][internals_unpin]. One of the best takeaways from there in my eyes
|
`Unpin` and `!Unpin` in this book to make them easier to reason about.
|
||||||
> is this quote from `tmandry`:
|
|
||||||
>
|
|
||||||
> _Think of taking a thumbtack out of a cork board so you can tweak how a flyer looks. For Unpin types, this unpinning is directly supported by the type; you can do this implicitly. You can even swap out the object with another before you put the pin back. For other types, you must be much more careful._
|
|
||||||
|
|
||||||
|
However, an experienced member of the Rust community convinced me that that there
|
||||||
|
is just too many nuances and edge-cases to consider which is easily overlooked when
|
||||||
|
naively giving these markers different names, and I'm convinced that we'll
|
||||||
|
just have to get used to them and use them as is.
|
||||||
|
|
||||||
For the next paragraph we'll rename these markers to:
|
If you want to you can read a bit of the discussion from the
|
||||||
|
[internals thread][internals_unpin]. One of the best takeaways from there in my
|
||||||
|
eyes is this quote from `tmandry`:
|
||||||
|
|
||||||
> `!Unpin` = `MustStay` and `Unpin` = `CanMove`
|
>_Think of taking a thumbtack out of a cork board so you can tweak how a flyer
|
||||||
|
looks. For Unpin types, this unpinning is directly supported by the type; you
|
||||||
|
can do this implicitly. You can even swap out the object with another before you
|
||||||
|
put the pin back. For other types, you must be much more careful._
|
||||||
|
|
||||||
It just makes it much easier to talk about them.
|
## Pinning and self-referential structs
|
||||||
|
|
||||||
## Rules to remember
|
Let's start where we left off in the last chapter by making the problem we
|
||||||
|
saw using a self-referential struct in our generator a lot simpler by making
|
||||||
|
some self-referential structs that are easier to reason about than our
|
||||||
|
state machines:
|
||||||
|
|
||||||
1. If `T: CanMove` (which is the default), then `Pin<'a, T>` is entirely equivalent to `&'a mut T`. in other words: `CanMove` means it's OK for this type to be moved even when pinned, so `Pin` will have no effect on such a type.
|
For now our example will look like this:
|
||||||
|
|
||||||
2. Getting a `&mut T` to a pinned pointer requires unsafe if `T: MustStay`. In other words: requiring a pinned pointer to a type which is `MustStay` prevents the _user_ of that API from moving that value unless it choses to write `unsafe` code.
|
```rust, ignore
|
||||||
|
|
||||||
3. Pinning does nothing special with memory allocation like putting it into some "read only" memory or anything fancy. It only tells the compiler that some operations on this value should be forbidden.
|
|
||||||
|
|
||||||
4. Most standard library types implement `CanMove`. The same goes for most
|
|
||||||
"normal" types you encounter in Rust. `Futures` and `Generators` are two
|
|
||||||
exceptions.
|
|
||||||
|
|
||||||
5. The main use case for `Pin` is to allow self referential types, the whole
|
|
||||||
justification for stabilizing them was to allow that. There are still corner
|
|
||||||
cases in the API which are being explored.
|
|
||||||
|
|
||||||
6. The implementation behind objects that are `MustStay` is most likely unsafe.
|
|
||||||
Moving such a type can cause the universe to crash. As of the time of writing
|
|
||||||
this book, creating and reading fields of a self referential struct still requires `unsafe`.
|
|
||||||
|
|
||||||
7. You can add a `MustStay` bound on a type on nightly with a feature flag, or
|
|
||||||
by adding `std::marker::PhantomPinned` to your type on stable.
|
|
||||||
|
|
||||||
8. You can either pin a value to memory on the stack or on the heap.
|
|
||||||
|
|
||||||
9. Pinning a `MustStay` pointer to the stack requires `unsafe`
|
|
||||||
|
|
||||||
10. Pinning a `MustStay` pointer to the heap does not require `unsafe`. There is a shortcut for doing this using `Box::pin`.
|
|
||||||
|
|
||||||
> Unsafe code does not mean it's literally "unsafe", it only relieves the
|
|
||||||
> guarantees you normally get from the compiler. An `unsafe` implementation can
|
|
||||||
> be perfectly safe to do, but you have no safety net.
|
|
||||||
|
|
||||||
Let's take a look at an example:
|
|
||||||
|
|
||||||
```rust,editable
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut test1 = Test::new("test1");
|
|
||||||
test1.init();
|
|
||||||
let mut test2 = Test::new("test2");
|
|
||||||
test2.init();
|
|
||||||
|
|
||||||
println!("a: {}, b: {}", test1.a(), test1.b());
|
|
||||||
std::mem::swap(&mut test1, &mut test2); // try commenting out this line
|
|
||||||
println!("a: {}, b: {}", test2.a(), test2.b());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Test {
|
struct Test {
|
||||||
a: String,
|
a: String,
|
||||||
@@ -107,7 +72,7 @@ impl Test {
|
|||||||
b: std::ptr::null(),
|
b: std::ptr::null(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self) {
|
fn init(&mut self) {
|
||||||
let self_ref: *const String = &self.a;
|
let self_ref: *const String = &self.a;
|
||||||
self.b = self_ref;
|
self.b = self_ref;
|
||||||
@@ -133,26 +98,177 @@ possible.
|
|||||||
`a` and `b`. Since `b` is a reference to `a` we store it as a pointer since
|
`a` and `b`. Since `b` is a reference to `a` we store it as a pointer since
|
||||||
the borrowing rules of Rust doesn't allow us to define this lifetime.
|
the borrowing rules of Rust doesn't allow us to define this lifetime.
|
||||||
|
|
||||||
|
Now, let's use this example to explain the problem we encounter in detail. As
|
||||||
|
you see, this works as expected:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let mut test1 = Test::new("test1");
|
||||||
|
test1.init();
|
||||||
|
let mut test2 = Test::new("test2");
|
||||||
|
test2.init();
|
||||||
|
|
||||||
|
println!("a: {}, b: {}", test1.a(), test1.b());
|
||||||
|
println!("a: {}, b: {}", test2.a(), test2.b());
|
||||||
|
|
||||||
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# // We need an `init` method to actually set our self-reference
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ref: *const String = &self.a;
|
||||||
|
# self.b = self_ref;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a(&self) -> &str {
|
||||||
|
# &self.a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b(&self) -> &String {
|
||||||
|
# unsafe {&*(self.b)}
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
In our main method we first instantiate two instances of `Test` and print out
|
In our main method we first instantiate two instances of `Test` and print out
|
||||||
the value of the fields on `test1`. We get:
|
the value of the fields on `test1`. We get what we'd expect:
|
||||||
|
|
||||||
```rust, ignore
|
```rust, ignore
|
||||||
a: test1, b: test1
|
a: test1, b: test1
|
||||||
|
a: test2, b: test2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Let's see what happens if we swap the data stored at the memory location
|
||||||
|
which `test1` is pointing to with the data stored at the memory location
|
||||||
|
`test2` is pointing to and vice a versa.
|
||||||
|
|
||||||
Next we swap the data stored at the memory location which `test1` is pointing to
|
```rust
|
||||||
with the data stored at the memory location `test2` is pointing to and vice a versa.
|
fn main() {
|
||||||
|
let mut test1 = Test::new("test1");
|
||||||
|
test1.init();
|
||||||
|
let mut test2 = Test::new("test2");
|
||||||
|
test2.init();
|
||||||
|
|
||||||
We should expect that printing the fields of `test2` should display the same as
|
println!("a: {}, b: {}", test1.a(), test1.b());
|
||||||
`test1` (since the object we printed before the swap has moved there now).
|
std::mem::swap(&mut test1, &mut test2);
|
||||||
|
println!("a: {}, b: {}", test2.a(), test2.b());
|
||||||
|
|
||||||
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ref: *const String = &self.a;
|
||||||
|
# self.b = self_ref;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a(&self) -> &str {
|
||||||
|
# &self.a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b(&self) -> &String {
|
||||||
|
# unsafe {&*(self.b)}
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
Naively, we could think that what we should get a debug print of `test1` two
|
||||||
|
times like this
|
||||||
|
|
||||||
```rust, ignore
|
```rust, ignore
|
||||||
|
a: test1, b: test1
|
||||||
|
a: test1, b: test1
|
||||||
|
```
|
||||||
|
|
||||||
|
But instead we get:
|
||||||
|
|
||||||
|
```rust, ignore
|
||||||
|
a: test1, b: test1
|
||||||
a: test1, b: test2
|
a: test1, b: test2
|
||||||
```
|
```
|
||||||
The pointer to `b` still points to the old location. That location is now
|
|
||||||
occupied with the string "test2". This can be a bit hard to visualize so I made
|
The pointer to `test2.b` still points to the old location which is inside `test1`
|
||||||
a figure that i hope can help.
|
now. The struct is not self-referential anymore, it holds a pointer to a field
|
||||||
|
in a different object. That means we can't rely on the lifetime of `test2.b` to
|
||||||
|
be tied to the lifetime of `test2` anymore.
|
||||||
|
|
||||||
|
If your still not convinced, this should at least convince you:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let mut test1 = Test::new("test1");
|
||||||
|
test1.init();
|
||||||
|
let mut test2 = Test::new("test2");
|
||||||
|
test2.init();
|
||||||
|
|
||||||
|
println!("a: {}, b: {}", test1.a(), test1.b());
|
||||||
|
std::mem::swap(&mut test1, &mut test2);
|
||||||
|
test1.a = "I've totally changed now!".to_string();
|
||||||
|
println!("a: {}, b: {}", test2.a(), test2.b());
|
||||||
|
|
||||||
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ref: *const String = &self.a;
|
||||||
|
# self.b = self_ref;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a(&self) -> &str {
|
||||||
|
# &self.a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b(&self) -> &String {
|
||||||
|
# unsafe {&*(self.b)}
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
That shouldn't happen. There is no serious error yet, but as you can imagine
|
||||||
|
it's easy to create serious bugs using this code.
|
||||||
|
|
||||||
|
I created a diagram to help visualize what's going on:
|
||||||
|
|
||||||
**Fig 1: Before and after swap**
|
**Fig 1: Before and after swap**
|
||||||

|

|
||||||
@@ -160,9 +276,12 @@ a figure that i hope can help.
|
|||||||
As you can see this results in unwanted behavior. It's easy to get this to
|
As you can see this results in unwanted behavior. It's easy to get this to
|
||||||
segfault, show UB and fail in other spectacular ways as well.
|
segfault, show UB and fail in other spectacular ways as well.
|
||||||
|
|
||||||
If we change the example to using `Pin` instead:
|
## Pinning to the stack
|
||||||
|
|
||||||
```rust,editable
|
Now, we can solve this problem by using `Pin` instead. Let's take a look at what
|
||||||
|
our example would look like then:
|
||||||
|
|
||||||
|
```rust, ignore
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::marker::PhantomPinned;
|
use std::marker::PhantomPinned;
|
||||||
|
|
||||||
@@ -197,7 +316,18 @@ impl Test {
|
|||||||
unsafe { &*(self.b) }
|
unsafe { &*(self.b) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, what we've done here is pinning a stack address. That will always be
|
||||||
|
`unsafe` if our type implements `!Unpin`.
|
||||||
|
|
||||||
|
We use the same tricks here, including requiring an `init`. If we want to fix that
|
||||||
|
and let users avoid `unsafe` we need to pin our data on the heap instead which
|
||||||
|
we'll show in a second.
|
||||||
|
|
||||||
|
Let's see what happens if we run our example now:
|
||||||
|
|
||||||
|
```rust
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let mut test1 = Test::new("test1");
|
let mut test1 = Test::new("test1");
|
||||||
test1.init();
|
test1.init();
|
||||||
@@ -206,36 +336,108 @@ pub fn main() {
|
|||||||
test2.init();
|
test2.init();
|
||||||
let mut test2_pin = unsafe { Pin::new_unchecked(&mut test2) };
|
let mut test2_pin = unsafe { Pin::new_unchecked(&mut test2) };
|
||||||
|
|
||||||
println!(
|
println!("a: {}, b: {}", Test::a(test1_pin.as_ref()), Test::b(test1_pin.as_ref()));
|
||||||
"a: {}, b: {}",
|
println!("a: {}, b: {}", Test::a(test2_pin.as_ref()), Test::b(test2_pin.as_ref()));
|
||||||
Test::a(test1_pin.as_ref()),
|
|
||||||
Test::b(test1_pin.as_ref())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Try to uncomment this and see what happens
|
|
||||||
// std::mem::swap(test1_pin.as_mut(), test2_pin.as_mut());
|
|
||||||
println!(
|
|
||||||
"a: {}, b: {}",
|
|
||||||
Test::a(test2_pin.as_ref()),
|
|
||||||
Test::b(test2_pin.as_ref())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# use std::marker::PhantomPinned;
|
||||||
|
#
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# _marker: PhantomPinned,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# // This makes our type `!Unpin`
|
||||||
|
# _marker: PhantomPinned,
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ptr: *const String = &self.a;
|
||||||
|
# self.b = self_ptr;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a<'a>(self: Pin<&'a Self>) -> &'a str {
|
||||||
|
# &self.get_ref().a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b<'a>(self: Pin<&'a Self>) -> &'a String {
|
||||||
|
# unsafe { &*(self.b) }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, what we've done here is pinning a stack address. That will always be
|
Now, if we try to pull the same trick which got us in to trouble the last time
|
||||||
`unsafe` if our type implements `!Unpin` (aka `MustStay`).
|
you'll get a compilation error. So t
|
||||||
|
|
||||||
We use some tricks here, including requiring an `init`. If we want to fix that
|
```rust, compile_fail
|
||||||
and let users avoid `unsafe` we need to pin our data on the heap instead.
|
pub fn main() {
|
||||||
|
let mut test1 = Test::new("test1");
|
||||||
|
test1.init();
|
||||||
|
let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
|
||||||
|
let mut test2 = Test::new("test2");
|
||||||
|
test2.init();
|
||||||
|
let mut test2_pin = unsafe { Pin::new_unchecked(&mut test2) };
|
||||||
|
|
||||||
> Stack pinning will always depend on the current stack frame we're in, so we
|
println!("a: {}, b: {}", Test::a(test1_pin.as_ref()), Test::b(test1_pin.as_ref()));
|
||||||
can't create a self referential object in one stack frame and return it since
|
std::mem::swap(test1_pin.as_mut(), test2_pin.as_mut());
|
||||||
any pointers we take to "self" is invalidated.
|
println!("a: {}, b: {}", Test::a(test2_pin.as_ref()), Test::b(test2_pin.as_ref()));
|
||||||
|
}
|
||||||
|
# use std::pin::Pin;
|
||||||
|
# use std::marker::PhantomPinned;
|
||||||
|
#
|
||||||
|
# #[derive(Debug)]
|
||||||
|
# struct Test {
|
||||||
|
# a: String,
|
||||||
|
# b: *const String,
|
||||||
|
# _marker: PhantomPinned,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# impl Test {
|
||||||
|
# fn new(txt: &str) -> Self {
|
||||||
|
# let a = String::from(txt);
|
||||||
|
# Test {
|
||||||
|
# a,
|
||||||
|
# b: std::ptr::null(),
|
||||||
|
# // This makes our type `!Unpin`
|
||||||
|
# _marker: PhantomPinned,
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# fn init(&mut self) {
|
||||||
|
# let self_ptr: *const String = &self.a;
|
||||||
|
# self.b = self_ptr;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn a<'a>(self: Pin<&'a Self>) -> &'a str {
|
||||||
|
# &self.get_ref().a
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn b<'a>(self: Pin<&'a Self>) -> &'a String {
|
||||||
|
# unsafe { &*(self.b) }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
The next example solves some of our friction at the cost of a heap allocation.
|
> It's important to note that stack pinning will always depend on the current
|
||||||
|
> stack frame we're in, so we can't create a self referential object in one
|
||||||
|
> stack frame and return it since any pointers we take to "self" is invalidated.
|
||||||
|
|
||||||
```rust, editbable
|
## Pinning to the heap
|
||||||
|
|
||||||
|
For completeness let's remove some unsafe and the need for an `init` method
|
||||||
|
at the cost of a heap allocation. Pinning to the heap is safe so the user
|
||||||
|
doesn't need to implement any unsafe code:
|
||||||
|
|
||||||
|
```rust
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::marker::PhantomPinned;
|
use std::marker::PhantomPinned;
|
||||||
|
|
||||||
@@ -275,9 +477,6 @@ pub fn main() {
|
|||||||
let mut test2 = Test::new("test2");
|
let mut test2 = Test::new("test2");
|
||||||
|
|
||||||
println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());
|
println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());
|
||||||
|
|
||||||
// Try to uncomment this and see what happens
|
|
||||||
// std::mem::swap(&mut test1, &mut test2);
|
|
||||||
println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());
|
println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -291,6 +490,47 @@ that the self-referential pointer stays valid.
|
|||||||
There are ways to safely give some guarantees on stack pinning as well, but right
|
There are ways to safely give some guarantees on stack pinning as well, but right
|
||||||
now you need to use a crate like [pin_project][pin_project] to do that.
|
now you need to use a crate like [pin_project][pin_project] to do that.
|
||||||
|
|
||||||
|
## Practical rules for Pinning
|
||||||
|
|
||||||
|
1. If `T: Unpin` (which is the default), then `Pin<'a, T>` is entirely
|
||||||
|
equivalent to `&'a mut T`. in other words: `Unpin` means it's OK for this type
|
||||||
|
to be moved even when pinned, so `Pin` will have no effect on such a type.
|
||||||
|
|
||||||
|
2. Getting a `&mut T` to a pinned pointer requires unsafe if `T: !Unpin`. In
|
||||||
|
other words: requiring a pinned pointer to a type which is `!Unpin` prevents
|
||||||
|
the _user_ of that API from moving that value unless it choses to write `unsafe`
|
||||||
|
code.
|
||||||
|
|
||||||
|
3. Pinning does nothing special with memory allocation like putting it into some
|
||||||
|
"read only" memory or anything fancy. It only tells the compiler that some
|
||||||
|
operations on this value should be forbidden.
|
||||||
|
|
||||||
|
4. Most standard library types implement `Unpin`. The same goes for most
|
||||||
|
"normal" types you encounter in Rust. `Futures` and `Generators` are two
|
||||||
|
exceptions.
|
||||||
|
|
||||||
|
5. The main use case for `Pin` is to allow self referential types, the whole
|
||||||
|
justification for stabilizing them was to allow that. There are still corner
|
||||||
|
cases in the API which are being explored.
|
||||||
|
|
||||||
|
6. The implementation behind objects that are `!Unpin` is most likely unsafe.
|
||||||
|
Moving such a type can cause the universe to crash. As of the time of writing
|
||||||
|
this book, creating and reading fields of a self referential struct still requires `unsafe`.
|
||||||
|
|
||||||
|
7. You can add a `!Unpin` bound on a type on nightly with a feature flag, or
|
||||||
|
by adding `std::marker::PhantomPinned` to your type on stable.
|
||||||
|
|
||||||
|
8. You can either pin a value to memory on the stack or on the heap.
|
||||||
|
|
||||||
|
9. Pinning a `!Unpin` pointer to the stack requires `unsafe`
|
||||||
|
|
||||||
|
10. Pinning a `!Unpin` pointer to the heap does not require `unsafe`. There is a shortcut for doing this using `Box::pin`.
|
||||||
|
|
||||||
|
> Unsafe code does not mean it's literally "unsafe", it only relieves the
|
||||||
|
> guarantees you normally get from the compiler. An `unsafe` implementation can
|
||||||
|
> be perfectly safe to do, but you have no safety net.
|
||||||
|
|
||||||
|
|
||||||
### Projection/structural pinning
|
### Projection/structural pinning
|
||||||
|
|
||||||
In short, projection is a programming language term. `mystruct.field1` is a
|
In short, projection is a programming language term. `mystruct.field1` is a
|
||||||
@@ -311,4 +551,133 @@ we're soon finished.
|
|||||||
|
|
||||||
[rfc2349]: https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md
|
[rfc2349]: https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md
|
||||||
[pin_project]: https://docs.rs/pin-project/
|
[pin_project]: https://docs.rs/pin-project/
|
||||||
[internals_unpin]: https://internals.rust-lang.org/t/naming-pin-anchor-move/6864/12
|
[internals_unpin]: https://internals.rust-lang.org/t/naming-pin-anchor-move/6864/12
|
||||||
|
|
||||||
|
## Bonus section: Fixing our self-referential generator and learning more about Pin
|
||||||
|
|
||||||
|
But now, let's prevent this problem using `Pin`. We'll discuss
|
||||||
|
`Pin` more in the next chapter, but you'll get an introduction here by just
|
||||||
|
reading the comments.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![feature(optin_builtin_traits)] // needed to implement `!Unpin`
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let gen1 = GeneratorA::start();
|
||||||
|
let gen2 = GeneratorA::start();
|
||||||
|
// Before we pin the pointers, this is safe to do
|
||||||
|
// std::mem::swap(&mut gen, &mut gen2);
|
||||||
|
|
||||||
|
// constructing a `Pin::new()` on a type which does not implement `Unpin` is
|
||||||
|
// unsafe. A value pinned to heap can be constructed while staying in safe
|
||||||
|
// Rust so we can use that to avoid unsafe. You can also use crates like
|
||||||
|
// `pin_utils` to pin to the stack safely, just remember that they use
|
||||||
|
// unsafe under the hood so it's like using an already-reviewed unsafe
|
||||||
|
// implementation.
|
||||||
|
|
||||||
|
let mut pinned1 = Box::pin(gen1);
|
||||||
|
let mut pinned2 = Box::pin(gen2);
|
||||||
|
|
||||||
|
// Uncomment these if you think it's safe to pin the values to the stack instead
|
||||||
|
// (it is in this case). Remember to comment out the two previous lines first.
|
||||||
|
//let mut pinned1 = unsafe { Pin::new_unchecked(&mut gen1) };
|
||||||
|
//let mut pinned2 = unsafe { Pin::new_unchecked(&mut gen2) };
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This won't work:
|
||||||
|
// std::mem::swap(&mut gen, &mut gen2);
|
||||||
|
// This will work but will just swap the pointers so nothing bad happens here:
|
||||||
|
// std::mem::swap(&mut pinned1, &mut pinned2);
|
||||||
|
|
||||||
|
let _ = pinned1.as_mut().resume();
|
||||||
|
let _ = pinned2.as_mut().resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GeneratorState<Y, R> {
|
||||||
|
Yielded(Y),
|
||||||
|
Complete(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Generator {
|
||||||
|
type Yield;
|
||||||
|
type Return;
|
||||||
|
fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return>;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GeneratorA {
|
||||||
|
Enter,
|
||||||
|
Yield1 {
|
||||||
|
to_borrow: String,
|
||||||
|
borrowed: *const String,
|
||||||
|
},
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GeneratorA {
|
||||||
|
fn start() -> Self {
|
||||||
|
GeneratorA::Enter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tells us that the underlying pointer is not safe to move after pinning.
|
||||||
|
// In this case, only we as implementors "feel" this, however, if someone is
|
||||||
|
// relying on our Pinned pointer this will prevent them from moving it. You need
|
||||||
|
// to enable the feature flag `#![feature(optin_builtin_traits)]` and use the
|
||||||
|
// nightly compiler to implement `!Unpin`. Normally, you would use
|
||||||
|
// `std::marker::PhantomPinned` to indicate that the struct is `!Unpin`.
|
||||||
|
impl !Unpin for GeneratorA { }
|
||||||
|
|
||||||
|
impl Generator for GeneratorA {
|
||||||
|
type Yield = usize;
|
||||||
|
type Return = ();
|
||||||
|
fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return> {
|
||||||
|
// lets us get ownership over current state
|
||||||
|
let this = unsafe { self.get_unchecked_mut() };
|
||||||
|
match this {
|
||||||
|
GeneratorA::Enter => {
|
||||||
|
let to_borrow = String::from("Hello");
|
||||||
|
let borrowed = &to_borrow;
|
||||||
|
let res = borrowed.len();
|
||||||
|
*this = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
||||||
|
|
||||||
|
// Trick to actually get a self reference. We can't reference
|
||||||
|
// the `String` earlier since these references will point to the
|
||||||
|
// location in this stack frame which will not be valid anymore
|
||||||
|
// when this function returns.
|
||||||
|
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);
|
||||||
|
*this = GeneratorA::Exit;
|
||||||
|
GeneratorState::Complete(())
|
||||||
|
}
|
||||||
|
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, as you see, the consumer of this API must either:
|
||||||
|
|
||||||
|
1. Box the value and thereby allocating it on the heap
|
||||||
|
2. Use `unsafe` and pin the value to the stack. The user knows that if they move
|
||||||
|
the value afterwards it will violate the guarantee they promise to uphold when
|
||||||
|
they did their unsafe implementation.
|
||||||
|
|
||||||
|
Hopefully, after this you'll have an idea of what happens when you use the
|
||||||
|
`yield` or `await` keywords inside an async function, and why we need `Pin` if
|
||||||
|
we want to be able to safely borrow across `yield/await` points.
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
/* Atelier-Dune Comment */
|
/* Atelier-Dune Comment */
|
||||||
.hljs-comment {
|
.hljs-comment {
|
||||||
color: rgb(255, 115, 0);;
|
color: rgba(34, 0, 155, 0.5);;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.hljs-quote {
|
.hljs-quote {
|
||||||
|
|||||||
Reference in New Issue
Block a user