Compare commits
186 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
098b953ad7 | ||
|
|
c831062743 | ||
|
|
f29aa474a2 | ||
|
|
8db646df5f | ||
|
|
8174b675ae | ||
|
|
df5613c87e | ||
|
|
ff8db7c549 | ||
|
|
2a5aa96677 | ||
|
|
9a26fdd854 | ||
|
|
fbfd42941c | ||
|
|
7640919aa4 | ||
|
|
954d73a385 | ||
|
|
dd5107a4e7 | ||
|
|
431e52c1fa | ||
|
|
66009ceab3 | ||
|
|
fd4619a1be | ||
|
|
8754f64baf | ||
|
|
11ef236afb | ||
|
|
f29048f969 | ||
|
|
00ffd295b7 | ||
|
|
250918b362 | ||
|
|
6adb910e51 | ||
|
|
8e1bb6bcd9 | ||
|
|
80d32c612d | ||
|
|
c898c47351 | ||
|
|
d2aaa3b213 | ||
|
|
fa959ac9a1 | ||
|
|
3def942a62 | ||
|
|
156ae48bbf | ||
|
|
6911e5a875 | ||
|
|
e4ac8419e4 | ||
|
|
9da948ef1d | ||
|
|
800e923824 | ||
|
|
2889b9d9a4 | ||
|
|
94cf575866 | ||
|
|
429c88c537 | ||
|
|
fe59cf6061 | ||
|
|
5d630f6201 | ||
|
|
21fa832c8c | ||
|
|
0b6ac51ec9 | ||
|
|
d06e85aa46 | ||
|
|
979d9d48dd | ||
|
|
ad1af303f4 | ||
|
|
1c22de4a4b | ||
|
|
351cd6c33e | ||
|
|
df6c6f98d4 | ||
|
|
8dca87526b | ||
|
|
146100264e | ||
|
|
064a9b8077 | ||
|
|
e1d7fa8d67 | ||
|
|
3b11b49410 | ||
|
|
a05cf73cd9 | ||
|
|
145d1b4e3a | ||
|
|
fdaf5ee00b | ||
|
|
b9e7242cf7 | ||
|
|
2a87978327 | ||
|
|
315599faf5 | ||
|
|
d551e74443 | ||
|
|
d31b7976d1 | ||
|
|
ee276abbd3 | ||
|
|
aa0423caaf | ||
|
|
b1e30646e6 | ||
|
|
e33b15af8b | ||
|
|
b085fdd966 | ||
|
|
b5ce4ff4c3 | ||
|
|
3a2d252468 | ||
|
|
ef4be98f4f | ||
|
|
05437bc6b5 | ||
|
|
7dc65df0f4 | ||
|
|
f38bc31bd3 | ||
|
|
4f4e6be15a | ||
|
|
a7ce98362a | ||
|
|
67aa39b8f6 | ||
|
|
3a1dca09fc | ||
|
|
94a194884a | ||
|
|
e4b84bdd83 | ||
|
|
760fd57be3 | ||
|
|
3ba348601a | ||
|
|
8dfd7f445c | ||
|
|
2128fc83d6 | ||
|
|
734763b969 | ||
|
|
527ec3835c | ||
|
|
b0ca76b3d5 | ||
|
|
4f275f1339 | ||
|
|
3ce63ffd13 | ||
|
|
467578773f | ||
|
|
90f16be8e4 | ||
|
|
6e75083b0e | ||
|
|
39169b737f | ||
|
|
65401cbaf3 | ||
|
|
c7bb485b8a | ||
|
|
30b6e394c6 | ||
|
|
6b41d2ff6d | ||
|
|
ac5d80aa64 | ||
|
|
18584400c7 | ||
|
|
92de09e917 | ||
|
|
aeb1c93449 | ||
|
|
5fc2932899 | ||
|
|
2d7d9f4680 | ||
|
|
07aab4e76b | ||
|
|
5da61651ba | ||
|
|
0d669a8034 | ||
|
|
41b28f466f | ||
|
|
786faf4fd9 | ||
|
|
7db0aaa991 | ||
|
|
f4deaaf87b | ||
|
|
a4c161c060 | ||
|
|
66d706b9e5 | ||
|
|
8689ac98cc | ||
|
|
ad5bc20676 | ||
|
|
a2f9535f3e | ||
|
|
fbef19b079 | ||
|
|
bd7e3c5572 | ||
|
|
2fc79a9e03 | ||
|
|
08b155698c | ||
|
|
d9eb756ef7 | ||
|
|
d2bcca7aa6 | ||
|
|
1520be032c | ||
|
|
6c38c20d16 | ||
|
|
3a31d08a9c | ||
|
|
3757194684 | ||
|
|
79a04ce025 | ||
|
|
f4b2029788 | ||
|
|
73363e0583 | ||
|
|
02bb33c6b6 | ||
|
|
9744403a6b | ||
|
|
8934c46679 | ||
|
|
4032c673e2 | ||
|
|
7d00580699 | ||
|
|
dbf8395bc0 | ||
|
|
32bedb934c | ||
|
|
08cda06ade | ||
|
|
39aecb264a | ||
|
|
9f2578f334 | ||
|
|
a90ff78349 | ||
|
|
882ea1bc2a | ||
|
|
9334463c06 | ||
|
|
f0a1d9a204 | ||
|
|
8dfc2f8ed3 | ||
|
|
331bf7e4df | ||
|
|
0d5934e8b4 | ||
|
|
a3ac3071df | ||
|
|
5947e07122 | ||
|
|
01181ce2a2 | ||
|
|
f99523aaf5 | ||
|
|
6e7a6bbc8d | ||
|
|
a840a1c3e2 | ||
|
|
90a0bed607 | ||
|
|
c31f162b37 | ||
|
|
7cfcd34d20 | ||
|
|
07cb377fc1 | ||
|
|
ad3f4ced09 | ||
|
|
1f4fd82851 | ||
|
|
2eb70ef351 | ||
|
|
578c22ce01 | ||
|
|
0e57808ac6 | ||
|
|
df7fe72386 | ||
|
|
16cd145661 | ||
|
|
9c2079c839 | ||
|
|
9b9b72afa5 | ||
|
|
310ee249fd | ||
|
|
12cfcb03c1 | ||
|
|
15d7c726f8 | ||
|
|
3a3ad1eeea | ||
|
|
3904912799 | ||
|
|
7a6f0592b0 | ||
|
|
83c0872af2 | ||
|
|
406804cebc | ||
|
|
6dd1e96751 | ||
|
|
9b2401b8a4 | ||
|
|
21db34c022 | ||
|
|
5ec0336d66 | ||
|
|
971288868b | ||
|
|
0d0c265dc7 | ||
|
|
c8cff655ec | ||
|
|
720cdfb36d | ||
|
|
8900666a55 | ||
|
|
7f7fe098f3 | ||
|
|
5c10caf9c5 | ||
|
|
8058f9fb22 | ||
|
|
aacb3683a0 | ||
|
|
15936dc004 | ||
|
|
70c4020059 | ||
|
|
9f2dd2af47 | ||
|
|
526921f5a6 | ||
|
|
a464846b73 |
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
./book
|
||||
book/
|
||||
./.vscode
|
||||
@@ -9,7 +9,7 @@ rust:
|
||||
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
|
||||
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "0.4.15" mdbook)
|
||||
- cargo install-update -a
|
||||
|
||||
script:
|
||||
@@ -22,4 +22,4 @@ deploy:
|
||||
local-dir: ./book
|
||||
keep-history: false
|
||||
on:
|
||||
branch: master
|
||||
branch: master
|
||||
|
||||
10
.vscode/settings.json
vendored
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"spellright.language": [],
|
||||
"spellright.language": [
|
||||
"en"
|
||||
],
|
||||
"spellright.documentTypes": [
|
||||
"latex",
|
||||
"plaintext"
|
||||
]
|
||||
"plaintext",
|
||||
"markdown"
|
||||
],
|
||||
"cSpell.enabled": true
|
||||
}
|
||||
64
README.md
@@ -1,12 +1,39 @@
|
||||

|
||||

|
||||
|
||||
# Futures Explained in 200 lines of Rust
|
||||
# Futures Explained in 200 Lines of Rust
|
||||
|
||||
This is the repositoru for the book: [Futures Explained in 200 Lines of Rust][rendered].
|
||||
The rendered version is found at: [https://cfsamson.github.io/books-futures-explained/](https://cfsamson.github.io/books-futures-explained/)
|
||||
|
||||
The book aims to explain `Futures` in Rust using an example driven approach, and
|
||||
the goal is to get a better understanding of `Futures` by implementing a toy
|
||||
`Reactor`, a very simple `Executor` and our own `Futures`.
|
||||
You can find the main example in the repository [examples-futures](https://github.com/cfsamson/examples-futures).
|
||||
|
||||
This book aims to explain `Futures` in Rust using an example driven approach,
|
||||
exploring why they're designed the way they are, and how they work. We'll also
|
||||
take a look at some of the alternatives we have when dealing with concurrency
|
||||
in programming.
|
||||
|
||||
Going into the level of detail I do in this book is not needed to use futures
|
||||
or async/await in Rust. It's for the curious out there that want to know _how_
|
||||
it all works.
|
||||
|
||||
## What this book covers
|
||||
|
||||
This book will try to explain everything you might wonder about up until the
|
||||
topic of different types of executors and runtimes. We'll just implement a very
|
||||
simple runtime in this book introducing some concepts but it's enough to get
|
||||
started.
|
||||
|
||||
[Stjepan Glavina](https://github.com/stjepang) has made an excellent series of
|
||||
articles about async runtimes and executors, and if the rumors are right there
|
||||
is more to come from him in the near future.
|
||||
|
||||
The way you should go about it is to read this book first, then continue
|
||||
reading the [articles from stejpang](https://web.archive.org/web/20200610130514/https://stjepang.github.io/) to learn more
|
||||
about runtimes and how they work, especially:
|
||||
|
||||
1. [Build your own block_on()](https://web.archive.org/web/20200511234503/https://stjepang.github.io/2020/01/25/build-your-own-block-on.html)
|
||||
2. [Build your own executor](https://web.archive.org/web/20200207092849/https://stjepang.github.io/2020/01/31/build-your-own-executor.html)
|
||||
|
||||
> Sorry for the archive links. The original site doesn't exist anymore.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -14,10 +41,31 @@ All kinds of contributions are welcome. Spelling, wording or clarifications are
|
||||
very welcome as well as adding or suggesting changes to the content. I'd appreciate
|
||||
if you contribute through a PR.
|
||||
|
||||
Questions or discussion is welcome in the issue tracker.
|
||||
The images in chapter 3 is created using Power Point. The power point itself is located in the
|
||||
"resources" folder.
|
||||
|
||||
Feedback, questions or discussion is welcome in the issue tracker.
|
||||
|
||||
## Changelog
|
||||
|
||||
**2020-04-06:** Final draft finished
|
||||
|
||||
**2020-04-10:** Rather substantial rewrite of the `Reactor` to better the
|
||||
readability and make it easier to reason about. In addition I fixed a mistake
|
||||
in the `Poll` method and a possible race condition. See #2 for more details.
|
||||
|
||||
**2020-04-13:** Added a "bonus section" to the [Implementing Futures chapter](https://cfsamson.github.io/books-futures-explained/6_future_example.html) where we avoid using `thread::park` and instead show how we
|
||||
can use a `Condvar` and a `Mutex` to create a proper `Parker`. Updated the [Finished Example](https://cfsamson.github.io/books-futures-explained/8_finished_example.html) to reflect these changes. Unfortunately, this led us
|
||||
a few lines over my initial promise of keeping the example below 200 LOC but the I think the inclusion
|
||||
is worth it.
|
||||
|
||||
**2020-12-21:** Rewrote the "Runtimes" paragraph of chapter 2 adding a useful model to understand
|
||||
how runtimes work and included a lot of the suggested text from @ckaran in #25. Added a new chapter
|
||||
"3 A mental model of how Futures work" which tries to visualize and give a good mental model to
|
||||
build upon.
|
||||
|
||||
## License
|
||||
|
||||
This book is MIT licensed.
|
||||
|
||||
[rendered]: https://cfsamson.github.io/books-futures-explained/
|
||||
[rendered]: https://cfsamson.github.io/books-futures-explained/
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This file makes sure that Github Pages doesn't process mdBook's output.
|
||||
@@ -1,317 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Some background information - Futures Explained in 200 Lines of Rust</title>
|
||||
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="1_background_information.html" class="active"><strong aria-hidden="true">1.</strong> Some background information</a></li><li><a href="2_trait_objects.html"><strong aria-hidden="true">2.</strong> Trait objects and fat pointers</a></li><li><a href="3_generators_pin.html"><strong aria-hidden="true">3.</strong> Generators</a></li><li><a href="4_pin.html"><strong aria-hidden="true">4.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">5.</strong> Futures - our main example</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">6.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div id="menu-bar" class="menu-bar">
|
||||
<div id="menu-bar-sticky-container">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#some-background-information" id="some-background-information">Some background information</a></h1>
|
||||
<blockquote>
|
||||
<p><strong>Relevant for:</strong></p>
|
||||
<ul>
|
||||
<li>High level introduction to concurrency in Rust</li>
|
||||
<li>Knowing what Rust provides and not when working with async code</li>
|
||||
<li>Understanding why we need runtimes </li>
|
||||
<li>Getting pointers to further reading on concurrency in general</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
<p>Before we start implementing our <code>Futures</code> , we'll go through some background
|
||||
information that will help demystify some of the concepts we encounter.</p>
|
||||
<p>Actually, after going through these concepts, implementing futures will seem
|
||||
pretty simple. I promise.</p>
|
||||
<h2><a class="header" href="#async-in-rust" id="async-in-rust">Async in Rust</a></h2>
|
||||
<p>Let's get some of the common roadblocks out of the way first.</p>
|
||||
<p>Async in Rust is different from most other languages in the sense that Rust
|
||||
has a very lightweight runtime.</p>
|
||||
<p>Languages like C#, JavaScript, Java and GO, already includes a runtime
|
||||
for handling concurrency. So if you come from one of those languages this will
|
||||
seem a bit strange to you.</p>
|
||||
<p>In Rust you will have to make an active choice about which runtime to use.</p>
|
||||
<h3><a class="header" href="#what-rusts-standard-library-takes-care-of" id="what-rusts-standard-library-takes-care-of">What Rust's standard library takes care of</a></h3>
|
||||
<ol>
|
||||
<li>The definition of an interruptible task</li>
|
||||
<li>An efficient technique to start, suspend, resume and store tasks which are
|
||||
executed concurrently.</li>
|
||||
<li>A defined way to wake up a suspended task</li>
|
||||
</ol>
|
||||
<p>That's really what Rusts standard library does. As you see there is no definition
|
||||
of non-blocking I/O, how these tasks are created or how they're run.</p>
|
||||
<h3><a class="header" href="#what-you-need-to-find-elsewhere" id="what-you-need-to-find-elsewhere">What you need to find elsewhere</a></h3>
|
||||
<p>A runtime. Well, in Rust we normally divide the runtime into two parts:</p>
|
||||
<ul>
|
||||
<li>The Reactor</li>
|
||||
<li>The Executor</li>
|
||||
</ul>
|
||||
<p>Reactors create leaf <code>Futures</code>, and provides things like non-blocking sockets,
|
||||
an event queue and so on.</p>
|
||||
<p>Executors, accepts one or more asynchronous tasks called <code>Futures</code> and takes
|
||||
care of actually running the code we write, suspend the tasks when they're
|
||||
waiting for I/O and resume them.</p>
|
||||
<p>In theory, we could choose one <code>Reactor</code> and one <code>Executor</code> that have nothing
|
||||
to do with each other besides that one creates leaf <code>Futures</code> and the other one
|
||||
runs them, but in reality today you'll most often get both in a <code>Runtime</code>.</p>
|
||||
<p>There are mainly two such runtimes today <a href="https://github.com/async-rs/async-std">async_std</a> and <a href="https://github.com/tokio-rs/tokio">tokio</a>.</p>
|
||||
<p>Quite a bit of complexity attributed to <code>Futures</code> are actually complexity rooted
|
||||
in runtimes. Creating an efficient runtime is hard. </p>
|
||||
<p>Learning how to use one correctly can require quite a bit of effort as well, but you'll see that there are several similarities between these kind of runtimes so
|
||||
learning one makes learning the next much easier.</p>
|
||||
<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 you'll just use
|
||||
the one provided for you.</p>
|
||||
<h2><a class="header" href="#futures-10-and-futures-30" id="futures-10-and-futures-30">Futures 1.0 and Futures 3.0</a></h2>
|
||||
<p>I'll not spend too much time on this, but it feels wrong to not mention that
|
||||
there have been several iterations on how async should work in Rust.</p>
|
||||
<p><code>Futures 3.0</code> works with the relatively new <code>async/await</code> syntax in Rust and
|
||||
it's what we'll learn.</p>
|
||||
<p>Now, since this is rather recent, you can encounter creates that use <code>Futures 1.0</code>
|
||||
still. This will get resolved in time, but unfortunately it's not always easy
|
||||
to know in advance.</p>
|
||||
<p>A good sign is that if you're required to use combinators like <code>and_then</code> then
|
||||
you're using <code>Futures 1.0</code>.</p>
|
||||
<p>While they're not directly compatible, there is a tool that let's you relatively
|
||||
easily convert a <code>Future 1.0</code> to a <code>Future 3.0</code> and vice a versa. You can find
|
||||
all you need in the <a href="https://github.com/rust-lang/futures-rs"><code>futures-rs</code></a> crate and all
|
||||
<a href="https://rust-lang.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html">information you need here</a>.</p>
|
||||
<h2><a class="header" href="#first-things-first" id="first-things-first">First things first</a></h2>
|
||||
<p>If you find the concepts of concurrency and async programming confusing in
|
||||
general, I know where you're coming from and I have written some resources to
|
||||
try to give a high level overview that will make it easier to learn Rusts
|
||||
<code>Futures</code> afterwards:</p>
|
||||
<ul>
|
||||
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/1_concurrent_vs_parallel.html">Async Basics - The difference between concurrency and parallelism</a></li>
|
||||
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/2_async_history.html">Async Basics - Async history</a></li>
|
||||
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/5_strategies_for_handling_io.html">Async Basics - Strategies for handling I/O</a></li>
|
||||
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/6_epoll_kqueue_iocp.html">Async Basics - Epoll, Kqueue and IOCP</a></li>
|
||||
</ul>
|
||||
<p>Learning these concepts by studying futures is making it much harder than
|
||||
it needs to be, so go on and read these chapters if you feel a bit unsure. </p>
|
||||
<p>I'll be right here when you're back.</p>
|
||||
<p>However, if you feel that you have the basics covered, then let's get moving!</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
||||
<a rel="prev" href="introduction.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a rel="next" href="2_trait_objects.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
|
||||
<a href="introduction.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a href="2_trait_objects.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-157536992-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,362 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Trait objects and fat pointers - Futures Explained in 200 Lines of Rust</title>
|
||||
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="1_background_information.html"><strong aria-hidden="true">1.</strong> Some background information</a></li><li><a href="2_trait_objects.html" class="active"><strong aria-hidden="true">2.</strong> Trait objects and fat pointers</a></li><li><a href="3_generators_pin.html"><strong aria-hidden="true">3.</strong> Generators</a></li><li><a href="4_pin.html"><strong aria-hidden="true">4.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">5.</strong> Futures - our main example</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">6.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div id="menu-bar" class="menu-bar">
|
||||
<div id="menu-bar-sticky-container">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#trait-objects-and-fat-pointers" id="trait-objects-and-fat-pointers">Trait objects and fat pointers</a></h1>
|
||||
<blockquote>
|
||||
<p><strong>Relevant for:</strong></p>
|
||||
<ul>
|
||||
<li>Understanding how the Waker object is constructed</li>
|
||||
<li>Getting a basic feel for "type erased" objects and what they are</li>
|
||||
<li>Learning the basics of dynamic dispatch</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
<h2><a class="header" href="#trait-objects-and-dynamic-dispatch" id="trait-objects-and-dynamic-dispatch">Trait objects and dynamic dispatch</a></h2>
|
||||
<p>One of the most confusing things we encounter when implementing our own <code>Futures</code>
|
||||
is how we implement a <code>Waker</code> . Creating a <code>Waker</code> involves creating a <code>vtable</code>
|
||||
which allows us to use dynamic dispatch to call methods on a <em>type erased</em> trait
|
||||
object we construct our selves.</p>
|
||||
<blockquote>
|
||||
<p>If you want to know more about dynamic dispatch in Rust I can recommend an article written by Adam Schwalm called <a href="https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/">Exploring Dynamic Dispatch in Rust</a>.</p>
|
||||
</blockquote>
|
||||
<p>Let's explain this a bit more in detail.</p>
|
||||
<h2><a class="header" href="#fat-pointers-in-rust" id="fat-pointers-in-rust">Fat pointers in Rust</a></h2>
|
||||
<p>Let's take a look at the size of some different pointer types in Rust. If we
|
||||
run the following code. <em>(You'll have to press "play" to see the output)</em>:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::mem::size_of;
|
||||
trait SomeTrait { }
|
||||
|
||||
fn main() {
|
||||
println!("======== The size of different pointers in Rust: ========");
|
||||
println!("&dyn Trait:-----{}", size_of::<&dyn SomeTrait>());
|
||||
println!("&[&dyn Trait]:--{}", size_of::<&[&dyn SomeTrait]>());
|
||||
println!("Box<Trait>:-----{}", size_of::<Box<SomeTrait>>());
|
||||
println!("&i32:-----------{}", size_of::<&i32>());
|
||||
println!("&[i32]:---------{}", size_of::<&[i32]>());
|
||||
println!("Box<i32>:-------{}", size_of::<Box<i32>>());
|
||||
println!("&Box<i32>:------{}", size_of::<&Box<i32>>());
|
||||
println!("[&dyn Trait;4]:-{}", size_of::<[&dyn SomeTrait; 4]>());
|
||||
println!("[i32;4]:--------{}", size_of::<[i32; 4]>());
|
||||
}
|
||||
</code></pre></pre>
|
||||
<p>As you see from the output after running this, the sizes of the references varies.
|
||||
Many are 8 bytes (which is a pointer size on 64 bit systems), but some are 16
|
||||
bytes.</p>
|
||||
<p>The 16 byte sized pointers are called "fat pointers" since they carry extra
|
||||
information.</p>
|
||||
<p><strong>Example <code>&[i32]</code> :</strong></p>
|
||||
<ul>
|
||||
<li>The first 8 bytes is the actual pointer to the first element in the array (or part of an array the slice refers to)</li>
|
||||
<li>The second 8 bytes is the length of the slice.</li>
|
||||
</ul>
|
||||
<p><strong>Example <code>&dyn SomeTrait</code>:</strong></p>
|
||||
<p>This is the type of fat pointer we'll concern ourselves about going forward.
|
||||
<code>&dyn SomeTrait</code> is a reference to a trait, or what Rust calls a <em>trait object</em>.</p>
|
||||
<p>The layout for a pointer to a <em>trait object</em> looks like this:</p>
|
||||
<ul>
|
||||
<li>The first 8 bytes points to the <code>data</code> for the trait object</li>
|
||||
<li>The second 8 bytes points to the <code>vtable</code> for the trait object</li>
|
||||
</ul>
|
||||
<p>The reason for this is to allow us to refer to an object we know nothing about
|
||||
except that it implements the methods defined by our trait. To accomplish this we use <em>dynamic dispatch</em>.</p>
|
||||
<p>Let's explain this in code instead of words by implementing our own trait
|
||||
object from these parts:</p>
|
||||
<blockquote>
|
||||
<p>This is an example of <em>editable</em> code. You can change everything in the example
|
||||
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>
|
||||
</blockquote>
|
||||
<pre><pre class="playpen"><code class="language-rust editable">// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr)
|
||||
trait Test {
|
||||
fn add(&self) -> i32;
|
||||
fn sub(&self) -> i32;
|
||||
fn mul(&self) -> i32;
|
||||
}
|
||||
|
||||
// This will represent our home brewn fat pointer to a trait object
|
||||
#[repr(C)]
|
||||
struct FatPointer<'a> {
|
||||
/// A reference is a pointer to an instantiated `Data` instance
|
||||
data: &'a mut Data,
|
||||
/// Since we need to pass in literal values like length and alignment it's
|
||||
/// easiest for us to convert pointers to usize-integers instead of the other way around.
|
||||
vtable: *const usize,
|
||||
}
|
||||
|
||||
// This is the data in our trait object. It's just two numbers we want to operate on.
|
||||
struct Data {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
// ====== function definitions ======
|
||||
fn add(s: &Data) -> i32 {
|
||||
s.a + s.b
|
||||
}
|
||||
fn sub(s: &Data) -> i32 {
|
||||
s.a - s.b
|
||||
}
|
||||
fn mul(s: &Data) -> i32 {
|
||||
s.a * s.b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut data = Data {a: 3, b: 2};
|
||||
// vtable is like special purpose array of pointer-length types with a fixed
|
||||
// format where the three first values has a special meaning like the
|
||||
// length of the array is encoded in the array itself as the second value.
|
||||
let vtable = vec![
|
||||
0, // pointer to `Drop` (which we're not implementing here)
|
||||
6, // lenght of vtable
|
||||
8, // alignment
|
||||
|
||||
// we need to make sure we add these in the same order as defined in the Trait.
|
||||
add as usize, // function pointer - try changing the order of `add`
|
||||
sub as usize, // function pointer - and `sub` to see what happens
|
||||
mul as usize, // function pointer
|
||||
];
|
||||
|
||||
let fat_pointer = FatPointer { data: &mut data, vtable: vtable.as_ptr()};
|
||||
let test = unsafe { std::mem::transmute::<FatPointer, &dyn Test>(fat_pointer) };
|
||||
|
||||
// And voalá, it's now a trait object we can call methods on
|
||||
println!("Add: 3 + 2 = {}", test.add());
|
||||
println!("Sub: 3 - 2 = {}", test.sub());
|
||||
println!("Mul: 3 * 2 = {}", test.mul());
|
||||
}
|
||||
|
||||
</code></pre></pre>
|
||||
<p>The reason we go through this will be clear later on when we implement our own
|
||||
<code>Waker</code> we'll actually set up a <code>vtable</code> like we do here to and knowing what
|
||||
it is will make this much less mysterious.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
||||
<a rel="prev" href="1_background_information.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a rel="next" href="3_generators_pin.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
|
||||
<a href="1_background_information.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a href="3_generators_pin.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-157536992-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,746 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Generators - Futures Explained in 200 Lines of Rust</title>
|
||||
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="1_background_information.html"><strong aria-hidden="true">1.</strong> Some background information</a></li><li><a href="2_trait_objects.html"><strong aria-hidden="true">2.</strong> Trait objects and fat pointers</a></li><li><a href="3_generators_pin.html" class="active"><strong aria-hidden="true">3.</strong> Generators</a></li><li><a href="4_pin.html"><strong aria-hidden="true">4.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">5.</strong> Futures - our main example</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">6.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div id="menu-bar" class="menu-bar">
|
||||
<div id="menu-bar-sticky-container">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#generators" id="generators">Generators</a></h1>
|
||||
<blockquote>
|
||||
<p><strong>Relevant for:</strong></p>
|
||||
<ul>
|
||||
<li>Understanding 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>Understanding why Rusts async model is very efficient</li>
|
||||
</ul>
|
||||
<p>The motivation for <code>Generators</code> can be found in <a href="https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md">RFC#2033</a>. It's very
|
||||
well written and I can recommend reading through it (it talks as much about
|
||||
async/await as it does about generators).</p>
|
||||
</blockquote>
|
||||
<p>The second difficult part is understanding Generators and the <code>Pin</code> type. Since
|
||||
they're related we'll start off by exploring generators first. By doing that
|
||||
we'll soon get to see why we need to be able to "pin" some data to a fixed
|
||||
location in memory and get an introduction to <code>Pin</code> as well.</p>
|
||||
<p>Basically, there were three main options discussed when designing how Rust would
|
||||
handle concurrency:</p>
|
||||
<ol>
|
||||
<li>Stackful coroutines, better known as green threads.</li>
|
||||
<li>Using combinators.</li>
|
||||
<li>Stackless coroutines, better known as generators.</li>
|
||||
</ol>
|
||||
<h3><a class="header" href="#stackful-coroutinesgreen-threads" id="stackful-coroutinesgreen-threads">Stackful coroutines/green threads</a></h3>
|
||||
<p>I've written about green threads before. Go check out
|
||||
<a href="https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/">Green Threads Explained in 200 lines of Rust</a> if you're interested.</p>
|
||||
<p>Green threads uses the same 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>
|
||||
<p><code>Futures 1.0</code> used combinators. If you've worked with <code>Promises</code> in JavaScript,
|
||||
you already know combinators. In Rust they look like this:</p>
|
||||
<pre><code class="language-rust noplaypen ignore">let future = Connection::connect(conn_str).and_then(|conn| {
|
||||
conn.query("somerequest").map(|row|{
|
||||
SomeStruct::from(row)
|
||||
}).collect::<Vec<SomeStruct>>()
|
||||
});
|
||||
|
||||
let rows: Result<Vec<SomeStruct>, SomeLibraryError> = block_on(future).unwrap();
|
||||
|
||||
</code></pre>
|
||||
<p>While an effective solution there are mainly three downsides I'll focus on:</p>
|
||||
<ol>
|
||||
<li>The error messages produced could be extremely long and arcane</li>
|
||||
<li>Not optimal memory usage</li>
|
||||
<li>Did not allow to borrow across combinator steps.</li>
|
||||
</ol>
|
||||
<p>Point #3, is actually a major drawback with <code>Futures 1.0</code>.</p>
|
||||
<p>Not allowing borrows across suspension points ends up being very
|
||||
un-ergonomic and to accomplish some tasks it requires extra allocations or
|
||||
copying which is inefficient.</p>
|
||||
<p>The reason for the higher than optimal memory usage is that this is basically
|
||||
a callback-based approach, where each closure stores all the data it needs
|
||||
for computation. This means that as we chain these, the memory required to store
|
||||
the needed state increases with each added step.</p>
|
||||
<h3><a class="header" href="#stackless-coroutinesgenerators" id="stackless-coroutinesgenerators">Stackless coroutines/generators</a></h3>
|
||||
<p>This is the model used in Rust today. It has a few notable advantages:</p>
|
||||
<ol>
|
||||
<li>It's easy to convert normal Rust code to a stackless coroutine using using
|
||||
async/await as keywords (it can even be done using a macro).</li>
|
||||
<li>No need for context switching and saving/restoring CPU state</li>
|
||||
<li>No need to handle dynamic stack allocation</li>
|
||||
<li>Very memory efficient</li>
|
||||
<li>Allows us to borrow across suspension points</li>
|
||||
</ol>
|
||||
<p>The last point is in contrast to <code>Futures 1.0</code>. With async/await we can do this:</p>
|
||||
<pre><code class="language-rust ignore">async fn myfn() {
|
||||
let text = String::from("Hello world");
|
||||
let borrowed = &text[0..5];
|
||||
somefuture.await;
|
||||
println!("{}", borrowed);
|
||||
}
|
||||
</code></pre>
|
||||
<p>Generators in Rust are implemented as state machines. The memory footprint of a
|
||||
chain of computations is only defined by the largest footprint of any single
|
||||
step require. That means that adding steps to a chain of computations might not
|
||||
require any increased memory at all.</p>
|
||||
<h2><a class="header" href="#how-generators-work" id="how-generators-work">How generators work</a></h2>
|
||||
<p>In Nightly Rust today you can use the <code>yield</code> keyword. Basically using this
|
||||
keyword in a closure, converts it to a generator. A closure could look like this
|
||||
before we had a concept of <code>Pin</code>:</p>
|
||||
<pre><code class="language-rust noplaypen ignore">#![feature(generators, generator_trait)]
|
||||
use std::ops::{Generator, GeneratorState};
|
||||
|
||||
fn main() {
|
||||
let a: i32 = 4;
|
||||
let mut gen = move || {
|
||||
println!("Hello");
|
||||
yield a * 2;
|
||||
println!("world!");
|
||||
};
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
}
|
||||
</code></pre>
|
||||
<p>Early on, before there was a consensus about the design of <code>Pin</code>, this
|
||||
compiled to something looking similar to this:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">fn main() {
|
||||
let mut gen = GeneratorA::start(4);
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
}
|
||||
|
||||
// If you've ever wondered why the parameters are called Y and R the naming from
|
||||
// the original rfc most likely holds the answer
|
||||
enum GeneratorState<Y, R> {
|
||||
Yielded(Y), // originally called `Yield(Y)`
|
||||
Complete(R), // originally called `Return(R)`
|
||||
}
|
||||
|
||||
trait Generator {
|
||||
type Yield;
|
||||
type Return;
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
}
|
||||
|
||||
enum GeneratorA {
|
||||
Enter(i32),
|
||||
Yield1(i32),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl GeneratorA {
|
||||
fn start(a1: i32) -> Self {
|
||||
GeneratorA::Enter(a1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator for GeneratorA {
|
||||
type Yield = i32;
|
||||
type Return = ();
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
// lets us get ownership over current state
|
||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||
GeneratorA::Enter(a1) => {
|
||||
|
||||
/*|---code before yield---|*/
|
||||
/*|*/ println!("Hello"); /*|*/
|
||||
/*|*/ let a = a1 * 2; /*|*/
|
||||
/*|------------------------|*/
|
||||
|
||||
*self = GeneratorA::Yield1(a);
|
||||
GeneratorState::Yielded(a)
|
||||
}
|
||||
GeneratorA::Yield1(_) => {
|
||||
|
||||
/*|----code after yield----|*/
|
||||
/*|*/ println!("world!"); /*|*/
|
||||
/*|-------------------------|*/
|
||||
|
||||
*self = GeneratorA::Exit;
|
||||
GeneratorState::Complete(())
|
||||
}
|
||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</code></pre></pre>
|
||||
<blockquote>
|
||||
<p>The <code>yield</code> keyword was discussed first in <a href="https://github.com/rust-lang/rfcs/pull/1823">RFC#1823</a> and in <a href="https://github.com/rust-lang/rfcs/pull/1832">RFC#1832</a>.</p>
|
||||
</blockquote>
|
||||
<p>Now that you know that the <code>yield</code> keyword in reality rewrites your code to become a state machine,
|
||||
you'll also know the basics of how <code>await</code> works. It's very similar.</p>
|
||||
<p>Now, there are some limitations in our naive state machine above. What happens when you have a
|
||||
<code>borrow</code> across a <code>yield</code> point?</p>
|
||||
<p>We could forbid that, but <strong>one of the major design goals for the async/await syntax has been
|
||||
to allow this</strong>. These kinds of borrows were not possible using <code>Futures 1.0</code> so we can't let this
|
||||
limitation just slip and call it a day yet.</p>
|
||||
<p>Instead of discussing it in theory, let's look at some code.</p>
|
||||
<blockquote>
|
||||
<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>
|
||||
</blockquote>
|
||||
<pre><code class="language-rust noplaypen ignore">let mut gen = move || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
</code></pre>
|
||||
<p>Now what does our rewritten state machine look like with this example?</p>
|
||||
<pre><pre class="playpen"><code class="language-rust compile_fail">
|
||||
# #![allow(unused_variables)]
|
||||
#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> {
|
||||
# // originally called `CoResult`
|
||||
# Yielded(Y), // originally called `Yield(Y)`
|
||||
# Complete(R), // originally called `Return(R)`
|
||||
# }
|
||||
#
|
||||
# trait Generator {
|
||||
# type Yield;
|
||||
# type Return;
|
||||
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
# }
|
||||
|
||||
enum GeneratorA {
|
||||
Enter,
|
||||
Yield1 {
|
||||
to_borrow: String,
|
||||
borrowed: &String, // uh, what lifetime should this have?
|
||||
},
|
||||
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> {
|
||||
// lets us get ownership over current state
|
||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||
GeneratorA::Enter => {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
let res = borrowed.len();
|
||||
|
||||
*self = GeneratorA::Yield1 {to_borrow, borrowed};
|
||||
GeneratorState::Yielded(res)
|
||||
}
|
||||
|
||||
GeneratorA::Yield1 {to_borrow, borrowed} => {
|
||||
println!("Hello {}", borrowed);
|
||||
*self = GeneratorA::Exit;
|
||||
GeneratorState::Complete(())
|
||||
}
|
||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>If you try to compile this you'll get an error (just try it yourself by pressing play).</p>
|
||||
<p>What is the lifetime of <code>&String</code>. It's not the same as the lifetime of <code>Self</code>. It's not <code>static</code>.
|
||||
Turns out that it's not possible for us in Rusts syntax to describe this lifetime, which means, that
|
||||
to make this work, we'll have to let the compiler know that <em>we</em> control this correctly ourselves.</p>
|
||||
<p>That means turning to unsafe.</p>
|
||||
<p>Let's try to write an implementation that will compiler using <code>unsafe</code>. As you'll
|
||||
see we end up in a <em>self referential struct</em>. A struct which holds references
|
||||
into itself.</p>
|
||||
<p>As you'll notice, this compiles just fine!</p>
|
||||
<pre><pre class="playpen"><code class="language-rust editable">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> {
|
||||
Yielded(Y), // originally called `Yield(Y)`
|
||||
Complete(R), // originally called `Return(R)`
|
||||
}
|
||||
|
||||
trait Generator {
|
||||
type Yield;
|
||||
type Return;
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
}
|
||||
|
||||
enum GeneratorA {
|
||||
Enter,
|
||||
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
|
||||
}
|
||||
}
|
||||
impl Generator for GeneratorA {
|
||||
type Yield = usize;
|
||||
type Return = ();
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
// lets us get ownership over current state
|
||||
match self {
|
||||
GeneratorA::Enter => {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
let res = borrowed.len();
|
||||
|
||||
// Trick to actually get a self reference
|
||||
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
||||
match self {
|
||||
GeneratorA::Yield1{to_borrow, borrowed} => *borrowed = to_borrow,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
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>
|
||||
<blockquote>
|
||||
<p>Try to uncomment the line with <code>mem::swap</code> and see the results.</p>
|
||||
</blockquote>
|
||||
<p>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>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);
|
||||
};
|
||||
|
||||
// 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!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>
|
||||
<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
|
||||
example in Rust today using the <code>static</code> keyword on nightly. Try it for
|
||||
yourself:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">#![feature(generators, generator_trait)]
|
||||
use std::ops::{Generator, GeneratorState};
|
||||
|
||||
|
||||
pub fn main() {
|
||||
let gen1 = static || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
|
||||
let gen2 = static || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
|
||||
let mut pinned1 = Box::pin(gen1);
|
||||
let mut pinned2 = Box::pin(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);
|
||||
};
|
||||
|
||||
let _ = pinned1.as_mut().resume();
|
||||
let _ = pinned2.as_mut().resume();
|
||||
}
|
||||
</code></pre></pre>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
||||
<a rel="prev" href="2_trait_objects.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a rel="next" href="4_pin.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
|
||||
<a href="2_trait_objects.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a href="4_pin.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-157536992-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
517
book/4_pin.html
@@ -1,517 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Pin - Futures Explained in 200 Lines of Rust</title>
|
||||
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="1_background_information.html"><strong aria-hidden="true">1.</strong> Some background information</a></li><li><a href="2_trait_objects.html"><strong aria-hidden="true">2.</strong> Trait objects and fat pointers</a></li><li><a href="3_generators_pin.html"><strong aria-hidden="true">3.</strong> Generators</a></li><li><a href="4_pin.html" class="active"><strong aria-hidden="true">4.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">5.</strong> Futures - our main example</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">6.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div id="menu-bar" class="menu-bar">
|
||||
<div id="menu-bar-sticky-container">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#pin" id="pin">Pin</a></h1>
|
||||
<blockquote>
|
||||
<p><strong>Relevant for</strong></p>
|
||||
<ol>
|
||||
<li>Understanding <code>Generators</code> and <code>Futures</code></li>
|
||||
<li>Knowing how to use <code>Pin</code> is required when implementing your own <code>Future</code></li>
|
||||
<li>Understanding how to make self-referential types safe to use in Rust</li>
|
||||
<li>Learning how borrowing across <code>await</code> points is accomplished</li>
|
||||
</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>
|
||||
</blockquote>
|
||||
<p>We already got a brief introduction of <code>Pin</code> in the previous chapters, so we'll
|
||||
start off without any further introduction.</p>
|
||||
<p>Let's jump strait to some definitions and then create 10 rules to remember when
|
||||
we work with <code>Pin</code>.</p>
|
||||
<h2><a class="header" href="#definitions" id="definitions">Definitions</a></h2>
|
||||
<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>
|
||||
<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
|
||||
"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>
|
||||
<p>On a more serious note, I feel obliged to mention that there are valid reasons for the names
|
||||
that were chosen. 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>
|
||||
<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>
|
||||
<p>For the next paragraph we'll rename these markers to:</p>
|
||||
<blockquote>
|
||||
<p><code>!Unpin</code> = <code>MustStay</code> and <code>Unpin</code> = <code>CanMove</code></p>
|
||||
</blockquote>
|
||||
<p>It just makes it much easier to talk about them.</p>
|
||||
<h2><a class="header" href="#rules-to-remember" id="rules-to-remember">Rules to remember</a></h2>
|
||||
<ol>
|
||||
<li>
|
||||
<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>
|
||||
</li>
|
||||
<li>
|
||||
<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)]
|
||||
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>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
|
||||
created which is strange but we'll need that to keep this example as short as
|
||||
possible.</p>
|
||||
<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
|
||||
the borrowing rules of Rust doesn't allow us to define this lifetime.</p>
|
||||
<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>
|
||||
<pre><code class="language-rust ignore">a: test1, b: test1
|
||||
</code></pre>
|
||||
<p>Next we swap the data stored at the memory location which <code>test1</code> is pointing to
|
||||
with the data stored at the memory location <code>test2</code> is pointing to and vice a versa.</p>
|
||||
<p>We should expect that printing the fields of <code>test2</code> should display the same as
|
||||
<code>test1</code> (since the object we printed before the swap has moved there now).</p>
|
||||
<pre><code class="language-rust ignore">a: test1, b: test2
|
||||
</code></pre>
|
||||
<p>The pointer to <code>b</code> 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
|
||||
a figure that i hope can help.</p>
|
||||
<p><strong>Fig 1: Before and after swap</strong>
|
||||
<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
|
||||
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>
|
||||
<pre><pre class="playpen"><code class="language-rust editable">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) }
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
);
|
||||
|
||||
// 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())
|
||||
);
|
||||
}
|
||||
|
||||
</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>
|
||||
<p>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.</p>
|
||||
</blockquote>
|
||||
<p>The next example solves some of our friction at the cost of a heap allocation.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust editbable">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) -> Pin<Box<Self>> {
|
||||
let a = String::from(txt);
|
||||
let t = Test {
|
||||
a,
|
||||
b: std::ptr::null(),
|
||||
_marker: PhantomPinned,
|
||||
};
|
||||
let mut boxed = Box::pin(t);
|
||||
let self_ptr: *const String = &boxed.as_ref().a;
|
||||
unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };
|
||||
|
||||
boxed
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let mut test1 = Test::new("test1");
|
||||
let mut test2 = Test::new("test2");
|
||||
|
||||
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());
|
||||
}
|
||||
</code></pre></pre>
|
||||
<p>The fact that boxing (heap allocating) a value that implements <code>!Unpin</code> is safe
|
||||
makes sense. Once the data is allocated on the heap it will have a stable address.</p>
|
||||
<p>There is no need for us as users of the API to take special care and ensure
|
||||
that the self-referential pointer stays valid.</p>
|
||||
<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>
|
||||
<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
|
||||
projection. Structural pinning is using <code>Pin</code> on fields. This has several
|
||||
caveats and is not something you'll normally see so I refer to the documentation
|
||||
for that.</p>
|
||||
<h3><a class="header" href="#pin-and-drop" id="pin-and-drop">Pin and Drop</a></h3>
|
||||
<p>The <code>Pin</code> guarantee exists from the moment the value is pinned until it's dropped.
|
||||
In the <code>Drop</code> implementation you take a mutable reference to <code>self</code>, which means
|
||||
extra care must be taken when implementing <code>Drop</code> for pinned types.</p>
|
||||
<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,
|
||||
we're soon finished.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
||||
<a rel="prev" href="3_generators_pin.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a rel="next" href="6_future_example.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
|
||||
<a href="3_generators_pin.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a href="6_future_example.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-157536992-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,439 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Finished example (editable) - Futures Explained in 200 Lines of Rust</title>
|
||||
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="1_background_information.html"><strong aria-hidden="true">1.</strong> Some background information</a></li><li><a href="2_trait_objects.html"><strong aria-hidden="true">2.</strong> Trait objects and fat pointers</a></li><li><a href="3_generators_pin.html"><strong aria-hidden="true">3.</strong> Generators</a></li><li><a href="4_pin.html"><strong aria-hidden="true">4.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">5.</strong> Futures - our main example</a></li><li><a href="8_finished_example.html" class="active"><strong aria-hidden="true">6.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div id="menu-bar" class="menu-bar">
|
||||
<div id="menu-bar-sticky-container">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#our-finished-code" id="our-finished-code">Our finished code</a></h1>
|
||||
<p>Here is the whole example. You can edit it right here in your browser and
|
||||
run it yourself. Have fun!</p>
|
||||
<pre><pre class="playpen"><code class="language-rust editable edition2018">use std::{
|
||||
future::Future, pin::Pin, sync::{mpsc::{channel, Sender}, Arc, Mutex},
|
||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
|
||||
thread::{self, JoinHandle}, time::{Duration, Instant}
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let start = Instant::now();
|
||||
let reactor = Reactor::new();
|
||||
let reactor = Arc::new(Mutex::new(reactor));
|
||||
let future1 = Task::new(reactor.clone(), 1, 1);
|
||||
let future2 = Task::new(reactor.clone(), 2, 2);
|
||||
|
||||
let fut1 = async {
|
||||
let val = future1.await;
|
||||
let dur = (Instant::now() - start).as_secs_f32();
|
||||
println!("Future got {} at time: {:.2}.", val, dur);
|
||||
};
|
||||
|
||||
let fut2 = async {
|
||||
let val = future2.await;
|
||||
let dur = (Instant::now() - start).as_secs_f32();
|
||||
println!("Future got {} at time: {:.2}.", val, dur);
|
||||
};
|
||||
|
||||
let mainfut = async {
|
||||
let handle1 = spawn(fut1);
|
||||
let handle2 = spawn(fut2);
|
||||
handle1.await;
|
||||
handle2.await;
|
||||
};
|
||||
|
||||
block_on(mainfut);
|
||||
reactor.lock().map(|mut r| r.close()).unwrap();
|
||||
}
|
||||
|
||||
// ============================= EXECUTOR ====================================
|
||||
fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
|
||||
let waker = waker_into_waker(Arc::into_raw(mywaker));
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
let val = loop {
|
||||
let pinned = unsafe { Pin::new_unchecked(&mut future) };
|
||||
match Future::poll(pinned, &mut cx) {
|
||||
Poll::Ready(val) => break val,
|
||||
Poll::Pending => thread::park(),
|
||||
};
|
||||
};
|
||||
val
|
||||
}
|
||||
|
||||
fn spawn<F: Future>(future: F) -> Pin<Box<F>> {
|
||||
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
|
||||
let waker = waker_into_waker(Arc::into_raw(mywaker));
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
let mut boxed = Box::pin(future);
|
||||
let _ = Future::poll(boxed.as_mut(), &mut cx);
|
||||
boxed
|
||||
}
|
||||
|
||||
// ====================== FUTURE IMPLEMENTATION ==============================
|
||||
#[derive(Clone)]
|
||||
struct MyWaker {
|
||||
thread: thread::Thread,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Task {
|
||||
id: usize,
|
||||
reactor: Arc<Mutex<Reactor>>,
|
||||
data: u64,
|
||||
is_registered: bool,
|
||||
}
|
||||
|
||||
fn mywaker_wake(s: &MyWaker) {
|
||||
let waker_ptr: *const MyWaker = s;
|
||||
let waker_arc = unsafe {Arc::from_raw(waker_ptr)};
|
||||
waker_arc.thread.unpark();
|
||||
}
|
||||
|
||||
fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
||||
let arc = unsafe { Arc::from_raw(s).clone() };
|
||||
std::mem::forget(arc.clone()); // increase ref count
|
||||
RawWaker::new(Arc::into_raw(arc) as *const (), &VTABLE)
|
||||
}
|
||||
|
||||
const VTABLE: RawWakerVTable = unsafe {
|
||||
RawWakerVTable::new(
|
||||
|s| mywaker_clone(&*(s as *const MyWaker)), // clone
|
||||
|s| mywaker_wake(&*(s as *const MyWaker)), // wake
|
||||
|s| mywaker_wake(*(s as *const &MyWaker)), // wake by ref
|
||||
|s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
|
||||
)
|
||||
};
|
||||
|
||||
fn waker_into_waker(s: *const MyWaker) -> Waker {
|
||||
let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
||||
unsafe { Waker::from_raw(raw_waker) }
|
||||
}
|
||||
|
||||
impl Task {
|
||||
fn new(reactor: Arc<Mutex<Reactor>>, data: u64, id: usize) -> Self {
|
||||
Task {
|
||||
id,
|
||||
reactor,
|
||||
data,
|
||||
is_registered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Task {
|
||||
type Output = usize;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut r = self.reactor.lock().unwrap();
|
||||
if r.is_ready(self.id) {
|
||||
Poll::Ready(self.id)
|
||||
} else if self.is_registered {
|
||||
Poll::Pending
|
||||
} else {
|
||||
r.register(self.data, cx.waker().clone(), self.id);
|
||||
drop(r);
|
||||
self.is_registered = true;
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================== REACTOR ===================================
|
||||
struct Reactor {
|
||||
dispatcher: Sender<Event>,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
readylist: Arc<Mutex<Vec<usize>>>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
enum Event {
|
||||
Close,
|
||||
Timeout(Waker, u64, usize),
|
||||
}
|
||||
|
||||
impl Reactor {
|
||||
fn new() -> Self {
|
||||
let (tx, rx) = channel::<Event>();
|
||||
let readylist = Arc::new(Mutex::new(vec![]));
|
||||
let rl_clone = readylist.clone();
|
||||
let mut handles = vec![];
|
||||
let handle = thread::spawn(move || {
|
||||
// This simulates some I/O resource
|
||||
for event in rx {
|
||||
println!("REACTOR: {:?}", event);
|
||||
let rl_clone = rl_clone.clone();
|
||||
match event {
|
||||
Event::Close => break,
|
||||
Event::Timeout(waker, duration, id) => {
|
||||
let event_handle = thread::spawn(move || {
|
||||
thread::sleep(Duration::from_secs(duration));
|
||||
rl_clone.lock().map(|mut rl| rl.push(id)).unwrap();
|
||||
waker.wake();
|
||||
});
|
||||
|
||||
handles.push(event_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
Reactor {
|
||||
readylist,
|
||||
dispatcher: tx,
|
||||
handle: Some(handle),
|
||||
}
|
||||
}
|
||||
|
||||
fn register(&mut self, duration: u64, waker: Waker, data: usize) {
|
||||
self.dispatcher
|
||||
.send(Event::Timeout(waker, duration, data))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.dispatcher.send(Event::Close).unwrap();
|
||||
}
|
||||
|
||||
fn is_ready(&self, id_to_check: usize) -> bool {
|
||||
self.readylist
|
||||
.lock()
|
||||
.map(|rl| rl.iter().any(|id| *id == id_to_check))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Reactor {
|
||||
fn drop(&mut self) {
|
||||
self.handle.take().map(|h| h.join().unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
</code></pre></pre>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
||||
<a rel="prev" href="6_future_example.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a rel="next" href="conclusion.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
|
||||
<a href="6_future_example.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<a href="conclusion.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-157536992-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
4
book/FontAwesome/css/font-awesome.css
vendored
|
Before Width: | Height: | Size: 434 KiB |
43
book/ace.js
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 165 KiB |
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
Based off of the Ayu theme
|
||||
Original by Dempfi (https://github.com/dempfi/ayu)
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
background: #191f26;
|
||||
color: #e6e1cf;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote,
|
||||
.hljs-meta {
|
||||
color: #5c6773;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-attr,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #ff7733;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #ffee99;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-bullet {
|
||||
color: #b8cc52;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-built_in,
|
||||
.hljs-section {
|
||||
color: #ffb454;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-symbol {
|
||||
color: #ff7733;
|
||||
}
|
||||
|
||||
.hljs-name {
|
||||
color: #36a3d9;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #00568d;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #91b362;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #d96c75;
|
||||
}
|
||||
620
book/book.js
@@ -1,620 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
// Fix back button cache problem
|
||||
window.onunload = function () { };
|
||||
|
||||
// Global variable, shared between modules
|
||||
function playpen_text(playpen) {
|
||||
let code_block = playpen.querySelector("code");
|
||||
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
let editor = window.ace.edit(code_block);
|
||||
return editor.getValue();
|
||||
} else {
|
||||
return code_block.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
(function codeSnippets() {
|
||||
// Hide Rust code lines prepended with a specific character
|
||||
var hiding_character = "#";
|
||||
|
||||
function fetch_with_timeout(url, options, timeout = 6000) {
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
|
||||
]);
|
||||
}
|
||||
|
||||
var playpens = Array.from(document.querySelectorAll(".playpen"));
|
||||
if (playpens.length > 0) {
|
||||
fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
|
||||
headers: {
|
||||
'Content-Type': "application/json",
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
// get list of crates available in the rust playground
|
||||
let playground_crates = response.crates.map(item => item["id"]);
|
||||
playpens.forEach(block => handle_crate_list_update(block, playground_crates));
|
||||
});
|
||||
}
|
||||
|
||||
function handle_crate_list_update(playpen_block, playground_crates) {
|
||||
// update the play buttons after receiving the response
|
||||
update_play_button(playpen_block, playground_crates);
|
||||
|
||||
// and install on change listener to dynamically update ACE editors
|
||||
if (window.ace) {
|
||||
let code_block = playpen_block.querySelector("code");
|
||||
if (code_block.classList.contains("editable")) {
|
||||
let editor = window.ace.edit(code_block);
|
||||
editor.addEventListener("change", function (e) {
|
||||
update_play_button(playpen_block, playground_crates);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updates the visibility of play button based on `no_run` class and
|
||||
// used crates vs ones available on http://play.rust-lang.org
|
||||
function update_play_button(pre_block, playground_crates) {
|
||||
var play_button = pre_block.querySelector(".play-button");
|
||||
|
||||
// skip if code is `no_run`
|
||||
if (pre_block.querySelector('code').classList.contains("no_run")) {
|
||||
play_button.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// get list of `extern crate`'s from snippet
|
||||
var txt = playpen_text(pre_block);
|
||||
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
|
||||
var snippet_crates = [];
|
||||
var item;
|
||||
while (item = re.exec(txt)) {
|
||||
snippet_crates.push(item[1]);
|
||||
}
|
||||
|
||||
// check if all used crates are available on play.rust-lang.org
|
||||
var all_available = snippet_crates.every(function (elem) {
|
||||
return playground_crates.indexOf(elem) > -1;
|
||||
});
|
||||
|
||||
if (all_available) {
|
||||
play_button.classList.remove("hidden");
|
||||
} else {
|
||||
play_button.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function run_rust_code(code_block) {
|
||||
var result_block = code_block.querySelector(".result");
|
||||
if (!result_block) {
|
||||
result_block = document.createElement('code');
|
||||
result_block.className = 'result hljs language-bash';
|
||||
|
||||
code_block.append(result_block);
|
||||
}
|
||||
|
||||
let text = playpen_text(code_block);
|
||||
let classes = code_block.querySelector('code').classList;
|
||||
let has_2018 = classes.contains("edition2018");
|
||||
let edition = has_2018 ? "2018" : "2015";
|
||||
|
||||
var params = {
|
||||
version: "stable",
|
||||
optimize: "0",
|
||||
code: text,
|
||||
edition: edition
|
||||
};
|
||||
|
||||
if (text.indexOf("#![feature") !== -1) {
|
||||
params.version = "nightly";
|
||||
}
|
||||
|
||||
result_block.innerText = "Running...";
|
||||
|
||||
fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
|
||||
headers: {
|
||||
'Content-Type': "application/json",
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => result_block.innerText = response.result)
|
||||
.catch(error => result_block.innerText = "Playground Communication: " + error.message);
|
||||
}
|
||||
|
||||
// Syntax highlighting Configuration
|
||||
hljs.configure({
|
||||
tabReplace: ' ', // 4 spaces
|
||||
languages: [], // Languages used for auto-detection
|
||||
});
|
||||
|
||||
if (window.ace) {
|
||||
// language-rust class needs to be removed for editable
|
||||
// blocks or highlightjs will capture events
|
||||
Array
|
||||
.from(document.querySelectorAll('code.editable'))
|
||||
.forEach(function (block) { block.classList.remove('language-rust'); });
|
||||
|
||||
Array
|
||||
.from(document.querySelectorAll('code:not(.editable)'))
|
||||
.forEach(function (block) { hljs.highlightBlock(block); });
|
||||
} else {
|
||||
Array
|
||||
.from(document.querySelectorAll('code'))
|
||||
.forEach(function (block) { hljs.highlightBlock(block); });
|
||||
}
|
||||
|
||||
// Adding the hljs class gives code blocks the color css
|
||||
// even if highlighting doesn't apply
|
||||
Array
|
||||
.from(document.querySelectorAll('code'))
|
||||
.forEach(function (block) { block.classList.add('hljs'); });
|
||||
|
||||
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
|
||||
|
||||
var code_block = block;
|
||||
var pre_block = block.parentNode;
|
||||
// hide lines
|
||||
var lines = code_block.innerHTML.split("\n");
|
||||
var first_non_hidden_line = false;
|
||||
var lines_hidden = false;
|
||||
var trimmed_line = "";
|
||||
|
||||
for (var n = 0; n < lines.length; n++) {
|
||||
trimmed_line = lines[n].trim();
|
||||
if (trimmed_line[0] == hiding_character && trimmed_line[1] != hiding_character) {
|
||||
if (first_non_hidden_line) {
|
||||
lines[n] = "<span class=\"hidden\">" + "\n" + lines[n].replace(/(\s*)# ?/, "$1") + "</span>";
|
||||
}
|
||||
else {
|
||||
lines[n] = "<span class=\"hidden\">" + lines[n].replace(/(\s*)# ?/, "$1") + "\n" + "</span>";
|
||||
}
|
||||
lines_hidden = true;
|
||||
}
|
||||
else if (first_non_hidden_line) {
|
||||
lines[n] = "\n" + lines[n];
|
||||
}
|
||||
else {
|
||||
first_non_hidden_line = true;
|
||||
}
|
||||
if (trimmed_line[0] == hiding_character && trimmed_line[1] == hiding_character) {
|
||||
lines[n] = lines[n].replace("##", "#")
|
||||
}
|
||||
}
|
||||
code_block.innerHTML = lines.join("");
|
||||
|
||||
// If no lines were hidden, return
|
||||
if (!lines_hidden) { return; }
|
||||
|
||||
var buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
buttons.innerHTML = "<button class=\"fa fa-expand\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
|
||||
|
||||
// add expand button
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
|
||||
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
|
||||
if (e.target.classList.contains('fa-expand')) {
|
||||
var lines = pre_block.querySelectorAll('span.hidden');
|
||||
|
||||
e.target.classList.remove('fa-expand');
|
||||
e.target.classList.add('fa-compress');
|
||||
e.target.title = 'Hide lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
Array.from(lines).forEach(function (line) {
|
||||
line.classList.remove('hidden');
|
||||
line.classList.add('unhidden');
|
||||
});
|
||||
} else if (e.target.classList.contains('fa-compress')) {
|
||||
var lines = pre_block.querySelectorAll('span.unhidden');
|
||||
|
||||
e.target.classList.remove('fa-compress');
|
||||
e.target.classList.add('fa-expand');
|
||||
e.target.title = 'Show hidden lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
Array.from(lines).forEach(function (line) {
|
||||
line.classList.remove('unhidden');
|
||||
line.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
|
||||
var pre_block = block.parentNode;
|
||||
if (!pre_block.classList.contains('playpen')) {
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var clipButton = document.createElement('button');
|
||||
clipButton.className = 'fa fa-copy clip-button';
|
||||
clipButton.title = 'Copy to clipboard';
|
||||
clipButton.setAttribute('aria-label', clipButton.title);
|
||||
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
|
||||
|
||||
buttons.insertBefore(clipButton, buttons.firstChild);
|
||||
}
|
||||
});
|
||||
|
||||
// Process playpen code blocks
|
||||
Array.from(document.querySelectorAll(".playpen")).forEach(function (pre_block) {
|
||||
// Add play button
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var runCodeButton = document.createElement('button');
|
||||
runCodeButton.className = 'fa fa-play play-button';
|
||||
runCodeButton.hidden = true;
|
||||
runCodeButton.title = 'Run this code';
|
||||
runCodeButton.setAttribute('aria-label', runCodeButton.title);
|
||||
|
||||
var copyCodeClipboardButton = document.createElement('button');
|
||||
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
|
||||
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
|
||||
copyCodeClipboardButton.title = 'Copy to clipboard';
|
||||
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
|
||||
|
||||
buttons.insertBefore(runCodeButton, buttons.firstChild);
|
||||
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
|
||||
|
||||
runCodeButton.addEventListener('click', function (e) {
|
||||
run_rust_code(pre_block);
|
||||
});
|
||||
|
||||
let code_block = pre_block.querySelector("code");
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
var undoChangesButton = document.createElement('button');
|
||||
undoChangesButton.className = 'fa fa-history reset-button';
|
||||
undoChangesButton.title = 'Undo changes';
|
||||
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
|
||||
|
||||
buttons.insertBefore(undoChangesButton, buttons.firstChild);
|
||||
|
||||
undoChangesButton.addEventListener('click', function () {
|
||||
let editor = window.ace.edit(code_block);
|
||||
editor.setValue(editor.originalCode);
|
||||
editor.clearSelection();
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function themes() {
|
||||
var html = document.querySelector('html');
|
||||
var themeToggleButton = document.getElementById('theme-toggle');
|
||||
var themePopup = document.getElementById('theme-list');
|
||||
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
|
||||
var stylesheets = {
|
||||
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
|
||||
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
|
||||
highlight: document.querySelector("[href$='highlight.css']"),
|
||||
};
|
||||
|
||||
function showThemes() {
|
||||
themePopup.style.display = 'block';
|
||||
themeToggleButton.setAttribute('aria-expanded', true);
|
||||
themePopup.querySelector("button#" + document.body.className).focus();
|
||||
}
|
||||
|
||||
function hideThemes() {
|
||||
themePopup.style.display = 'none';
|
||||
themeToggleButton.setAttribute('aria-expanded', false);
|
||||
themeToggleButton.focus();
|
||||
}
|
||||
|
||||
function set_theme(theme) {
|
||||
let ace_theme;
|
||||
|
||||
if (theme == 'coal' || theme == 'navy') {
|
||||
stylesheets.ayuHighlight.disabled = true;
|
||||
stylesheets.tomorrowNight.disabled = false;
|
||||
stylesheets.highlight.disabled = true;
|
||||
|
||||
ace_theme = "ace/theme/tomorrow_night";
|
||||
} else if (theme == 'ayu') {
|
||||
stylesheets.ayuHighlight.disabled = false;
|
||||
stylesheets.tomorrowNight.disabled = true;
|
||||
stylesheets.highlight.disabled = true;
|
||||
ace_theme = "ace/theme/tomorrow_night";
|
||||
} else {
|
||||
stylesheets.ayuHighlight.disabled = true;
|
||||
stylesheets.tomorrowNight.disabled = true;
|
||||
stylesheets.highlight.disabled = false;
|
||||
ace_theme = "ace/theme/dawn";
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
|
||||
}, 1);
|
||||
|
||||
if (window.ace && window.editors) {
|
||||
window.editors.forEach(function (editor) {
|
||||
editor.setTheme(ace_theme);
|
||||
});
|
||||
}
|
||||
|
||||
var previousTheme;
|
||||
try { previousTheme = localStorage.getItem('mdbook-theme'); } catch (e) { }
|
||||
if (previousTheme === null || previousTheme === undefined) { previousTheme = default_theme; }
|
||||
|
||||
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
|
||||
|
||||
document.body.className = theme;
|
||||
html.classList.remove(previousTheme);
|
||||
html.classList.add(theme);
|
||||
}
|
||||
|
||||
// Set theme
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
|
||||
set_theme(theme);
|
||||
|
||||
themeToggleButton.addEventListener('click', function () {
|
||||
if (themePopup.style.display === 'block') {
|
||||
hideThemes();
|
||||
} else {
|
||||
showThemes();
|
||||
}
|
||||
});
|
||||
|
||||
themePopup.addEventListener('click', function (e) {
|
||||
var theme = e.target.id || e.target.parentElement.id;
|
||||
set_theme(theme);
|
||||
});
|
||||
|
||||
themePopup.addEventListener('focusout', function(e) {
|
||||
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
|
||||
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang-nursery/mdBook/issues/628
|
||||
document.addEventListener('click', function(e) {
|
||||
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (!themePopup.contains(e.target)) { return; }
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
hideThemes();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.previousElementSibling) {
|
||||
li.previousElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.nextElementSibling) {
|
||||
li.nextElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:first-child button').focus();
|
||||
break;
|
||||
case 'End':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:last-child button').focus();
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function sidebar() {
|
||||
var html = document.querySelector("html");
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
var sidebarLinks = document.querySelectorAll('#sidebar a');
|
||||
var sidebarToggleButton = document.getElementById("sidebar-toggle");
|
||||
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
|
||||
var firstContact = null;
|
||||
|
||||
function showSidebar() {
|
||||
html.classList.remove('sidebar-hidden')
|
||||
html.classList.add('sidebar-visible');
|
||||
Array.from(sidebarLinks).forEach(function (link) {
|
||||
link.setAttribute('tabIndex', 0);
|
||||
});
|
||||
sidebarToggleButton.setAttribute('aria-expanded', true);
|
||||
sidebar.setAttribute('aria-hidden', false);
|
||||
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
|
||||
}
|
||||
|
||||
function hideSidebar() {
|
||||
html.classList.remove('sidebar-visible')
|
||||
html.classList.add('sidebar-hidden');
|
||||
Array.from(sidebarLinks).forEach(function (link) {
|
||||
link.setAttribute('tabIndex', -1);
|
||||
});
|
||||
sidebarToggleButton.setAttribute('aria-expanded', false);
|
||||
sidebar.setAttribute('aria-hidden', true);
|
||||
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
|
||||
}
|
||||
|
||||
// Toggle sidebar
|
||||
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
|
||||
if (html.classList.contains("sidebar-hidden")) {
|
||||
showSidebar();
|
||||
} else if (html.classList.contains("sidebar-visible")) {
|
||||
hideSidebar();
|
||||
} else {
|
||||
if (getComputedStyle(sidebar)['transform'] === 'none') {
|
||||
hideSidebar();
|
||||
} else {
|
||||
showSidebar();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sidebarResizeHandle.addEventListener('mousedown', initResize, false);
|
||||
|
||||
function initResize(e) {
|
||||
window.addEventListener('mousemove', resize, false);
|
||||
window.addEventListener('mouseup', stopResize, false);
|
||||
html.classList.add('sidebar-resizing');
|
||||
}
|
||||
function resize(e) {
|
||||
document.documentElement.style.setProperty('--sidebar-width', (e.clientX - sidebar.offsetLeft) + 'px');
|
||||
}
|
||||
//on mouseup remove windows functions mousemove & mouseup
|
||||
function stopResize(e) {
|
||||
html.classList.remove('sidebar-resizing');
|
||||
window.removeEventListener('mousemove', resize, false);
|
||||
window.removeEventListener('mouseup', stopResize, false);
|
||||
}
|
||||
|
||||
document.addEventListener('touchstart', function (e) {
|
||||
firstContact = {
|
||||
x: e.touches[0].clientX,
|
||||
time: Date.now()
|
||||
};
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener('touchmove', function (e) {
|
||||
if (!firstContact)
|
||||
return;
|
||||
|
||||
var curX = e.touches[0].clientX;
|
||||
var xDiff = curX - firstContact.x,
|
||||
tDiff = Date.now() - firstContact.time;
|
||||
|
||||
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
|
||||
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
|
||||
showSidebar();
|
||||
else if (xDiff < 0 && curX < 300)
|
||||
hideSidebar();
|
||||
|
||||
firstContact = null;
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
// Scroll sidebar to current active section
|
||||
var activeSection = sidebar.querySelector(".active");
|
||||
if (activeSection) {
|
||||
sidebar.scrollTop = activeSection.offsetTop;
|
||||
}
|
||||
})();
|
||||
|
||||
(function chapterNavigation() {
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (window.search && window.search.hasFocus()) { return; }
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
var nextButton = document.querySelector('.nav-chapters.next');
|
||||
if (nextButton) {
|
||||
window.location.href = nextButton.href;
|
||||
}
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
e.preventDefault();
|
||||
var previousButton = document.querySelector('.nav-chapters.previous');
|
||||
if (previousButton) {
|
||||
window.location.href = previousButton.href;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function clipboard() {
|
||||
var clipButtons = document.querySelectorAll('.clip-button');
|
||||
|
||||
function hideTooltip(elem) {
|
||||
elem.firstChild.innerText = "";
|
||||
elem.className = 'fa fa-copy clip-button';
|
||||
}
|
||||
|
||||
function showTooltip(elem, msg) {
|
||||
elem.firstChild.innerText = msg;
|
||||
elem.className = 'fa fa-copy tooltipped';
|
||||
}
|
||||
|
||||
var clipboardSnippets = new ClipboardJS('.clip-button', {
|
||||
text: function (trigger) {
|
||||
hideTooltip(trigger);
|
||||
let playpen = trigger.closest("pre");
|
||||
return playpen_text(playpen);
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(clipButtons).forEach(function (clipButton) {
|
||||
clipButton.addEventListener('mouseout', function (e) {
|
||||
hideTooltip(e.currentTarget);
|
||||
});
|
||||
});
|
||||
|
||||
clipboardSnippets.on('success', function (e) {
|
||||
e.clearSelection();
|
||||
showTooltip(e.trigger, "Copied!");
|
||||
});
|
||||
|
||||
clipboardSnippets.on('error', function (e) {
|
||||
showTooltip(e.trigger, "Clipboard error!");
|
||||
});
|
||||
})();
|
||||
|
||||
(function scrollToTop () {
|
||||
var menuTitle = document.querySelector('.menu-title');
|
||||
|
||||
menuTitle.addEventListener('click', function () {
|
||||
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
})();
|
||||
|
||||
(function autoHideMenu() {
|
||||
var menu = document.getElementById('menu-bar');
|
||||
|
||||
var previousScrollTop = document.scrollingElement.scrollTop;
|
||||
|
||||
document.addEventListener('scroll', function () {
|
||||
if (menu.classList.contains('folded') && document.scrollingElement.scrollTop < previousScrollTop) {
|
||||
menu.classList.remove('folded');
|
||||
} else if (!menu.classList.contains('folded') && document.scrollingElement.scrollTop > previousScrollTop) {
|
||||
menu.classList.add('folded');
|
||||
}
|
||||
|
||||
if (!menu.classList.contains('bordered') && document.scrollingElement.scrollTop > 0) {
|
||||
menu.classList.add('bordered');
|
||||
}
|
||||
|
||||
if (menu.classList.contains('bordered') && document.scrollingElement.scrollTop === 0) {
|
||||
menu.classList.remove('bordered');
|
||||
}
|
||||
|
||||
previousScrollTop = document.scrollingElement.scrollTop;
|
||||
}, { passive: true });
|
||||
})();
|
||||
7
book/clipboard.min.js
vendored
@@ -1,301 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Conclusion and exercises - Futures Explained in 200 Lines of Rust</title>
|
||||
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="1_background_information.html"><strong aria-hidden="true">1.</strong> Some background information</a></li><li><a href="2_trait_objects.html"><strong aria-hidden="true">2.</strong> Trait objects and fat pointers</a></li><li><a href="3_generators_pin.html"><strong aria-hidden="true">3.</strong> Generators</a></li><li><a href="4_pin.html"><strong aria-hidden="true">4.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">5.</strong> Futures - our main example</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">6.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html" class="active">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div id="menu-bar" class="menu-bar">
|
||||
<div id="menu-bar-sticky-container">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#conclusion-and-exercises" id="conclusion-and-exercises">Conclusion and exercises</a></h1>
|
||||
<p>Congratulations. Good job! If you got this far you must have stayed with me
|
||||
all the way. I hope you enjoyed the ride!</p>
|
||||
<p>I'll leave you with some predictions and a set of exercises I'm suggesting for
|
||||
those interested.</p>
|
||||
<p>Futures will be more ergonomic to use with time. For example, instead of having
|
||||
to create a <code>RawWaker</code> and so on, the <code>Waker</code> will also be possible to implement
|
||||
as a normal <code>Trait</code>. It's probably going to be pretty similar to
|
||||
<a href="https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.13/futures/task/trait.ArcWake.html">ArcWake</a>.</p>
|
||||
<p>There will probably be several more improvements like this, but since relatively
|
||||
few people will actually need implement leaf Futures compared to those that use
|
||||
them, focus will first and foremost be on how ergonomic it's to work with
|
||||
futures inside async/await functions and blocks.</p>
|
||||
<p>It will still take some time for the ecosystem to migrate over to <code>Futures 3.0</code>
|
||||
but since the advantages are so huge, it will not be a split between libraries
|
||||
using <code>Futures 1.0</code> and libraries using <code>Futures 3.0</code> for long.</p>
|
||||
<h1><a class="header" href="#reader-exercises" id="reader-exercises">Reader exercises</a></h1>
|
||||
<p>So our implementation has taken some obvious shortcuts and could use some improvement. Actually digging into the code and try things yourself is a good
|
||||
way to learn. Here are some good exercises if you want to explore more:</p>
|
||||
<h2><a class="header" href="#avoid-threadpark" id="avoid-threadpark">Avoid <code>thread::park</code></a></h2>
|
||||
<p>The big problem using <code>Thread::park</code> and <code>Thread::unpark</code> is that the user can access these same methods from their own code. Try to use another method to
|
||||
suspend our thread and wake it up again on our command. Some hints:</p>
|
||||
<ul>
|
||||
<li>Check out <code>CondVars</code>, here are two sources <a href="https://en.wikipedia.org/wiki/Monitor_(synchronization)#Condition_variables">Wikipedia</a> and the
|
||||
docs for <a href="https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html"><code>CondVar</code></a></li>
|
||||
<li>Take a look at crates that help you with this exact problem like <a href="https://github.com/crossbeam-rs/crossbeam">Crossbeam </a>(specifically the <a href="https://docs.rs/crossbeam/0.7.3/crossbeam/sync/struct.Parker.html"><code>Parker</code></a>)</li>
|
||||
</ul>
|
||||
<h2><a class="header" href="#avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around" id="avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around">Avoid wrapping the whole <code>Reactor</code> in a mutex and pass it around</a></h2>
|
||||
<p>First of all, protecting the whole <code>Reactor</code> and passing it around is overkill. We're only interested in synchronizing some parts of the information it contains. Try to refactor that out and only synchronize access to what's really needed.</p>
|
||||
<ul>
|
||||
<li>Do you want to pass around a reference to this information using an <code>Arc</code>?</li>
|
||||
<li>Do you want to make a global <code>Reactor</code> so it can be accessed from anywhere?</li>
|
||||
</ul>
|
||||
<p>Next , using a <code>Mutex</code> as a synchronization mechanism might be overkill since many methods only reads data. </p>
|
||||
<ul>
|
||||
<li>Could an <a href="https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html"><code>RwLock</code></a> be more efficient some places?</li>
|
||||
<li>Could you use any of the synchronization mechanisms in <a href="https://github.com/crossbeam-rs/crossbeam">Crossbeam</a>?</li>
|
||||
<li>Do you want to dig into <a href="https://cfsamsonbooks.gitbook.io/epoll-kqueue-iocp-explained/appendix-1/atomics-in-rust">atomics in Rust and implement a synchronization mechanism</a> of your own?</li>
|
||||
</ul>
|
||||
<h2><a class="header" href="#avoid-creating-a-new-waker-for-every-event" id="avoid-creating-a-new-waker-for-every-event">Avoid creating a new Waker for every event</a></h2>
|
||||
<p>Right now we create a new instance of a Waker for every event we create. Is this really needed? </p>
|
||||
<ul>
|
||||
<li>Could we create one instance and then cache it (see <a href="https://stjepang.github.io/2020/01/25/build-your-own-block-on.html">this article from <code>u/sjepang</code></a>)?
|
||||
<ul>
|
||||
<li>Should we cache it in <code>thread_local!</code> storage?</li>
|
||||
<li>Or should be cache it using a global constant?</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2><a class="header" href="#could-we-implement-more-methods-on-our-executor" id="could-we-implement-more-methods-on-our-executor">Could we implement more methods on our executor?</a></h2>
|
||||
<p>What about CPU intensive tasks? Right now they'll prevent our executor thread from progressing an handling events. Could you create a thread pool and create a method to send such tasks to the thread pool instead together with a Waker which will wake up the executor thread once the CPU intensive task is done?</p>
|
||||
<p>In both <code>async_std</code> and <code>tokio</code> this method is called <code>spawn_blocking</code>, a good place to start is to read the documentation and the code thy use to implement that.</p>
|
||||
<h2><a class="header" href="#building-a-better-exectuor" id="building-a-better-exectuor">Building a better exectuor</a></h2>
|
||||
<p>Right now, we can only run one and one future. Most runtimes has a <code>spawn</code>
|
||||
function which let's you start off a future and <code>await</code> it later so you
|
||||
can run multiple futures concurrently.</p>
|
||||
<p>As I'm writing this <a href="https://github.com/stjepang">@stjepan</a> is writing a blog
|
||||
series about implementing your own executors, and he just released a post
|
||||
on how to accomplish just this you can visit <a href="https://stjepang.github.io/2020/01/31/build-your-own-executor.html">here</a>.
|
||||
He knows what he's talking about so I recommend following that.</p>
|
||||
<p>In the <a href="https://github.com/cfsamson/examples-futures/tree/bonus_spawn">bonus_spawn</a>
|
||||
branch of the example repository you can also find an extremely simplified
|
||||
(and worse) way of accomplishing the same in only a few lines of code.</p>
|
||||
<h2><a class="header" href="#further-reading" id="further-reading">Further reading</a></h2>
|
||||
<p>There are many great resources for further study. In addition to the RFCs and
|
||||
articles I've already linked to in the book, here are some of my suggestions:</p>
|
||||
<p><a href="https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html">The official Asyc book</a></p>
|
||||
<p><a href="https://book.async.rs/">The async_std book</a></p>
|
||||
<p><a href="https://aturon.github.io/blog/2016/09/07/futures-design/">Aron Turon: Designing futures for Rust</a></p>
|
||||
<p><a href="https://www.infoq.com/presentations/rust-2019/">Steve Klabnik's presentation: Rust's journey to Async/Await</a></p>
|
||||
<p><a href="https://tokio.rs/blog/2019-10-scheduler/">The Tokio Blog</a></p>
|
||||
<p><a href="https://stjepang.github.io/">Stjepan's blog with a series where he implements an Executor</a></p>
|
||||
<p><a href="https://youtu.be/DkMwYxfSYNQ">Jon Gjengset's video on The Why, What and How of Pinning in Rust</a></p>
|
||||
<p><a href="https://boats.gitlab.io/blog/post/2018-01-25-async-i-self-referential-structs/">Withoutboats blog series about async/await</a></p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
||||
<a rel="prev" href="8_finished_example.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
|
||||
<a href="8_finished_example.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-157536992-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,451 +0,0 @@
|
||||
/* CSS for UI elements (a.k.a. chrome) */
|
||||
|
||||
@import 'variables.css';
|
||||
|
||||
::-webkit-scrollbar {
|
||||
background: var(--bg);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar);
|
||||
}
|
||||
|
||||
#searchresults a,
|
||||
.content a:link,
|
||||
a:visited,
|
||||
a > .hljs {
|
||||
color: var(--links);
|
||||
}
|
||||
|
||||
/* Menu Bar */
|
||||
|
||||
#menu-bar {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 101;
|
||||
margin: auto calc(0px - var(--page-padding));
|
||||
}
|
||||
#menu-bar > #menu-bar-sticky-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background-color: var(--bg);
|
||||
border-bottom-color: var(--bg);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
.js #menu-bar > #menu-bar-sticky-container {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
#menu-bar.bordered > #menu-bar-sticky-container {
|
||||
border-bottom-color: var(--table-border-color);
|
||||
}
|
||||
#menu-bar i, #menu-bar .icon-button {
|
||||
position: relative;
|
||||
padding: 0 8px;
|
||||
z-index: 10;
|
||||
line-height: 50px;
|
||||
cursor: pointer;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
@media only screen and (max-width: 420px) {
|
||||
#menu-bar i, #menu-bar .icon-button {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.icon-button i {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.right-buttons {
|
||||
margin: 0 15px;
|
||||
}
|
||||
.right-buttons a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container {
|
||||
transform: translateY(-60px);
|
||||
}
|
||||
|
||||
.left-buttons {
|
||||
display: flex;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.no-js .left-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
display: inline-block;
|
||||
font-weight: 200;
|
||||
font-size: 20px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.js .menu-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-bar,
|
||||
.menu-bar:visited,
|
||||
.nav-chapters,
|
||||
.nav-chapters:visited,
|
||||
.mobile-nav-chapters,
|
||||
.mobile-nav-chapters:visited,
|
||||
.menu-bar .icon-button,
|
||||
.menu-bar a i {
|
||||
color: var(--icons);
|
||||
}
|
||||
|
||||
.menu-bar i:hover,
|
||||
.menu-bar .icon-button:hover,
|
||||
.nav-chapters:hover,
|
||||
.mobile-nav-chapters i:hover {
|
||||
color: var(--icons-hover);
|
||||
}
|
||||
|
||||
/* Nav Icons */
|
||||
|
||||
.nav-chapters {
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
|
||||
position: fixed;
|
||||
top: 50px; /* Height of menu-bar */
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
max-width: 150px;
|
||||
min-width: 90px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
.nav-chapters:hover { text-decoration: none; }
|
||||
|
||||
.nav-wrapper {
|
||||
margin-top: 50px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-nav-chapters {
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
width: 90px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
.previous {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.next {
|
||||
float: right;
|
||||
right: var(--page-padding);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1080px) {
|
||||
.nav-wide-wrapper { display: none; }
|
||||
.nav-wrapper { display: block; }
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1380px) {
|
||||
.sidebar-visible .nav-wide-wrapper { display: none; }
|
||||
.sidebar-visible .nav-wrapper { display: block; }
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
|
||||
:not(pre) > .hljs {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:not(pre):not(a) > .hljs {
|
||||
color: var(--inline-code-color);
|
||||
overflow-x: initial;
|
||||
}
|
||||
|
||||
a:hover > .hljs {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
pre {
|
||||
position: relative;
|
||||
}
|
||||
pre > .buttons {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
|
||||
color: var(--sidebar-fg);
|
||||
cursor: pointer;
|
||||
}
|
||||
pre > .buttons :hover {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
pre > .buttons i {
|
||||
margin-left: 8px;
|
||||
}
|
||||
pre > .buttons button {
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: inherit;
|
||||
}
|
||||
pre > .result {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
|
||||
#searchresults a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
mark {
|
||||
border-radius: 2px;
|
||||
padding: 0 3px 1px 3px;
|
||||
margin: 0 -3px -1px -3px;
|
||||
background-color: var(--search-mark-bg);
|
||||
transition: background-color 300ms linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
mark.fade-out {
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.searchbar-outer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
|
||||
#searchbar {
|
||||
width: 100%;
|
||||
margin: 5px auto 0px auto;
|
||||
padding: 10px 16px;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
border: 1px solid var(--searchbar-border-color);
|
||||
border-radius: 3px;
|
||||
background-color: var(--searchbar-bg);
|
||||
color: var(--searchbar-fg);
|
||||
}
|
||||
#searchbar:focus,
|
||||
#searchbar.active {
|
||||
box-shadow: 0 0 3px var(--searchbar-shadow-color);
|
||||
}
|
||||
|
||||
.searchresults-header {
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
padding: 18px 0 0 5px;
|
||||
color: var(--searchresults-header-fg);
|
||||
}
|
||||
|
||||
.searchresults-outer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--content-max-width);
|
||||
border-bottom: 1px dashed var(--searchresults-border-color);
|
||||
}
|
||||
|
||||
ul#searchresults {
|
||||
list-style: none;
|
||||
padding-left: 20px;
|
||||
}
|
||||
ul#searchresults li {
|
||||
margin: 10px 0px;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
ul#searchresults li.focus {
|
||||
background-color: var(--searchresults-li-bg);
|
||||
}
|
||||
ul#searchresults span.teaser {
|
||||
display: block;
|
||||
clear: both;
|
||||
margin: 5px 0 0 20px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
ul#searchresults span.teaser em {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: var(--sidebar-width);
|
||||
font-size: 0.875em;
|
||||
box-sizing: border-box;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-y: contain;
|
||||
background-color: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
.sidebar-resizing {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.js:not(.sidebar-resizing) .sidebar {
|
||||
transition: transform 0.3s; /* Animation: slide away */
|
||||
}
|
||||
.sidebar code {
|
||||
line-height: 2em;
|
||||
}
|
||||
.sidebar .sidebar-scrollbox {
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 10px 10px;
|
||||
}
|
||||
.sidebar .sidebar-resize-handle {
|
||||
position: absolute;
|
||||
cursor: col-resize;
|
||||
width: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.js .sidebar .sidebar-resize-handle {
|
||||
cursor: col-resize;
|
||||
width: 5px;
|
||||
}
|
||||
.sidebar-hidden .sidebar {
|
||||
transform: translateX(calc(0px - var(--sidebar-width)));
|
||||
}
|
||||
.sidebar::-webkit-scrollbar {
|
||||
background: var(--sidebar-bg);
|
||||
}
|
||||
.sidebar::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar);
|
||||
}
|
||||
|
||||
.sidebar-visible .page-wrapper {
|
||||
transform: translateX(var(--sidebar-width));
|
||||
}
|
||||
@media only screen and (min-width: 620px) {
|
||||
.sidebar-visible .page-wrapper {
|
||||
transform: none;
|
||||
margin-left: var(--sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
.chapter {
|
||||
list-style: none outside none;
|
||||
padding-left: 0;
|
||||
line-height: 2.2em;
|
||||
}
|
||||
.chapter li {
|
||||
color: var(--sidebar-non-existant);
|
||||
}
|
||||
.chapter li a {
|
||||
display: block;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
|
||||
.chapter li a:hover {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.chapter li .active {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
.chapter .spacer {
|
||||
background-color: var(--sidebar-spacer);
|
||||
}
|
||||
|
||||
@media (-moz-touch-enabled: 1), (pointer: coarse) {
|
||||
.chapter li a { padding: 5px 0; }
|
||||
.spacer { margin: 10px 0; }
|
||||
}
|
||||
|
||||
.section {
|
||||
list-style: none outside none;
|
||||
padding-left: 20px;
|
||||
line-height: 1.9em;
|
||||
}
|
||||
|
||||
/* Theme Menu Popup */
|
||||
|
||||
.theme-popup {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50px;
|
||||
z-index: 1000;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7em;
|
||||
color: var(--fg);
|
||||
background: var(--theme-popup-bg);
|
||||
border: 1px solid var(--theme-popup-border);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: none;
|
||||
}
|
||||
.theme-popup .default {
|
||||
color: var(--icons);
|
||||
}
|
||||
.theme-popup .theme {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 2px 10px;
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
.theme-popup .theme:hover {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
.theme-popup .theme:hover:first-child,
|
||||
.theme-popup .theme:hover:last-child {
|
||||
border-top-left-radius: inherit;
|
||||
border-top-right-radius: inherit;
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/* Base styles and content styles */
|
||||
|
||||
@import 'variables.css';
|
||||
|
||||
html {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
|
||||
font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
|
||||
}
|
||||
|
||||
.left { float: left; }
|
||||
.right { float: right; }
|
||||
.hidden { display: none; }
|
||||
.play-button.hidden { display: none; }
|
||||
|
||||
h2, h3 { margin-top: 2.5em; }
|
||||
h4, h5 { margin-top: 2em; }
|
||||
|
||||
.header + .header h3,
|
||||
.header + .header h4,
|
||||
.header + .header h5 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
h1 a.header:target::before,
|
||||
h2 a.header:target::before,
|
||||
h3 a.header:target::before,
|
||||
h4 a.header:target::before {
|
||||
display: inline-block;
|
||||
content: "»";
|
||||
margin-left: -30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.page {
|
||||
outline: 0;
|
||||
padding: 0 var(--page-padding);
|
||||
}
|
||||
.page-wrapper {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.js:not(.sidebar-resizing) .page-wrapper {
|
||||
transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
padding: 0 15px;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
.content main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
.content a { text-decoration: none; }
|
||||
.content a:hover { text-decoration: underline; }
|
||||
.content img { max-width: 100%; }
|
||||
.content .header:link,
|
||||
.content .header:visited {
|
||||
color: var(--fg);
|
||||
}
|
||||
.content .header:link,
|
||||
.content .header:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 0 auto;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table td {
|
||||
padding: 3px 20px;
|
||||
border: 1px var(--table-border-color) solid;
|
||||
}
|
||||
table thead {
|
||||
background: var(--table-header-bg);
|
||||
}
|
||||
table thead td {
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
}
|
||||
table thead tr {
|
||||
border: 1px var(--table-header-bg) solid;
|
||||
}
|
||||
/* Alternate background colors for rows */
|
||||
table tbody tr:nth-child(2n) {
|
||||
background: var(--table-alternate-bg);
|
||||
}
|
||||
|
||||
|
||||
blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 0 20px;
|
||||
color: var(--fg);
|
||||
background-color: var(--quote-bg);
|
||||
border-top: .1em solid var(--quote-border);
|
||||
border-bottom: .1em solid var(--quote-border);
|
||||
}
|
||||
|
||||
|
||||
:not(.footnote-definition) + .footnote-definition,
|
||||
.footnote-definition + :not(.footnote-definition) {
|
||||
margin-top: 2em;
|
||||
}
|
||||
.footnote-definition {
|
||||
font-size: 0.9em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.footnote-definition p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
|
||||
left: -8px; /* Half of the width of the icon */
|
||||
top: -35px;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 8px;
|
||||
margin: 5px;
|
||||
z-index: 1000;
|
||||
}
|
||||
.tooltipped .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
|
||||
#sidebar,
|
||||
#menu-bar,
|
||||
.nav-chapters,
|
||||
.mobile-nav-chapters {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#page-wrapper.page-wrapper {
|
||||
transform: none;
|
||||
margin-left: 0px;
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
#content {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #666666;
|
||||
border-radius: 5px;
|
||||
|
||||
/* Force background to be printed in Chrome */
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
pre > .buttons {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
a, a:visited, a:active, a:hover {
|
||||
color: #4183c4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-inside: avoid;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
page-break-inside: avoid;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.fa {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
|
||||
/* Globals */
|
||||
|
||||
:root {
|
||||
--sidebar-width: 300px;
|
||||
--page-padding: 15px;
|
||||
--content-max-width: 750px;
|
||||
}
|
||||
|
||||
/* Themes */
|
||||
|
||||
.ayu {
|
||||
--bg: hsl(210, 25%, 8%);
|
||||
--fg: #c5c5c5;
|
||||
|
||||
--sidebar-bg: #14191f;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #5c6773;
|
||||
--sidebar-active: #ffb454;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
--links: #0096cf;
|
||||
|
||||
--inline-code-color: #ffb454;
|
||||
|
||||
--theme-popup-bg: #14191f;
|
||||
--theme-popup-border: #5c6773;
|
||||
--theme-hover: #191f26;
|
||||
|
||||
--quote-bg: hsl(226, 15%, 17%);
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--table-border-color: hsl(210, 25%, 13%);
|
||||
--table-header-bg: hsl(210, 25%, 28%);
|
||||
--table-alternate-bg: hsl(210, 25%, 11%);
|
||||
|
||||
--searchbar-border-color: #848484;
|
||||
--searchbar-bg: #424242;
|
||||
--searchbar-fg: #fff;
|
||||
--searchbar-shadow-color: #d4c89f;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #252932;
|
||||
--search-mark-bg: #e3b171;
|
||||
}
|
||||
|
||||
.coal {
|
||||
--bg: hsl(200, 7%, 8%);
|
||||
--fg: #98a3ad;
|
||||
|
||||
--sidebar-bg: #292c2f;
|
||||
--sidebar-fg: #a1adb8;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #3473ad;
|
||||
--sidebar-spacer: #393939;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #43484d;
|
||||
--icons-hover: #b3c0cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
--theme-hover: #1f2124;
|
||||
|
||||
--quote-bg: hsl(234, 21%, 18%);
|
||||
--quote-border: hsl(234, 21%, 23%);
|
||||
|
||||
--table-border-color: hsl(200, 7%, 13%);
|
||||
--table-header-bg: hsl(200, 7%, 28%);
|
||||
--table-alternate-bg: hsl(200, 7%, 11%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #b7b7b7;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #98a3ad;
|
||||
--searchresults-li-bg: #2b2b2f;
|
||||
--search-mark-bg: #355c7d;
|
||||
}
|
||||
|
||||
.light {
|
||||
--bg: hsl(0, 0%, 100%);
|
||||
--fg: #333333;
|
||||
|
||||
--sidebar-bg: #fafafa;
|
||||
--sidebar-fg: #364149;
|
||||
--sidebar-non-existant: #aaaaaa;
|
||||
--sidebar-active: #008cff;
|
||||
--sidebar-spacer: #f4f4f4;
|
||||
|
||||
--scrollbar: #cccccc;
|
||||
|
||||
--icons: #cccccc;
|
||||
--icons-hover: #333333;
|
||||
|
||||
--links: #4183c4;
|
||||
|
||||
--inline-code-color: #6e6b5e;
|
||||
|
||||
--theme-popup-bg: #fafafa;
|
||||
--theme-popup-border: #cccccc;
|
||||
--theme-hover: #e6e6e6;
|
||||
|
||||
--quote-bg: hsl(197, 37%, 96%);
|
||||
--quote-border: hsl(197, 37%, 91%);
|
||||
|
||||
--table-border-color: hsl(0, 0%, 95%);
|
||||
--table-header-bg: hsl(0, 0%, 80%);
|
||||
--table-alternate-bg: hsl(0, 0%, 97%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #fafafa;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #e4f2fe;
|
||||
--search-mark-bg: #a2cff5;
|
||||
}
|
||||
|
||||
.navy {
|
||||
--bg: hsl(226, 23%, 11%);
|
||||
--fg: #bcbdd0;
|
||||
|
||||
--sidebar-bg: #282d3f;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505274;
|
||||
--sidebar-active: #2b79a2;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;;
|
||||
|
||||
--theme-popup-bg: #161923;
|
||||
--theme-popup-border: #737480;
|
||||
--theme-hover: #282e40;
|
||||
|
||||
--quote-bg: hsl(226, 15%, 17%);
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--table-border-color: hsl(226, 23%, 16%);
|
||||
--table-header-bg: hsl(226, 23%, 31%);
|
||||
--table-alternate-bg: hsl(226, 23%, 14%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #aeaec6;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #5f5f71;
|
||||
--searchresults-border-color: #5c5c68;
|
||||
--searchresults-li-bg: #242430;
|
||||
--search-mark-bg: #a2cff5;
|
||||
}
|
||||
|
||||
.rust {
|
||||
--bg: hsl(60, 9%, 87%);
|
||||
--fg: #262625;
|
||||
|
||||
--sidebar-bg: #3b2e2a;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #e69f67;
|
||||
--sidebar-spacer: #45373a;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #262625;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #6e6b5e;
|
||||
|
||||
--theme-popup-bg: #e1e1db;
|
||||
--theme-popup-border: #b38f6b;
|
||||
--theme-hover: #99908a;
|
||||
|
||||
--quote-bg: hsl(60, 5%, 75%);
|
||||
--quote-border: hsl(60, 5%, 70%);
|
||||
|
||||
--table-border-color: hsl(60, 9%, 82%);
|
||||
--table-header-bg: #b3a497;
|
||||
--table-alternate-bg: hsl(60, 9%, 84%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #fafafa;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #dec2a2;
|
||||
--search-mark-bg: #e69f67;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
"use strict";
|
||||
window.editors = [];
|
||||
(function(editors) {
|
||||
if (typeof(ace) === 'undefined' || !ace) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array.from(document.querySelectorAll('.editable')).forEach(function(editable) {
|
||||
let editor = ace.edit(editable);
|
||||
editor.setOptions({
|
||||
highlightActiveLine: false,
|
||||
showPrintMargin: false,
|
||||
showLineNumbers: false,
|
||||
showGutter: false,
|
||||
maxLines: Infinity,
|
||||
fontSize: "0.875em" // please adjust the font size of the code in general.css
|
||||
});
|
||||
|
||||
editor.$blockScrolling = Infinity;
|
||||
|
||||
editor.getSession().setMode("ace/mode/rust");
|
||||
|
||||
editor.originalCode = editor.getValue();
|
||||
|
||||
editors.push(editor);
|
||||
});
|
||||
})(window.editors);
|
||||
10
book/elasticlunr.min.js
vendored
BIN
book/favicon.png
|
Before Width: | Height: | Size: 5.5 KiB |
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
|
||||
Visual Studio-like style based on original C# coloring by Jason Diamond <jason@diamond.name>
|
||||
|
||||
*/
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: rgba(253, 153, 3, 0.027);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote,
|
||||
.hljs-variable {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-built_in,
|
||||
.hljs-name,
|
||||
.hljs-tag {
|
||||
color: #00f;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-attribute,
|
||||
.hljs-literal,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-addition {
|
||||
color: #a31515;
|
||||
}
|
||||
|
||||
.hljs-deletion,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-meta {
|
||||
color: #2b91af;
|
||||
}
|
||||
|
||||
.hljs-doctag {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.hljs-attr {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-link {
|
||||
color: #00b0e8;
|
||||
}
|
||||
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
254
book/index.html
@@ -1,254 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Introduction - Futures Explained in 200 Lines of Rust</title>
|
||||
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="1_background_information.html"><strong aria-hidden="true">1.</strong> Some background information</a></li><li><a href="2_trait_objects.html"><strong aria-hidden="true">2.</strong> Trait objects and fat pointers</a></li><li><a href="3_generators_pin.html"><strong aria-hidden="true">3.</strong> Generators</a></li><li><a href="4_pin.html"><strong aria-hidden="true">4.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">5.</strong> Futures - our main example</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">6.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div id="menu-bar" class="menu-bar">
|
||||
<div id="menu-bar-sticky-container">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#futures-explained-in-200-lines-of-rust" id="futures-explained-in-200-lines-of-rust">Futures Explained in 200 Lines of Rust</a></h1>
|
||||
<p>This book aims to explain <code>Futures</code> in Rust using an example driven approach.</p>
|
||||
<p>The goal is to get a better understanding of <code>Futures</code> by implementing a toy
|
||||
<code>Reactor</code>, a very simple <code>Executor</code> and our own <code>Futures</code>. </p>
|
||||
<p>We'll start off a bit differently than most other explanations. Instead of
|
||||
deferring some of the details about what's special about futures in Rust we
|
||||
try to tackle that head on first. We'll be as brief as possible, but as thorough
|
||||
as needed. This way, most questions will be answered and explored up front. </p>
|
||||
<p>We'll end up with futures that can run an any executor like <code>tokio</code> and <code>async_str</code>.</p>
|
||||
<p>In the end I've made some reader exercises you can do if you want to fix some
|
||||
of the most glaring omissions and shortcuts we took and create a slightly better
|
||||
example yourself.</p>
|
||||
<blockquote>
|
||||
<p>This book is developed in the open, and contributions are welcome. You'll find
|
||||
<a href="https://github.com/cfsamson/books-futures-explained">the repository for the book itself here</a>. The final example which
|
||||
you can clone, fork or copy <a href="https://github.com/cfsamson/examples-futures">can be found here</a></p>
|
||||
</blockquote>
|
||||
<h2><a class="header" href="#what-does-this-book-give-you-that-isnt-covered-elsewhere" id="what-does-this-book-give-you-that-isnt-covered-elsewhere">What does this book give you that isn't covered elsewhere?</a></h2>
|
||||
<p>There are many good resources and examples already. First
|
||||
of all, this book will focus on <code>Futures</code> and <code>async/await</code> specifically and
|
||||
not in the context of any specific runtime.</p>
|
||||
<p>Secondly, I've always found small runnable examples very exiting to learn from.
|
||||
Thanks to <a href="https://github.com/rust-lang/mdBook">Mdbook</a> the examples can even be edited and explored further
|
||||
by uncommenting certain lines or adding new ones yourself. I use that quite a
|
||||
but throughout so keep an eye out when reading through editable code segments.</p>
|
||||
<p>It's all code that you can download, play with and learn from.</p>
|
||||
<p>We'll and end up with an understandable example including a <code>Future</code>
|
||||
implementation, an <code>Executor</code> and a <code>Reactor</code> in less than 200 lines of code.
|
||||
We don't rely on any dependencies or real I/O which means it's very easy to
|
||||
explore further and try your own ideas.</p>
|
||||
<h2><a class="header" href="#credits-and-thanks" id="credits-and-thanks">Credits and thanks</a></h2>
|
||||
<p>I'll like to take the chance of thanking the people behind <code>mio</code>, <code>tokio</code>,
|
||||
<code>async_std</code>, <code>Futures</code>, <code>libc</code>, <code>crossbeam</code> and many other libraries which so
|
||||
much is built upon. Even the RFCs that much of the design is built upon is
|
||||
very well written and very helpful. So thanks!</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
||||
|
||||
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
|
||||
|
||||
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-157536992-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,262 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Introduction - Futures Explained in 200 Lines of Rust</title>
|
||||
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
<link rel="stylesheet" href="css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html" class="active">Introduction</a></li><li><a href="1_background_information.html"><strong aria-hidden="true">1.</strong> Some background information</a></li><li><a href="2_trait_objects.html"><strong aria-hidden="true">2.</strong> Trait objects and fat pointers</a></li><li><a href="3_generators_pin.html"><strong aria-hidden="true">3.</strong> Generators</a></li><li><a href="4_pin.html"><strong aria-hidden="true">4.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">5.</strong> Futures - our main example</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">6.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div id="menu-bar" class="menu-bar">
|
||||
<div id="menu-bar-sticky-container">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1><a class="header" href="#futures-explained-in-200-lines-of-rust" id="futures-explained-in-200-lines-of-rust">Futures Explained in 200 Lines of Rust</a></h1>
|
||||
<p>This book aims to explain <code>Futures</code> in Rust using an example driven approach.</p>
|
||||
<p>The goal is to get a better understanding of <code>Futures</code> by implementing a toy
|
||||
<code>Reactor</code>, a very simple <code>Executor</code> and our own <code>Futures</code>. </p>
|
||||
<p>We'll start off a bit differently than most other explanations. Instead of
|
||||
deferring some of the details about what's special about futures in Rust we
|
||||
try to tackle that head on first. We'll be as brief as possible, but as thorough
|
||||
as needed. This way, most questions will be answered and explored up front. </p>
|
||||
<p>We'll end up with futures that can run an any executor like <code>tokio</code> and <code>async_str</code>.</p>
|
||||
<p>In the end I've made some reader exercises you can do if you want to fix some
|
||||
of the most glaring omissions and shortcuts we took and create a slightly better
|
||||
example yourself.</p>
|
||||
<blockquote>
|
||||
<p>This book is developed in the open, and contributions are welcome. You'll find
|
||||
<a href="https://github.com/cfsamson/books-futures-explained">the repository for the book itself here</a>. The final example which
|
||||
you can clone, fork or copy <a href="https://github.com/cfsamson/examples-futures">can be found here</a></p>
|
||||
</blockquote>
|
||||
<h2><a class="header" href="#what-does-this-book-give-you-that-isnt-covered-elsewhere" id="what-does-this-book-give-you-that-isnt-covered-elsewhere">What does this book give you that isn't covered elsewhere?</a></h2>
|
||||
<p>There are many good resources and examples already. First
|
||||
of all, this book will focus on <code>Futures</code> and <code>async/await</code> specifically and
|
||||
not in the context of any specific runtime.</p>
|
||||
<p>Secondly, I've always found small runnable examples very exiting to learn from.
|
||||
Thanks to <a href="https://github.com/rust-lang/mdBook">Mdbook</a> the examples can even be edited and explored further
|
||||
by uncommenting certain lines or adding new ones yourself. I use that quite a
|
||||
but throughout so keep an eye out when reading through editable code segments.</p>
|
||||
<p>It's all code that you can download, play with and learn from.</p>
|
||||
<p>We'll and end up with an understandable example including a <code>Future</code>
|
||||
implementation, an <code>Executor</code> and a <code>Reactor</code> in less than 200 lines of code.
|
||||
We don't rely on any dependencies or real I/O which means it's very easy to
|
||||
explore further and try your own ideas.</p>
|
||||
<h2><a class="header" href="#credits-and-thanks" id="credits-and-thanks">Credits and thanks</a></h2>
|
||||
<p>I'll like to take the chance of thanking the people behind <code>mio</code>, <code>tokio</code>,
|
||||
<code>async_std</code>, <code>Futures</code>, <code>libc</code>, <code>crossbeam</code> and many other libraries which so
|
||||
much is built upon. Even the RFCs that much of the design is built upon is
|
||||
very well written and very helpful. So thanks!</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
||||
|
||||
|
||||
<a rel="next" href="1_background_information.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
|
||||
|
||||
|
||||
<a href="1_background_information.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-157536992-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
|
||||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
7
book/mark.min.js
vendored
2399
book/print.html
477
book/searcher.js
@@ -1,477 +0,0 @@
|
||||
"use strict";
|
||||
window.search = window.search || {};
|
||||
(function search(search) {
|
||||
// Search functionality
|
||||
//
|
||||
// You can use !hasFocus() to prevent keyhandling in your key
|
||||
// event handlers while the user is typing their search.
|
||||
|
||||
if (!Mark || !elasticlunr) {
|
||||
return;
|
||||
}
|
||||
|
||||
//IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
|
||||
if (!String.prototype.startsWith) {
|
||||
String.prototype.startsWith = function(search, pos) {
|
||||
return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
|
||||
};
|
||||
}
|
||||
|
||||
var search_wrap = document.getElementById('search-wrapper'),
|
||||
searchbar = document.getElementById('searchbar'),
|
||||
searchbar_outer = document.getElementById('searchbar-outer'),
|
||||
searchresults = document.getElementById('searchresults'),
|
||||
searchresults_outer = document.getElementById('searchresults-outer'),
|
||||
searchresults_header = document.getElementById('searchresults-header'),
|
||||
searchicon = document.getElementById('search-toggle'),
|
||||
content = document.getElementById('content'),
|
||||
|
||||
searchindex = null,
|
||||
doc_urls = [],
|
||||
results_options = {
|
||||
teaser_word_count: 30,
|
||||
limit_results: 30,
|
||||
},
|
||||
search_options = {
|
||||
bool: "AND",
|
||||
expand: true,
|
||||
fields: {
|
||||
title: {boost: 1},
|
||||
body: {boost: 1},
|
||||
breadcrumbs: {boost: 0}
|
||||
}
|
||||
},
|
||||
mark_exclude = [],
|
||||
marker = new Mark(content),
|
||||
current_searchterm = "",
|
||||
URL_SEARCH_PARAM = 'search',
|
||||
URL_MARK_PARAM = 'highlight',
|
||||
teaser_count = 0,
|
||||
|
||||
SEARCH_HOTKEY_KEYCODE = 83,
|
||||
ESCAPE_KEYCODE = 27,
|
||||
DOWN_KEYCODE = 40,
|
||||
UP_KEYCODE = 38,
|
||||
SELECT_KEYCODE = 13;
|
||||
|
||||
function hasFocus() {
|
||||
return searchbar === document.activeElement;
|
||||
}
|
||||
|
||||
function removeChildren(elem) {
|
||||
while (elem.firstChild) {
|
||||
elem.removeChild(elem.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to parse a url into its building blocks.
|
||||
function parseURL(url) {
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
return {
|
||||
source: url,
|
||||
protocol: a.protocol.replace(':',''),
|
||||
host: a.hostname,
|
||||
port: a.port,
|
||||
params: (function(){
|
||||
var ret = {};
|
||||
var seg = a.search.replace(/^\?/,'').split('&');
|
||||
var len = seg.length, i = 0, s;
|
||||
for (;i<len;i++) {
|
||||
if (!seg[i]) { continue; }
|
||||
s = seg[i].split('=');
|
||||
ret[s[0]] = s[1];
|
||||
}
|
||||
return ret;
|
||||
})(),
|
||||
file: (a.pathname.match(/\/([^/?#]+)$/i) || [,''])[1],
|
||||
hash: a.hash.replace('#',''),
|
||||
path: a.pathname.replace(/^([^/])/,'/$1')
|
||||
};
|
||||
}
|
||||
|
||||
// Helper to recreate a url string from its building blocks.
|
||||
function renderURL(urlobject) {
|
||||
var url = urlobject.protocol + "://" + urlobject.host;
|
||||
if (urlobject.port != "") {
|
||||
url += ":" + urlobject.port;
|
||||
}
|
||||
url += urlobject.path;
|
||||
var joiner = "?";
|
||||
for(var prop in urlobject.params) {
|
||||
if(urlobject.params.hasOwnProperty(prop)) {
|
||||
url += joiner + prop + "=" + urlobject.params[prop];
|
||||
joiner = "&";
|
||||
}
|
||||
}
|
||||
if (urlobject.hash != "") {
|
||||
url += "#" + urlobject.hash;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// Helper to escape html special chars for displaying the teasers
|
||||
var escapeHTML = (function() {
|
||||
var MAP = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
var repl = function(c) { return MAP[c]; };
|
||||
return function(s) {
|
||||
return s.replace(/[&<>'"]/g, repl);
|
||||
};
|
||||
})();
|
||||
|
||||
function formatSearchMetric(count, searchterm) {
|
||||
if (count == 1) {
|
||||
return count + " search result for '" + searchterm + "':";
|
||||
} else if (count == 0) {
|
||||
return "No search results for '" + searchterm + "'.";
|
||||
} else {
|
||||
return count + " search results for '" + searchterm + "':";
|
||||
}
|
||||
}
|
||||
|
||||
function formatSearchResult(result, searchterms) {
|
||||
var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms);
|
||||
teaser_count++;
|
||||
|
||||
// The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor
|
||||
var url = doc_urls[result.ref].split("#");
|
||||
if (url.length == 1) { // no anchor found
|
||||
url.push("");
|
||||
}
|
||||
|
||||
return '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + searchterms + '#' + url[1]
|
||||
+ '" aria-details="teaser_' + teaser_count + '">' + result.doc.breadcrumbs + '</a>'
|
||||
+ '<span class="teaser" id="teaser_' + teaser_count + '" aria-label="Search Result Teaser">'
|
||||
+ teaser + '</span>';
|
||||
}
|
||||
|
||||
function makeTeaser(body, searchterms) {
|
||||
// The strategy is as follows:
|
||||
// First, assign a value to each word in the document:
|
||||
// Words that correspond to search terms (stemmer aware): 40
|
||||
// Normal words: 2
|
||||
// First word in a sentence: 8
|
||||
// Then use a sliding window with a constant number of words and count the
|
||||
// sum of the values of the words within the window. Then use the window that got the
|
||||
// maximum sum. If there are multiple maximas, then get the last one.
|
||||
// Enclose the terms in <em>.
|
||||
var stemmed_searchterms = searchterms.map(function(w) {
|
||||
return elasticlunr.stemmer(w.toLowerCase());
|
||||
});
|
||||
var searchterm_weight = 40;
|
||||
var weighted = []; // contains elements of ["word", weight, index_in_document]
|
||||
// split in sentences, then words
|
||||
var sentences = body.toLowerCase().split('. ');
|
||||
var index = 0;
|
||||
var value = 0;
|
||||
var searchterm_found = false;
|
||||
for (var sentenceindex in sentences) {
|
||||
var words = sentences[sentenceindex].split(' ');
|
||||
value = 8;
|
||||
for (var wordindex in words) {
|
||||
var word = words[wordindex];
|
||||
if (word.length > 0) {
|
||||
for (var searchtermindex in stemmed_searchterms) {
|
||||
if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) {
|
||||
value = searchterm_weight;
|
||||
searchterm_found = true;
|
||||
}
|
||||
};
|
||||
weighted.push([word, value, index]);
|
||||
value = 2;
|
||||
}
|
||||
index += word.length;
|
||||
index += 1; // ' ' or '.' if last word in sentence
|
||||
};
|
||||
index += 1; // because we split at a two-char boundary '. '
|
||||
};
|
||||
|
||||
if (weighted.length == 0) {
|
||||
return body;
|
||||
}
|
||||
|
||||
var window_weight = [];
|
||||
var window_size = Math.min(weighted.length, results_options.teaser_word_count);
|
||||
|
||||
var cur_sum = 0;
|
||||
for (var wordindex = 0; wordindex < window_size; wordindex++) {
|
||||
cur_sum += weighted[wordindex][1];
|
||||
};
|
||||
window_weight.push(cur_sum);
|
||||
for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) {
|
||||
cur_sum -= weighted[wordindex][1];
|
||||
cur_sum += weighted[wordindex + window_size][1];
|
||||
window_weight.push(cur_sum);
|
||||
};
|
||||
|
||||
if (searchterm_found) {
|
||||
var max_sum = 0;
|
||||
var max_sum_window_index = 0;
|
||||
// backwards
|
||||
for (var i = window_weight.length - 1; i >= 0; i--) {
|
||||
if (window_weight[i] > max_sum) {
|
||||
max_sum = window_weight[i];
|
||||
max_sum_window_index = i;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
max_sum_window_index = 0;
|
||||
}
|
||||
|
||||
// add <em/> around searchterms
|
||||
var teaser_split = [];
|
||||
var index = weighted[max_sum_window_index][2];
|
||||
for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) {
|
||||
var word = weighted[i];
|
||||
if (index < word[2]) {
|
||||
// missing text from index to start of `word`
|
||||
teaser_split.push(body.substring(index, word[2]));
|
||||
index = word[2];
|
||||
}
|
||||
if (word[1] == searchterm_weight) {
|
||||
teaser_split.push("<em>")
|
||||
}
|
||||
index = word[2] + word[0].length;
|
||||
teaser_split.push(body.substring(word[2], index));
|
||||
if (word[1] == searchterm_weight) {
|
||||
teaser_split.push("</em>")
|
||||
}
|
||||
};
|
||||
|
||||
return teaser_split.join('');
|
||||
}
|
||||
|
||||
function init(config) {
|
||||
results_options = config.results_options;
|
||||
search_options = config.search_options;
|
||||
searchbar_outer = config.searchbar_outer;
|
||||
doc_urls = config.doc_urls;
|
||||
searchindex = elasticlunr.Index.load(config.index);
|
||||
|
||||
// Set up events
|
||||
searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false);
|
||||
searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false);
|
||||
document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false);
|
||||
// If the user uses the browser buttons, do the same as if a reload happened
|
||||
window.onpopstate = function(e) { doSearchOrMarkFromUrl(); };
|
||||
// Suppress "submit" events so the page doesn't reload when the user presses Enter
|
||||
document.addEventListener('submit', function(e) { e.preventDefault(); }, false);
|
||||
|
||||
// If reloaded, do the search or mark again, depending on the current url parameters
|
||||
doSearchOrMarkFromUrl();
|
||||
}
|
||||
|
||||
function unfocusSearchbar() {
|
||||
// hacky, but just focusing a div only works once
|
||||
var tmp = document.createElement('input');
|
||||
tmp.setAttribute('style', 'position: absolute; opacity: 0;');
|
||||
searchicon.appendChild(tmp);
|
||||
tmp.focus();
|
||||
tmp.remove();
|
||||
}
|
||||
|
||||
// On reload or browser history backwards/forwards events, parse the url and do search or mark
|
||||
function doSearchOrMarkFromUrl() {
|
||||
// Check current URL for search request
|
||||
var url = parseURL(window.location.href);
|
||||
if (url.params.hasOwnProperty(URL_SEARCH_PARAM)
|
||||
&& url.params[URL_SEARCH_PARAM] != "") {
|
||||
showSearch(true);
|
||||
searchbar.value = decodeURIComponent(
|
||||
(url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20'));
|
||||
searchbarKeyUpHandler(); // -> doSearch()
|
||||
} else {
|
||||
showSearch(false);
|
||||
}
|
||||
|
||||
if (url.params.hasOwnProperty(URL_MARK_PARAM)) {
|
||||
var words = url.params[URL_MARK_PARAM].split(' ');
|
||||
marker.mark(words, {
|
||||
exclude: mark_exclude
|
||||
});
|
||||
|
||||
var markers = document.querySelectorAll("mark");
|
||||
function hide() {
|
||||
for (var i = 0; i < markers.length; i++) {
|
||||
markers[i].classList.add("fade-out");
|
||||
window.setTimeout(function(e) { marker.unmark(); }, 300);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < markers.length; i++) {
|
||||
markers[i].addEventListener('click', hide);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eventhandler for keyevents on `document`
|
||||
function globalKeyHandler(e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea') { return; }
|
||||
|
||||
if (e.keyCode === ESCAPE_KEYCODE) {
|
||||
e.preventDefault();
|
||||
searchbar.classList.remove("active");
|
||||
setSearchUrlParameters("",
|
||||
(searchbar.value.trim() !== "") ? "push" : "replace");
|
||||
if (hasFocus()) {
|
||||
unfocusSearchbar();
|
||||
}
|
||||
showSearch(false);
|
||||
marker.unmark();
|
||||
} else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) {
|
||||
e.preventDefault();
|
||||
showSearch(true);
|
||||
window.scrollTo(0, 0);
|
||||
searchbar.select();
|
||||
} else if (hasFocus() && e.keyCode === DOWN_KEYCODE) {
|
||||
e.preventDefault();
|
||||
unfocusSearchbar();
|
||||
searchresults.firstElementChild.classList.add("focus");
|
||||
} else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE
|
||||
|| e.keyCode === UP_KEYCODE
|
||||
|| e.keyCode === SELECT_KEYCODE)) {
|
||||
// not `:focus` because browser does annoying scrolling
|
||||
var focused = searchresults.querySelector("li.focus");
|
||||
if (!focused) return;
|
||||
e.preventDefault();
|
||||
if (e.keyCode === DOWN_KEYCODE) {
|
||||
var next = focused.nextElementSibling;
|
||||
if (next) {
|
||||
focused.classList.remove("focus");
|
||||
next.classList.add("focus");
|
||||
}
|
||||
} else if (e.keyCode === UP_KEYCODE) {
|
||||
focused.classList.remove("focus");
|
||||
var prev = focused.previousElementSibling;
|
||||
if (prev) {
|
||||
prev.classList.add("focus");
|
||||
} else {
|
||||
searchbar.select();
|
||||
}
|
||||
} else { // SELECT_KEYCODE
|
||||
window.location.assign(focused.querySelector('a'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showSearch(yes) {
|
||||
if (yes) {
|
||||
search_wrap.classList.remove('hidden');
|
||||
searchicon.setAttribute('aria-expanded', 'true');
|
||||
} else {
|
||||
search_wrap.classList.add('hidden');
|
||||
searchicon.setAttribute('aria-expanded', 'false');
|
||||
var results = searchresults.children;
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
results[i].classList.remove("focus");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showResults(yes) {
|
||||
if (yes) {
|
||||
searchresults_outer.classList.remove('hidden');
|
||||
} else {
|
||||
searchresults_outer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Eventhandler for search icon
|
||||
function searchIconClickHandler() {
|
||||
if (search_wrap.classList.contains('hidden')) {
|
||||
showSearch(true);
|
||||
window.scrollTo(0, 0);
|
||||
searchbar.select();
|
||||
} else {
|
||||
showSearch(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Eventhandler for keyevents while the searchbar is focused
|
||||
function searchbarKeyUpHandler() {
|
||||
var searchterm = searchbar.value.trim();
|
||||
if (searchterm != "") {
|
||||
searchbar.classList.add("active");
|
||||
doSearch(searchterm);
|
||||
} else {
|
||||
searchbar.classList.remove("active");
|
||||
showResults(false);
|
||||
removeChildren(searchresults);
|
||||
}
|
||||
|
||||
setSearchUrlParameters(searchterm, "push_if_new_search_else_replace");
|
||||
|
||||
// Remove marks
|
||||
marker.unmark();
|
||||
}
|
||||
|
||||
// Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor .
|
||||
// `action` can be one of "push", "replace", "push_if_new_search_else_replace"
|
||||
// and replaces or pushes a new browser history item.
|
||||
// "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet.
|
||||
function setSearchUrlParameters(searchterm, action) {
|
||||
var url = parseURL(window.location.href);
|
||||
var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM);
|
||||
if (searchterm != "" || action == "push_if_new_search_else_replace") {
|
||||
url.params[URL_SEARCH_PARAM] = searchterm;
|
||||
delete url.params[URL_MARK_PARAM];
|
||||
url.hash = "";
|
||||
} else {
|
||||
delete url.params[URL_SEARCH_PARAM];
|
||||
}
|
||||
// A new search will also add a new history item, so the user can go back
|
||||
// to the page prior to searching. A updated search term will only replace
|
||||
// the url.
|
||||
if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) {
|
||||
history.pushState({}, document.title, renderURL(url));
|
||||
} else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) {
|
||||
history.replaceState({}, document.title, renderURL(url));
|
||||
}
|
||||
}
|
||||
|
||||
function doSearch(searchterm) {
|
||||
|
||||
// Don't search the same twice
|
||||
if (current_searchterm == searchterm) { return; }
|
||||
else { current_searchterm = searchterm; }
|
||||
|
||||
if (searchindex == null) { return; }
|
||||
|
||||
// Do the actual search
|
||||
var results = searchindex.search(searchterm, search_options);
|
||||
var resultcount = Math.min(results.length, results_options.limit_results);
|
||||
|
||||
// Display search metrics
|
||||
searchresults_header.innerText = formatSearchMetric(resultcount, searchterm);
|
||||
|
||||
// Clear and insert results
|
||||
var searchterms = searchterm.split(' ');
|
||||
removeChildren(searchresults);
|
||||
for(var i = 0; i < resultcount ; i++){
|
||||
var resultElem = document.createElement('li');
|
||||
resultElem.innerHTML = formatSearchResult(results[i], searchterms);
|
||||
searchresults.appendChild(resultElem);
|
||||
}
|
||||
|
||||
// Display results
|
||||
showResults(true);
|
||||
}
|
||||
|
||||
fetch(path_to_root + 'searchindex.json')
|
||||
.then(response => response.json())
|
||||
.then(json => init(json))
|
||||
.catch(error => { // Try to load searchindex.js if fetch failed
|
||||
var script = document.createElement('script');
|
||||
script.src = path_to_root + 'searchindex.js';
|
||||
script.onload = () => init(window.search);
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
// Exported functions
|
||||
search.hasFocus = hasFocus;
|
||||
})(window.search);
|
||||
@@ -1,7 +0,0 @@
|
||||
ace.define("ace/theme/dawn",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-dawn",t.cssText=".ace-dawn .ace_gutter {background: #ebebeb;color: #333}.ace-dawn .ace_print-margin {width: 1px;background: #e8e8e8}.ace-dawn {background-color: #F9F9F9;color: #080808}.ace-dawn .ace_cursor {color: #000000}.ace-dawn .ace_marker-layer .ace_selection {background: rgba(39, 95, 255, 0.30)}.ace-dawn.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #F9F9F9;}.ace-dawn .ace_marker-layer .ace_step {background: rgb(255, 255, 0)}.ace-dawn .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgba(75, 75, 126, 0.50)}.ace-dawn .ace_marker-layer .ace_active-line {background: rgba(36, 99, 180, 0.12)}.ace-dawn .ace_gutter-active-line {background-color : #dcdcdc}.ace-dawn .ace_marker-layer .ace_selected-word {border: 1px solid rgba(39, 95, 255, 0.30)}.ace-dawn .ace_invisible {color: rgba(75, 75, 126, 0.50)}.ace-dawn .ace_keyword,.ace-dawn .ace_meta {color: #794938}.ace-dawn .ace_constant,.ace-dawn .ace_constant.ace_character,.ace-dawn .ace_constant.ace_character.ace_escape,.ace-dawn .ace_constant.ace_other {color: #811F24}.ace-dawn .ace_invalid.ace_illegal {text-decoration: underline;font-style: italic;color: #F8F8F8;background-color: #B52A1D}.ace-dawn .ace_invalid.ace_deprecated {text-decoration: underline;font-style: italic;color: #B52A1D}.ace-dawn .ace_support {color: #691C97}.ace-dawn .ace_support.ace_constant {color: #B4371F}.ace-dawn .ace_fold {background-color: #794938;border-color: #080808}.ace-dawn .ace_list,.ace-dawn .ace_markup.ace_list,.ace-dawn .ace_support.ace_function {color: #693A17}.ace-dawn .ace_storage {font-style: italic;color: #A71D5D}.ace-dawn .ace_string {color: #0B6125}.ace-dawn .ace_string.ace_regexp {color: #CF5628}.ace-dawn .ace_comment {font-style: italic;color: #5A525F}.ace-dawn .ace_heading,.ace-dawn .ace_markup.ace_heading {color: #19356D}.ace-dawn .ace_variable {color: #234A97}.ace-dawn .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYLh/5+x/AAizA4hxNNsZAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}); (function() {
|
||||
ace.require(["ace/theme/dawn"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -1,7 +0,0 @@
|
||||
ace.define("ace/theme/tomorrow_night",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-tomorrow-night",t.cssText=".ace-tomorrow-night .ace_gutter {background: #25282c;color: #C5C8C6}.ace-tomorrow-night .ace_print-margin {width: 1px;background: #25282c}.ace-tomorrow-night {background-color: #1D1F21;color: #C5C8C6}.ace-tomorrow-night .ace_cursor {color: #AEAFAD}.ace-tomorrow-night .ace_marker-layer .ace_selection {background: #373B41}.ace-tomorrow-night.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #1D1F21;}.ace-tomorrow-night .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-tomorrow-night .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #4B4E55}.ace-tomorrow-night .ace_marker-layer .ace_active-line {background: #282A2E}.ace-tomorrow-night .ace_gutter-active-line {background-color: #282A2E}.ace-tomorrow-night .ace_marker-layer .ace_selected-word {border: 1px solid #373B41}.ace-tomorrow-night .ace_invisible {color: #4B4E55}.ace-tomorrow-night .ace_keyword,.ace-tomorrow-night .ace_meta,.ace-tomorrow-night .ace_storage,.ace-tomorrow-night .ace_storage.ace_type,.ace-tomorrow-night .ace_support.ace_type {color: #B294BB}.ace-tomorrow-night .ace_keyword.ace_operator {color: #8ABEB7}.ace-tomorrow-night .ace_constant.ace_character,.ace-tomorrow-night .ace_constant.ace_language,.ace-tomorrow-night .ace_constant.ace_numeric,.ace-tomorrow-night .ace_keyword.ace_other.ace_unit,.ace-tomorrow-night .ace_support.ace_constant,.ace-tomorrow-night .ace_variable.ace_parameter {color: #DE935F}.ace-tomorrow-night .ace_constant.ace_other {color: #CED1CF}.ace-tomorrow-night .ace_invalid {color: #CED2CF;background-color: #DF5F5F}.ace-tomorrow-night .ace_invalid.ace_deprecated {color: #CED2CF;background-color: #B798BF}.ace-tomorrow-night .ace_fold {background-color: #81A2BE;border-color: #C5C8C6}.ace-tomorrow-night .ace_entity.ace_name.ace_function,.ace-tomorrow-night .ace_support.ace_function,.ace-tomorrow-night .ace_variable {color: #81A2BE}.ace-tomorrow-night .ace_support.ace_class,.ace-tomorrow-night .ace_support.ace_type {color: #F0C674}.ace-tomorrow-night .ace_heading,.ace-tomorrow-night .ace_markup.ace_heading,.ace-tomorrow-night .ace_string {color: #B5BD68}.ace-tomorrow-night .ace_entity.ace_name.ace_tag,.ace-tomorrow-night .ace_entity.ace_other.ace_attribute-name,.ace-tomorrow-night .ace_meta.ace_tag,.ace-tomorrow-night .ace_string.ace_regexp,.ace-tomorrow-night .ace_variable {color: #CC6666}.ace-tomorrow-night .ace_comment {color: #969896}.ace-tomorrow-night .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHB3d/8PAAOIAdULw8qMAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}); (function() {
|
||||
ace.require(["ace/theme/tomorrow_night"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -1,104 +0,0 @@
|
||||
/* Tomorrow Night Theme */
|
||||
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
|
||||
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||
|
||||
/* Tomorrow Comment */
|
||||
.hljs-comment {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
/* Tomorrow Red */
|
||||
.hljs-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-regexp,
|
||||
.ruby .hljs-constant,
|
||||
.xml .hljs-tag .hljs-title,
|
||||
.xml .hljs-pi,
|
||||
.xml .hljs-doctype,
|
||||
.html .hljs-doctype,
|
||||
.css .hljs-id,
|
||||
.css .hljs-class,
|
||||
.css .hljs-pseudo {
|
||||
color: #cc6666;
|
||||
}
|
||||
|
||||
/* Tomorrow Orange */
|
||||
.hljs-number,
|
||||
.hljs-preprocessor,
|
||||
.hljs-pragma,
|
||||
.hljs-built_in,
|
||||
.hljs-literal,
|
||||
.hljs-params,
|
||||
.hljs-constant {
|
||||
color: #de935f;
|
||||
}
|
||||
|
||||
/* Tomorrow Yellow */
|
||||
.ruby .hljs-class .hljs-title,
|
||||
.css .hljs-rule .hljs-attribute {
|
||||
color: #f0c674;
|
||||
}
|
||||
|
||||
/* Tomorrow Green */
|
||||
.hljs-string,
|
||||
.hljs-value,
|
||||
.hljs-inheritance,
|
||||
.hljs-header,
|
||||
.hljs-name,
|
||||
.ruby .hljs-symbol,
|
||||
.xml .hljs-cdata {
|
||||
color: #b5bd68;
|
||||
}
|
||||
|
||||
/* Tomorrow Aqua */
|
||||
.hljs-title,
|
||||
.css .hljs-hexcolor {
|
||||
color: #8abeb7;
|
||||
}
|
||||
|
||||
/* Tomorrow Blue */
|
||||
.hljs-function,
|
||||
.python .hljs-decorator,
|
||||
.python .hljs-title,
|
||||
.ruby .hljs-function .hljs-title,
|
||||
.ruby .hljs-title .hljs-keyword,
|
||||
.perl .hljs-sub,
|
||||
.javascript .hljs-title,
|
||||
.coffeescript .hljs-title {
|
||||
color: #81a2be;
|
||||
}
|
||||
|
||||
/* Tomorrow Purple */
|
||||
.hljs-keyword,
|
||||
.javascript .hljs-function {
|
||||
color: #b294bb;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
background: #1d1f21;
|
||||
color: #c5c8c6;
|
||||
padding: 0.5em;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
.coffeescript .javascript,
|
||||
.javascript .xml,
|
||||
.tex .hljs-formula,
|
||||
.xml .javascript,
|
||||
.xml .vbscript,
|
||||
.xml .css,
|
||||
.xml .hljs-cdata {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #718c00;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #c82829;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
[package]
|
||||
name = "futures_example"
|
||||
version = "0.1.0"
|
||||
authors = ["Carl Fredrik Samson <cfsamson@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
203
gcm-diagnose.log
Normal file
@@ -0,0 +1,203 @@
|
||||
Diagnose log at 2022-12-12T21:46:29Z
|
||||
|
||||
AppPath: git-credential-manager
|
||||
InstallDir:
|
||||
Version: 2.0.877+c7c35983b8
|
||||
|
||||
------------
|
||||
Diagnostic: Environment
|
||||
Skipped: False
|
||||
Success: True
|
||||
Exception: None
|
||||
Log:
|
||||
OSType: Windows
|
||||
OSVersion: 10.0 (build 19044)
|
||||
Reading environment variables... OK
|
||||
Variables:
|
||||
HOMEPATH=\Users\cf
|
||||
DriverData=C:\Windows\System32\Drivers\DriverData
|
||||
COMPUTERNAME=DESKTOP-372EPA8
|
||||
CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
|
||||
OneDrive=C:\Users\cf\OneDrive - W B Samson AS
|
||||
ChocolateyLastPathUpdate=132006090649689040
|
||||
WT_PROFILE_ID={574e775e-4f2a-5b96-ac1e-a2962a402336}
|
||||
LANG=nb.UTF-8
|
||||
TMP=C:\Users\cf\AppData\Local\Temp
|
||||
PROCESSOR_REVISION=9e0a
|
||||
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL
|
||||
VSCODE_INJECTION=1
|
||||
GIT_ASKPASS=c:\Program Files\Microsoft VS Code\resources\app\extensions\git\dist\askpass.sh
|
||||
POWERSHELL_DISTRIBUTION_CHANNEL=MSI:Windows 10 Home
|
||||
TEMP=C:\Users\cf\AppData\Local\Temp
|
||||
LOCALAPPDATA=C:\Users\cf\AppData\Local
|
||||
MSYSTEM=MINGW64
|
||||
TERM=xterm-256color
|
||||
PGLOCALEDIR=C:\Program Files\PostgreSQL\10\share\locale
|
||||
PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 158 Stepping 10, GenuineIntel
|
||||
GOPATH=C:\Users\cf\dev\go
|
||||
ProgramW6432=C:\Program Files
|
||||
USERDOMAIN=DESKTOP-372EPA8
|
||||
PGUSER=postgres
|
||||
VSCODE_GIT_ASKPASS_NODE=C:\Program Files\Microsoft VS Code\Code.exe
|
||||
Path=C:/Program Files/Git/mingw64/libexec/git-core;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Users\cf\bin;C:\Program Files\PowerShell\7;C:\Program Files\Microsoft\jdk-11.0.12.7-hotspot\bin;C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Intel\WiFi\bin\;C:\Program Files\Common Files\Intel\WirelessCommon\;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\;C:\Program Files\Microsoft VS Code\bin;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\Program Files\dotnet\;C:\ProgramData\chocolatey\bin;C:\Program Files\OpenSSL\bin;C:\Program Files (x86)\vim\vim80;C:\Program Files\WinMerge;C:\Program Files\CMake\bin;C:\Go\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files (x86)\Microsoft SQL Server\150\DTS\Binn\;C:\Program Files\PuTTY\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;C:\SysGCC\raspberry\bin;C:\Program Files\nodejs\;C:\Program Files (x86)\dotnet\;C:\Program Files\PowerShell\7\;C:\Program Files\Git\cmd;C:\Program Files (x86)\Microsoft SQL Server\150\Tools\Binn\;C:\Program Files\Microsoft SQL Server\150\Tools\Binn\;C:\Program Files\Microsoft SQL Server\150\DTS\Binn\;C:\Users\cf\.cargo\bin;C:\Users\cf\AppData\Local\Microsoft\WindowsApps;C:\Program Files\PostgreSQL\10\bin;C:\Users\cf\MyApps;C:\Users\cf\dev\go\bin;C:\Users\cf\.dotnet\tools;C:\Program Files (x86)\Dr. Memory\bin\;C:\Users\cf\AppData\Local\Programs\Fiddler;C:\Program Files\Tesseract-OCR;C:\Program Files (x86)\GnuWin32\bin;C:\cygwin64\usr\local\lib;C:\Users\cf\go\bin;C:\Users\cf\AppData\Local\Microsoft\WindowsApps;C:\Program Files\qemu;C:\Users\cf\.dotnet\tools;C:\Program Files\heroku\bin;C:\Users\cf\AppData\Roaming\npm;C:\Users\cf\dev\java\jdk-16\bin
|
||||
TERM_PROGRAM=vscode
|
||||
PROCESSOR_LEVEL=6
|
||||
NUMBER_OF_PROCESSORS=12
|
||||
TMPDIR=C:\Users\cf\AppData\Local\Temp
|
||||
PROMPT=$P$G
|
||||
TERM_PROGRAM_VERSION=1.73.1
|
||||
PGDATABASE=postgres
|
||||
ProgramFiles(x86)=C:\Program Files (x86)
|
||||
PUBLIC=C:\Users\Public
|
||||
VSCODE_GIT_IPC_HANDLE=\\.\pipe\vscode-git-835874194e-sock
|
||||
CommonProgramFiles=C:\Program Files (x86)\Common Files
|
||||
WT_SESSION=44c90a34-b416-4508-9804-2d9a09975bcb
|
||||
WSLENV=WT_SESSION::WT_PROFILE_ID
|
||||
CHROME_CRASHPAD_PIPE_NAME=\\.\pipe\crashpad_6032_LUERKPSXFNMPHSYG
|
||||
VSCODE_GIT_ASKPASS_MAIN=c:\Program Files\Microsoft VS Code\resources\app\extensions\git\dist\askpass-main.js
|
||||
PGDATA=C:\Program Files\PostgreSQL\10\data
|
||||
ProgramData=C:\ProgramData
|
||||
ProgramFiles=C:\Program Files (x86)
|
||||
ChocolateyInstall=C:\ProgramData\chocolatey
|
||||
PLINK_PROTOCOL=ssh
|
||||
PQ_LIB_DIR=C:\Program Files\PostgreSQL\10\lib
|
||||
SystemRoot=C:\WINDOWS
|
||||
ORIGINAL_XDG_CURRENT_DESKTOP=undefined
|
||||
HOME=C:\Users\cf
|
||||
CommonProgramW6432=C:\Program Files\Common Files
|
||||
LOGONSERVER=\\DESKTOP-372EPA8
|
||||
GIT_EXEC_PATH=C:/Program Files/Git/mingw64/libexec/git-core
|
||||
COLORTERM=truecolor
|
||||
USERDOMAIN_ROAMINGPROFILE=DESKTOP-372EPA8
|
||||
PGPORT=5432
|
||||
APPDATA=C:\Users\cf\AppData\Roaming
|
||||
HOMEDRIVE=C:
|
||||
configsetroot=C:\WINDOWS\ConfigSetRoot
|
||||
OneDriveCommercial=C:\Users\cf\OneDrive - W B Samson AS
|
||||
PSModulePath=C:\Users\cf\OneDrive\Dokumenter\PowerShell\Modules;C:\Program Files\PowerShell\Modules;c:\program files\powershell\7\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules;C:\Program Files (x86)\Microsoft SQL Server\150\Tools\PowerShell\Modules\
|
||||
USERNAME=cf
|
||||
PROCESSOR_ARCHITEW6432=AMD64
|
||||
PROCESSOR_ARCHITECTURE=x86
|
||||
USERPROFILE=C:\Users\cf
|
||||
OS=Windows_NT
|
||||
ComSpec=C:\WINDOWS\system32\cmd.exe
|
||||
SystemDrive=C:
|
||||
windir=C:\WINDOWS
|
||||
OneDriveConsumer=C:\Users\cf\OneDrive
|
||||
ALLUSERSPROFILE=C:\ProgramData
|
||||
|
||||
|
||||
------------
|
||||
Diagnostic: File system
|
||||
Skipped: False
|
||||
Success: True
|
||||
Exception: None
|
||||
Log:
|
||||
Temporary directory is 'C:\Users\cf\AppData\Local\Temp\'...
|
||||
Checking basic file I/O...
|
||||
Writing to temporary file 'C:\Users\cf\AppData\Local\Temp\066b45919e3c59fe8520696c'... OK
|
||||
Reading from temporary file 'C:\Users\cf\AppData\Local\Temp\066b45919e3c59fe8520696c'... OK
|
||||
Deleting temporary file 'C:\Users\cf\AppData\Local\Temp\066b45919e3c59fe8520696c'... OK
|
||||
Testing IFileSystem instance...
|
||||
UserHomePath: C:\Users\cf
|
||||
UserDataDirectoryPath: C:\Users\cf\.gcm
|
||||
GetCurrentDirectory(): C:\Users\cf\dev\rust\articles\books-futures-explained
|
||||
|
||||
------------
|
||||
Diagnostic: Networking
|
||||
Skipped: False
|
||||
Success: True
|
||||
Exception: None
|
||||
Log:
|
||||
Checking networking and HTTP stack...
|
||||
Creating HTTP client... OK
|
||||
IsNetworkAvailable: True
|
||||
Sending HEAD request to http://example.com... OK
|
||||
Sending HEAD request to https://example.com... OK
|
||||
Acquiring free TCP port... OK
|
||||
Testing local HTTP loopback connections...
|
||||
Creating new HTTP listener for http://localhost:50572/... OK
|
||||
Waiting for loopback connection... OK
|
||||
Writing response... OK
|
||||
Waiting for response data... OK
|
||||
Loopback connection data OK
|
||||
|
||||
------------
|
||||
Diagnostic: Git
|
||||
Skipped: False
|
||||
Success: True
|
||||
Exception: None
|
||||
Log:
|
||||
Getting Git version... OK
|
||||
Git version is '2.39.0.windows.1'
|
||||
Locating current repository... OK
|
||||
Git repository at 'C:/Users/cf/dev/rust/articles/books-futures-explained/.git'
|
||||
Listing all Git configuration... OK
|
||||
Git configuration:
|
||||
file:C:/Program Files/Git/etc/gitconfig http.sslcainfo=C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
|
||||
file:C:/Program Files/Git/etc/gitconfig http.sslbackend=openssl
|
||||
file:C:/Program Files/Git/etc/gitconfig diff.astextplain.textconv=astextplain
|
||||
file:C:/Program Files/Git/etc/gitconfig filter.lfs.clean=git-lfs clean -- %f
|
||||
file:C:/Program Files/Git/etc/gitconfig filter.lfs.smudge=git-lfs smudge -- %f
|
||||
file:C:/Program Files/Git/etc/gitconfig filter.lfs.process=git-lfs filter-process
|
||||
file:C:/Program Files/Git/etc/gitconfig filter.lfs.required=true
|
||||
file:C:/Program Files/Git/etc/gitconfig credential.helper=manager
|
||||
file:C:/Program Files/Git/etc/gitconfig core.editor="C:\\Program Files\\Microsoft VS Code\\bin\\code" --wait
|
||||
file:C:/Program Files/Git/etc/gitconfig core.autocrlf=input
|
||||
file:C:/Program Files/Git/etc/gitconfig core.fscache=true
|
||||
file:C:/Program Files/Git/etc/gitconfig core.symlinks=false
|
||||
file:C:/Program Files/Git/etc/gitconfig pull.rebase=false
|
||||
file:C:/Program Files/Git/etc/gitconfig credential.https://dev.azure.com.usehttppath=true
|
||||
file:C:/Program Files/Git/etc/gitconfig init.defaultbranch=master
|
||||
file:C:/Users/cf/.gitconfig winupdater.recentlyseenversion=2.25.0.windows.1
|
||||
file:C:/Users/cf/.gitconfig user.email=cf@samson.no
|
||||
file:C:/Users/cf/.gitconfig user.name=Carl Fredrik Samson
|
||||
file:C:/Users/cf/.gitconfig core.eol=lf
|
||||
file:C:/Users/cf/.gitconfig core.autocrlf=input
|
||||
file:.git/config core.repositoryformatversion=0
|
||||
file:.git/config core.filemode=false
|
||||
file:.git/config core.bare=false
|
||||
file:.git/config core.logallrefupdates=true
|
||||
file:.git/config core.symlinks=false
|
||||
file:.git/config core.ignorecase=true
|
||||
file:.git/config remote.origin.url=https://github.com/cfsamson/books-futures-explained
|
||||
file:.git/config remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
|
||||
file:.git/config branch.master.remote=origin
|
||||
file:.git/config branch.master.merge=refs/heads/master
|
||||
|
||||
|
||||
------------
|
||||
Diagnostic: Credential storage
|
||||
Skipped: False
|
||||
Success: True
|
||||
Exception: None
|
||||
Log:
|
||||
ICredentialStore instance is of type: CredentialStore
|
||||
Writing test credential... OK
|
||||
Reading test credential... OK
|
||||
Deleting test credential... OK
|
||||
|
||||
------------
|
||||
Diagnostic: Microsoft authentication (AAD/MSA)
|
||||
Skipped: False
|
||||
Success: True
|
||||
Exception: None
|
||||
Log:
|
||||
Broker not supported.
|
||||
Flow type is: Auto
|
||||
Gathering MSAL token cache data... OK
|
||||
CacheDirectory: C:\Users\cf\AppData\Local\.IdentityService
|
||||
CacheFileName: msal.cache
|
||||
CacheFilePath: C:\Users\cf\AppData\Local\.IdentityService\msal.cache
|
||||
Creating cache helper... OK
|
||||
Verifying MSAL token cache persistence... OK
|
||||
|
||||
------------
|
||||
Diagnostic: GitHub API
|
||||
Skipped: False
|
||||
Success: True
|
||||
Exception: None
|
||||
Log:
|
||||
Using 'https://github.com/' as API target.
|
||||
Querying '/meta' endpoint... OK
|
||||
|
||||
BIN
resources/futures_animation.pptx
Normal file
@@ -26,7 +26,7 @@ I'll re-iterate the most important parts here.
|
||||
|
||||
1. **A reactor**
|
||||
- handles some kind of event queue
|
||||
- has the responsibility of respoonding to events
|
||||
- has the responsibility of responding to events
|
||||
2. **An executor**
|
||||
- Often has a scheduler
|
||||
- Holds a set of suspended tasks, and has the responsibility of resuming
|
||||
@@ -34,30 +34,30 @@ I'll re-iterate the most important parts here.
|
||||
3. **The concept of a task**
|
||||
- A set of operations that can be stopped half way and resumed later on
|
||||
|
||||
This kind of pattern common outside of Rust as well, but it's especially popular in Rust due to how well it alignes with the API provided by Rusts standard library. This model separates concerns between handling and scheduling tasks, and queing and responding to I/O events.
|
||||
This kind of pattern common outside of Rust as well, but it's especially popular in Rust due to how well it aligns with the API provided by Rusts standard library. This model separates concerns between handling and scheduling tasks, and queuing and responding to I/O events.
|
||||
|
||||
## The Reactor
|
||||
|
||||
Since concurrency mostly makes sense when interacting with the outside world (or
|
||||
at least some peripheral), we need something to actually abstract over this
|
||||
interaction in an asynchronous way.
|
||||
interaction in an asynchronous way.
|
||||
|
||||
This is the `Reactors` job. Most often you'll
|
||||
see reactors in rust use a library called [Mio][mio], which provides non
|
||||
see reactors in rust use a library called [Mio][mio], which provides non
|
||||
blocking APIs and event notification for several platforms.
|
||||
|
||||
The reactor will typically give you something like a `TcpStream` (or any other resource) which you'll use to create an I/O request. What you get in return
|
||||
is a `Future`.
|
||||
is a `Future`.
|
||||
|
||||
We can call this kind of `Future` a "leaf Future`, since it's some operation
|
||||
We can call this kind of `Future` a "leaf Future`, since it's some operation
|
||||
we'll actually wait on and that we can chain operations on which are performed
|
||||
once the leaf future is ready.
|
||||
once the leaf future is ready.
|
||||
|
||||
## The Task
|
||||
|
||||
In Rust we call an interruptible task a `Future`. Futures has a well defined interface, which means they can be used across the entire ecosystem. We can chain
|
||||
these `Futures` so that once a "leaf future" is ready we'll perform a set of
|
||||
operations.
|
||||
operations.
|
||||
|
||||
These operations can spawn new leaf futures themselves.
|
||||
|
||||
@@ -65,7 +65,7 @@ These operations can spawn new leaf futures themselves.
|
||||
|
||||
The executors task is to take one or more futures and run them to completion.
|
||||
|
||||
The first thing an `executor` does when it get's a `Future` is polling it.
|
||||
The first thing an `executor` does when it gets a `Future` is polling it.
|
||||
|
||||
**When polled one of three things can happen:**
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ In contrast to many other languages, Rust doesn't come with a large runtime. The
|
||||
* Provide zero cost abstractions \(a runtime is never zero cost\)
|
||||
* Usable in embedded scenarios
|
||||
|
||||
Actually, at one point, Rust provided green threads for handling `async` programming, but they were dropped before Rust hit 1.0. The road after that has been a long one, but it has always revolved around the `Future`trait.
|
||||
Actually, at one point, Rust provided green threads for handling `async` programming, but they were dropped before Rust hit 1.0. The road after that has been a long one, but it has always revolved around the `Future`trait.
|
||||
|
||||
`Futures` in Rust comes in several versions, and that can be a source of some confusion for new users.
|
||||
|
||||
#### Futures 1.0
|
||||
#### Futures 0.1
|
||||
|
||||
This was the first iteration on how zero cost async programming could be implemented in Rust. Rusts 1.0 `Futures` is used using `combinators`. This means that we used methods on the `Futures` object themselves to chain operations on them.
|
||||
|
||||
@@ -36,11 +36,11 @@ As you can see, these chains quickly become long and hard to work with. Callback
|
||||
|
||||
There were other issues as well, but the lack of ergonomics was one of the major ones.
|
||||
|
||||
#### Futures 2.0
|
||||
#### Futures 0.2
|
||||
|
||||
#### Futures 3.0
|
||||
#### Futures 0.3
|
||||
|
||||
This is the current iteration over `Futures` and the one we'll use in our examples. This iteration solved a lot of the problems with 1.0, especially concerning ergonimics.
|
||||
This is the current iteration over `Futures` and the one we'll use in our examples. This iteration solved a lot of the problems with 1.0, especially concerning ergonomics.
|
||||
|
||||
The `async/await` syntax was designed in tandem with `Futures 3.0` and provides a much more ergonomic way to work with `Futures`:
|
||||
|
||||
@@ -55,11 +55,8 @@ let value = executor.block_on(asyncfunc()).unwrap();
|
||||
println!("{}", value);
|
||||
```
|
||||
|
||||
Before we go on further, let's separate the topic of async programming into some topics to better understand what we'll cover in this book and what we'll not cover.
|
||||
Before we go on further, let's separate the topic of async programming into some topics to better understand what we'll cover in this book and what we'll not cover.
|
||||
|
||||
#### How \`Futures\` are implemented in the language
|
||||
|
||||
If you've followed the discussions about Rusts `Futures` and `async/await` you realize that there has gone a ton of work into implementing these concepts in the Runtime.
|
||||
|
||||
|
||||
|
||||
|
||||
574
src/0_background_information.md
Normal file
@@ -0,0 +1,574 @@
|
||||
# Some Background Information
|
||||
|
||||
Before we go into the details about Futures in Rust, let's take a quick look
|
||||
at the alternatives for handling concurrent programming in general and some
|
||||
pros and cons for each of them.
|
||||
|
||||
While we do that we'll also explain some aspects when it comes to concurrency which
|
||||
will make it easier for us when we dive into Futures specifically.
|
||||
|
||||
> For fun, I've added a small snippet of runnable code with most of the examples.
|
||||
> If you're like me, things get way more interesting then and maybe you'll see some
|
||||
> things you haven't seen before along the way.
|
||||
|
||||
## Threads provided by the operating system
|
||||
|
||||
Now, one way of accomplishing concurrent programming is letting the OS take care
|
||||
of everything for us. We do this by simply spawning a new OS thread for each
|
||||
task we want to accomplish and write code like we normally would.
|
||||
|
||||
The runtime we use to handle concurrency for us is the operating system itself.
|
||||
|
||||
**Advantages:**
|
||||
|
||||
- Simple
|
||||
- Easy to use
|
||||
- Switching between tasks is reasonably fast
|
||||
- You get parallelism for free
|
||||
|
||||
**Drawbacks:**
|
||||
|
||||
- OS level threads come with a rather large stack. If you have many tasks
|
||||
waiting simultaneously (like you would in a web server under heavy load) you'll
|
||||
run out of memory pretty fast.
|
||||
- There are a lot of syscalls involved. This can be pretty costly when the number
|
||||
of tasks is high.
|
||||
- The OS has many things it needs to handle. It might not switch back to your
|
||||
thread as fast as you'd wish.
|
||||
- Might not be an option on some systems
|
||||
|
||||
**Using OS threads in Rust looks like this:**
|
||||
|
||||
```rust
|
||||
use std::thread;
|
||||
|
||||
fn main() {
|
||||
println!("So we start the program here!");
|
||||
let t1 = thread::spawn(move || {
|
||||
thread::sleep(std::time::Duration::from_millis(200));
|
||||
println!("We create tasks which gets run when they're finished!");
|
||||
});
|
||||
|
||||
let t2 = thread::spawn(move || {
|
||||
thread::sleep(std::time::Duration::from_millis(100));
|
||||
println!("We can even chain callbacks...");
|
||||
let t3 = thread::spawn(move || {
|
||||
thread::sleep(std::time::Duration::from_millis(50));
|
||||
println!("...like this!");
|
||||
});
|
||||
t3.join().unwrap();
|
||||
});
|
||||
println!("While our tasks are executing we can do other stuff here.");
|
||||
|
||||
t1.join().unwrap();
|
||||
t2.join().unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
OS threads sure have some pretty big advantages. So why all this talk about
|
||||
"async" and concurrency in the first place?
|
||||
|
||||
First, for computers to be [_efficient_](https://en.wikipedia.org/wiki/Efficiency) they need to multitask. Once you
|
||||
start to look under the covers (like [how an operating system works](https://os.phil-opp.com/async-await/))
|
||||
you'll see concurrency everywhere. It's very fundamental in everything we do.
|
||||
|
||||
Secondly, we have the web.
|
||||
|
||||
Web servers are all about I/O and handling small tasks
|
||||
(requests). When the number of small tasks is large it's not a good fit for OS
|
||||
threads as of today because of the memory they require and the overhead involved
|
||||
when creating new threads.
|
||||
|
||||
This gets even more problematic when the load is variable which means the current number of tasks a
|
||||
program has at any point in time is unpredictable. That's why you'll see so many async web
|
||||
frameworks and database drivers today.
|
||||
|
||||
However, for a huge number of problems, the standard OS threads will often be the
|
||||
right solution. So, just think twice about your problem before you reach for an
|
||||
async library.
|
||||
|
||||
Now, let's look at some other options for multitasking. They all have in common
|
||||
that they implement a way to do multitasking by having a "userland"
|
||||
runtime.
|
||||
|
||||
## Green threads/stackful coroutines
|
||||
|
||||
In this book I'll use the term "green threads" to mean stackful coroutines to differentiate
|
||||
them from the other continuation mechanisms described in this chapter. You can, however, see
|
||||
the term "green threads" be used to describe a broader set of continuation mechanisms in different
|
||||
literature or discussions on the internet.
|
||||
|
||||
Green threads use the same mechanism as an OS - creating a thread for
|
||||
each task, setting up a stack, saving the CPU's state, and jumping 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`, `Future` or `Pin`. In many ways, green threads mimics how
|
||||
an operating system facilitates concurrency, and implementing them is a great
|
||||
learning experience.
|
||||
|
||||
**The typical flow looks like this:**
|
||||
|
||||
1. Run some non-blocking code.
|
||||
2. Make a blocking call to some external resource.
|
||||
3. CPU "jumps" to the "main" thread which schedules a different thread to run and
|
||||
"jumps" to that stack.
|
||||
4. Run some non-blocking code on the new thread until a new blocking call or the
|
||||
task is finished.
|
||||
5. CPU "jumps" back to the "main" thread, schedules a new thread which is ready
|
||||
to make progress, and "jumps" to that thread.
|
||||
|
||||
These "jumps" are known as **context switches**. Your OS is doing it many times each
|
||||
second as you read this.
|
||||
|
||||
**Advantages:**
|
||||
|
||||
1. Simple to use. The code will look like it does when using OS threads.
|
||||
2. A "context switch" is reasonably fast.
|
||||
3. Each stack only gets a little memory to start with so you can have hundreds of
|
||||
thousands of green threads running.
|
||||
4. It's easy to incorporate [_preemption_](https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/green-threads#preemptive-multitasking)
|
||||
which puts a lot of control in the hands of the runtime implementors.
|
||||
|
||||
**Drawbacks:**
|
||||
|
||||
1. The stacks might need to grow. Solving this is not easy and will have a cost.
|
||||
2. You need to save the CPU state on every switch.
|
||||
3. It's not a _zero cost abstraction_ (Rust had green threads early on and this
|
||||
was one of the reasons they were removed).
|
||||
4. Complicated to implement correctly if you want to support many different
|
||||
platforms.
|
||||
|
||||
A green threads example could look something like this:
|
||||
|
||||
> The example presented below is an adapted example from an earlier gitbook I
|
||||
> wrote about green threads called [Green Threads Explained in 200 lines of Rust.](https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/)
|
||||
> If you want to know what's going on you'll find everything explained in detail
|
||||
> in that book. The code below is wildly unsafe and it's just to show a real example.
|
||||
> It's not in any way meant to showcase "best practice". Just so we're on
|
||||
> the same page.
|
||||
|
||||
_**Press the expand icon in the top right corner to show the example code (you'll actually find a minimal implementation of green threads)**_
|
||||
|
||||
```rust, edition2021
|
||||
# #![feature(naked_functions)]
|
||||
# use std::arch::asm;
|
||||
#
|
||||
# const DEFAULT_STACK_SIZE: usize = 1024 * 1024 * 2;
|
||||
# const MAX_THREADS: usize = 4;
|
||||
# static mut RUNTIME: usize = 0;
|
||||
#
|
||||
# pub struct Runtime {
|
||||
# threads: Vec<Thread>,
|
||||
# current: usize,
|
||||
# }
|
||||
#
|
||||
# #[derive(PartialEq, Eq, Debug)]
|
||||
# enum State {
|
||||
# Available,
|
||||
# Running,
|
||||
# Ready,
|
||||
# }
|
||||
#
|
||||
# struct Thread {
|
||||
# id: usize,
|
||||
# stack: Vec<u8>,
|
||||
# ctx: ThreadContext,
|
||||
# state: State,
|
||||
# task: Option<Box<dyn Fn()>>,
|
||||
# }
|
||||
#
|
||||
# #[derive(Debug, Default)]
|
||||
# #[repr(C)]
|
||||
# struct ThreadContext {
|
||||
# rsp: u64,
|
||||
# r15: u64,
|
||||
# r14: u64,
|
||||
# r13: u64,
|
||||
# r12: u64,
|
||||
# rbx: u64,
|
||||
# rbp: u64,
|
||||
# thread_ptr: u64,
|
||||
# }
|
||||
#
|
||||
# impl Thread {
|
||||
# fn new(id: usize) -> Self {
|
||||
# Thread {
|
||||
# id,
|
||||
# stack: vec![0_u8; DEFAULT_STACK_SIZE],
|
||||
# ctx: ThreadContext::default(),
|
||||
# state: State::Available,
|
||||
# task: None,
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# impl Runtime {
|
||||
# pub fn new() -> Self {
|
||||
# let base_thread = Thread {
|
||||
# id: 0,
|
||||
# stack: vec![0_u8; DEFAULT_STACK_SIZE],
|
||||
# ctx: ThreadContext::default(),
|
||||
# state: State::Running,
|
||||
# task: None,
|
||||
# };
|
||||
#
|
||||
# let mut threads = vec![base_thread];
|
||||
# threads[0].ctx.thread_ptr = &threads[0] as *const Thread as u64;
|
||||
# let mut available_threads: Vec<Thread> = (1..MAX_THREADS).map(|i| Thread::new(i)).collect();
|
||||
# threads.append(&mut available_threads);
|
||||
#
|
||||
# Runtime {
|
||||
# threads,
|
||||
# current: 0,
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# pub fn init(&self) {
|
||||
# unsafe {
|
||||
# let r_ptr: *const Runtime = self;
|
||||
# RUNTIME = r_ptr as usize;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# pub fn run(&mut self) -> ! {
|
||||
# while self.t_yield() {}
|
||||
# std::process::exit(0);
|
||||
# }
|
||||
#
|
||||
# fn t_return(&mut self) {
|
||||
# if self.current != 0 {
|
||||
# self.threads[self.current].state = State::Available;
|
||||
# self.t_yield();
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# #[inline(never)]
|
||||
# fn t_yield(&mut self) -> bool {
|
||||
# let mut pos = self.current;
|
||||
# while self.threads[pos].state != State::Ready {
|
||||
# pos += 1;
|
||||
# if pos == self.threads.len() {
|
||||
# pos = 0;
|
||||
# }
|
||||
# if pos == self.current {
|
||||
# return false;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# if self.threads[self.current].state != State::Available {
|
||||
# self.threads[self.current].state = State::Ready;
|
||||
# }
|
||||
#
|
||||
# self.threads[pos].state = State::Running;
|
||||
# let old_pos = self.current;
|
||||
# self.current = pos;
|
||||
#
|
||||
# unsafe {
|
||||
# let old: *mut ThreadContext = &mut self.threads[old_pos].ctx;
|
||||
# let new: *const ThreadContext = &self.threads[pos].ctx;
|
||||
# asm!("call switch", in("rdi") old, in("rsi") new, clobber_abi("C"));
|
||||
# }
|
||||
# self.threads.len() > 0
|
||||
# }
|
||||
#
|
||||
# pub fn spawn<F: Fn() + 'static>(f: F){
|
||||
# unsafe {
|
||||
# let rt_ptr = RUNTIME as *mut Runtime;
|
||||
# let available = (*rt_ptr)
|
||||
# .threads
|
||||
# .iter_mut()
|
||||
# .find(|t| t.state == State::Available)
|
||||
# .expect("no available thread.");
|
||||
#
|
||||
# let size = available.stack.len();
|
||||
# let s_ptr = available.stack.as_mut_ptr().offset(size as isize);
|
||||
# let s_ptr = (s_ptr as usize & !15) as *mut u8;
|
||||
# available.task = Some(Box::new(f));
|
||||
# available.ctx.thread_ptr = available as *const Thread as u64;
|
||||
# //ptr::write(s_ptr.offset((size - 8) as isize) as *mut u64, guard as u64);
|
||||
# std::ptr::write(s_ptr.offset(-16) as *mut u64, guard as u64);
|
||||
# std::ptr::write(s_ptr.offset(-24) as *mut u64, skip as u64);
|
||||
# std::ptr::write(s_ptr.offset(-32) as *mut u64, call as u64);
|
||||
# available.ctx.rsp = s_ptr.offset(-32) as u64;
|
||||
# available.state = State::Ready;
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# fn call(thread: u64) {
|
||||
# let thread = unsafe { &*(thread as *const Thread) };
|
||||
# if let Some(f) = &thread.task {
|
||||
# f();
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# #[naked]
|
||||
# unsafe extern "C" fn skip() {
|
||||
# asm!("ret", options(noreturn))
|
||||
# }
|
||||
#
|
||||
# fn guard() {
|
||||
# unsafe {
|
||||
# let rt_ptr = RUNTIME as *mut Runtime;
|
||||
# let rt = &mut *rt_ptr;
|
||||
# println!("THREAD {} FINISHED.", rt.threads[rt.current].id);
|
||||
# rt.t_return();
|
||||
# };
|
||||
# }
|
||||
#
|
||||
# pub fn yield_thread() {
|
||||
# unsafe {
|
||||
# let rt_ptr = RUNTIME as *mut Runtime;
|
||||
# (*rt_ptr).t_yield();
|
||||
# };
|
||||
# }
|
||||
# #[naked]
|
||||
# #[no_mangle]
|
||||
# unsafe extern "C" fn switch() {
|
||||
# asm!(
|
||||
# "mov 0x00[rdi], rsp",
|
||||
# "mov 0x08[rdi], r15",
|
||||
# "mov 0x10[rdi], r14",
|
||||
# "mov 0x18[rdi], r13",
|
||||
# "mov 0x20[rdi], r12",
|
||||
# "mov 0x28[rdi], rbx",
|
||||
# "mov 0x30[rdi], rbp",
|
||||
# "mov rsp, 0x00[rsi]",
|
||||
# "mov r15, 0x08[rsi]",
|
||||
# "mov r14, 0x10[rsi]",
|
||||
# "mov r13, 0x18[rsi]",
|
||||
# "mov r12, 0x20[rsi]",
|
||||
# "mov rbx, 0x28[rsi]",
|
||||
# "mov rbp, 0x30[rsi]",
|
||||
# "mov rdi, 0x38[rsi]",
|
||||
# "ret", options(noreturn)
|
||||
# );
|
||||
# }
|
||||
# #[cfg(not(windows))]
|
||||
pub fn main() {
|
||||
let mut runtime = Runtime::new();
|
||||
runtime.init();
|
||||
Runtime::spawn(|| {
|
||||
println!("I haven't implemented a timer in this example.");
|
||||
yield_thread();
|
||||
println!("Finally, notice how the tasks are executed concurrently.");
|
||||
});
|
||||
Runtime::spawn(|| {
|
||||
println!("But we can still nest tasks...");
|
||||
Runtime::spawn(|| {
|
||||
println!("...like this!");
|
||||
})
|
||||
});
|
||||
runtime.run();
|
||||
}
|
||||
# #[cfg(windows)]
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
Still hanging in there? Good. Don't get frustrated if the code above is
|
||||
difficult to understand. If I hadn't written it myself I would probably feel
|
||||
the same. You can always go back and read the book which explains it later.
|
||||
|
||||
## Callback based approaches
|
||||
|
||||
You probably already know what we're going to talk about in the next paragraphs
|
||||
from JavaScript which I assume most know.
|
||||
|
||||
>If your exposure to JavaScript callbacks has given you any sorts of PTSD earlier
|
||||
in life, close your eyes now and scroll down for 2-3 seconds. You'll find a link
|
||||
there that takes you to safety.
|
||||
|
||||
The whole idea behind a callback based approach is to save a pointer to a set of
|
||||
instructions we want to run later together with whatever state is needed. In Rust this
|
||||
would be a `closure`. In the example below, we save this information in a `HashMap`
|
||||
but it's not the only option.
|
||||
|
||||
The basic idea of _not_ involving threads as a primary way to achieve concurrency
|
||||
is the common denominator for the rest of the approaches. Including the one
|
||||
Rust uses today which we'll soon get to.
|
||||
|
||||
**Advantages:**
|
||||
|
||||
- Easy to implement in most languages
|
||||
- No context switching
|
||||
- Relatively low memory overhead (in most cases)
|
||||
|
||||
**Drawbacks:**
|
||||
|
||||
- Since each task must save the state it needs for later, the memory usage will grow
|
||||
linearly with the number of callbacks in a chain of computations.
|
||||
- Can be hard to reason about. Many people already know this as "callback hell".
|
||||
- It's a very different way of writing a program, and will require a substantial
|
||||
rewrite to go from a "normal" program flow to one that uses a "callback based" flow.
|
||||
- Sharing state between tasks is a hard problem in Rust using this approach due
|
||||
to its ownership model.
|
||||
|
||||
An extremely simplified example of a how a callback based approach could look
|
||||
like is:
|
||||
|
||||
```rust
|
||||
fn program_main() {
|
||||
println!("So we start the program here!");
|
||||
set_timeout(200, || {
|
||||
println!("We create tasks with a callback that runs once the task finished!");
|
||||
});
|
||||
set_timeout(100, || {
|
||||
println!("We can even chain sub-tasks...");
|
||||
set_timeout(50, || {
|
||||
println!("...like this!");
|
||||
})
|
||||
});
|
||||
println!("While our tasks are executing we can do other stuff instead of waiting.");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
RT.with(|rt| rt.run(program_main));
|
||||
}
|
||||
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::{cell::RefCell, collections::HashMap, thread};
|
||||
|
||||
thread_local! {
|
||||
static RT: Runtime = Runtime::new();
|
||||
}
|
||||
|
||||
struct Runtime {
|
||||
callbacks: RefCell<HashMap<usize, Box<dyn FnOnce() -> ()>>>,
|
||||
next_id: RefCell<usize>,
|
||||
evt_sender: Sender<usize>,
|
||||
evt_receiver: Receiver<usize>,
|
||||
}
|
||||
|
||||
fn set_timeout(ms: u64, cb: impl FnOnce() + 'static) {
|
||||
RT.with(|rt| {
|
||||
let id = *rt.next_id.borrow();
|
||||
*rt.next_id.borrow_mut() += 1;
|
||||
rt.callbacks.borrow_mut().insert(id, Box::new(cb));
|
||||
let evt_sender = rt.evt_sender.clone();
|
||||
thread::spawn(move || {
|
||||
thread::sleep(std::time::Duration::from_millis(ms));
|
||||
evt_sender.send(id).unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
fn new() -> Self {
|
||||
let (evt_sender, evt_receiver) = channel();
|
||||
Runtime {
|
||||
callbacks: RefCell::new(HashMap::new()),
|
||||
next_id: RefCell::new(1),
|
||||
evt_sender,
|
||||
evt_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self, program: fn()) {
|
||||
program();
|
||||
for evt_id in &self.evt_receiver {
|
||||
let cb = self.callbacks.borrow_mut().remove(&evt_id).unwrap();
|
||||
cb();
|
||||
if self.callbacks.borrow().is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We're keeping this super simple, and you might wonder what's the difference
|
||||
between this approach and the one using OS threads and passing in the callbacks
|
||||
to the OS threads directly.
|
||||
|
||||
The difference is that the callbacks are run on the
|
||||
same thread using this example. The OS threads we create are basically just used
|
||||
as timers but could represent any kind of resource that we'll have to wait for.
|
||||
|
||||
## From callbacks to promises
|
||||
|
||||
You might start to wonder by now, when are we going to talk about Futures?
|
||||
|
||||
Well, we're getting there. You see Promises, Futures, and other names for
|
||||
deferred computations are often used interchangeably.
|
||||
|
||||
There are formal differences between them, but we won't cover those
|
||||
here. It's worth explaining `promises` a bit since they're widely known due to
|
||||
their use in JavaScript. Promises also have a lot in common with Rust's Futures.
|
||||
|
||||
First of all, many languages have a concept of promises, but I'll use the one
|
||||
from JavaScript in the examples below.
|
||||
|
||||
Promises are one way to deal with the complexity which comes with a callback
|
||||
based approach.
|
||||
|
||||
Instead of:
|
||||
|
||||
```js, ignore
|
||||
setTimer(200, () => {
|
||||
setTimer(100, () => {
|
||||
setTimer(50, () => {
|
||||
console.log("I'm the last one");
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
We can do this:
|
||||
|
||||
```js, ignore
|
||||
function timer(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
timer(200)
|
||||
.then(() => timer(100))
|
||||
.then(() => timer(50))
|
||||
.then(() => console.log("I'm the last one"));
|
||||
```
|
||||
|
||||
The change is even more substantial under the hood. You see, promises return
|
||||
a state machine which can be in one of three states: `pending`, `fulfilled` or
|
||||
`rejected`.
|
||||
|
||||
When we call `timer(200)` in the sample above, we get back a promise in the state `pending`.
|
||||
|
||||
Since promises are re-written as state machines, they also enable an even better
|
||||
syntax which allows us to write our last example like this:
|
||||
|
||||
```js, ignore
|
||||
async function run() {
|
||||
await timer(200);
|
||||
await timer(100);
|
||||
await timer(50);
|
||||
console.log("I'm the last one");
|
||||
}
|
||||
```
|
||||
|
||||
You can consider the `run` function as a _pausable_ task consisting of several
|
||||
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 state to either `fulfilled` or `rejected`, the
|
||||
task is scheduled to continue to the next step.
|
||||
|
||||
Syntactically, Rust's Futures 0.1 was a lot like the promises example above, and
|
||||
Rust's Futures 0.3 is a lot like async/await in our last example.
|
||||
|
||||
Now this is also where the similarities between JavaScript promises and Rust's
|
||||
Futures stop. The reason we go through all this is to get an introduction and
|
||||
get into the right mindset for exploring Rust's Futures.
|
||||
|
||||
> To avoid confusion later on: There's one difference you should know. JavaScript
|
||||
> promises are _eagerly_ evaluated. That means that once it's created, it starts
|
||||
> running a task. Rust's Futures on the other hand are _lazily_ evaluated. They
|
||||
> need to be polled once before they do any work.
|
||||
|
||||
<br />
|
||||
<div style="text-align: center; padding-top: 2em;">
|
||||
<a href="/books-futures-explained/1_futures_in_rust.html" style="display: inline-block; background: red; color: white; padding:2em 2em 2em 2em; font-size: 1.2em;"><strong>PANIC BUTTON (next chapter)</strong></a>
|
||||
</div>
|
||||
@@ -1,112 +0,0 @@
|
||||
# Some background information
|
||||
|
||||
> **Relevant for:**
|
||||
>
|
||||
> - High level introduction to concurrency in Rust
|
||||
> - Knowing what Rust provides and not when working with async code
|
||||
> - Understanding why we need runtimes
|
||||
> - Getting pointers to further reading on concurrency in general
|
||||
|
||||
Before we start implementing our `Futures` , we'll go through some background
|
||||
information that will help demystify some of the concepts we encounter.
|
||||
|
||||
Actually, after going through these concepts, implementing futures will seem
|
||||
pretty simple. I promise.
|
||||
|
||||
## Async in Rust
|
||||
|
||||
Let's get some of the common roadblocks out of the way first.
|
||||
|
||||
Async in Rust is different from most other languages in the sense that Rust
|
||||
has a very lightweight runtime.
|
||||
|
||||
Languages like C#, JavaScript, Java and GO, already includes a runtime
|
||||
for handling concurrency. So if you come from one of those languages this will
|
||||
seem a bit strange to you.
|
||||
|
||||
In Rust you will have to make an active choice about which runtime to use.
|
||||
|
||||
### What Rust's standard library takes care of
|
||||
|
||||
1. The definition of an interruptible task
|
||||
2. An efficient technique to start, suspend, resume and store tasks which are
|
||||
executed concurrently.
|
||||
3. A defined way to wake up a suspended task
|
||||
|
||||
That's really what Rusts standard library does. As you see there is no definition
|
||||
of non-blocking I/O, how these tasks are created or how they're run.
|
||||
|
||||
|
||||
### What you need to find elsewhere
|
||||
|
||||
A runtime. Well, in Rust we normally divide the runtime into two parts:
|
||||
|
||||
- The Reactor
|
||||
- The Executor
|
||||
|
||||
Reactors create leaf `Futures`, and provides things like non-blocking sockets,
|
||||
an event queue and so on.
|
||||
|
||||
Executors, accepts one or more asynchronous tasks called `Futures` and takes
|
||||
care of actually running the code we write, suspend the tasks when they're
|
||||
waiting for I/O and resume them.
|
||||
|
||||
In theory, we could choose one `Reactor` and one `Executor` that have nothing
|
||||
to do with each other besides that one creates leaf `Futures` and the other one
|
||||
runs them, but in reality today you'll most often get both in a `Runtime`.
|
||||
|
||||
There are mainly two such runtimes today [async_std][async_std] and [tokio][tokio].
|
||||
|
||||
Quite a bit of complexity attributed to `Futures` are actually complexity rooted
|
||||
in runtimes. Creating an efficient runtime is hard.
|
||||
|
||||
Learning how to use one correctly can require quite a bit of effort as well, but you'll see that there are several similarities between these kind of runtimes so
|
||||
learning one makes learning the next much easier.
|
||||
|
||||
The difference between Rust and other languages is that you have to make an
|
||||
active choice when it comes to picking a runtime. Most often you'll just use
|
||||
the one provided for you.
|
||||
|
||||
## Futures 1.0 and Futures 3.0
|
||||
|
||||
I'll not spend too much time on this, but it feels wrong to not mention that
|
||||
there have been several iterations on how async should work in Rust.
|
||||
|
||||
`Futures 3.0` works with the relatively new `async/await` syntax in Rust and
|
||||
it's what we'll learn.
|
||||
|
||||
Now, since this is rather recent, you can encounter creates that use `Futures 1.0`
|
||||
still. This will get resolved in time, but unfortunately it's not always easy
|
||||
to know in advance.
|
||||
|
||||
A good sign is that if you're required to use combinators like `and_then` then
|
||||
you're using `Futures 1.0`.
|
||||
|
||||
While they're not directly compatible, there is a tool that let's you relatively
|
||||
easily convert a `Future 1.0` to a `Future 3.0` and vice a versa. You can find
|
||||
all you need in the [`futures-rs`][futures_rs] crate and all
|
||||
[information you need here][compat_info].
|
||||
|
||||
## First things first
|
||||
|
||||
If you find the concepts of concurrency and async programming confusing in
|
||||
general, I know where you're coming from and I have written some resources to
|
||||
try to give a high level overview that will make it easier to learn Rusts
|
||||
`Futures` afterwards:
|
||||
|
||||
* [Async Basics - The difference between concurrency and parallelism](https://cfsamson.github.io/book-exploring-async-basics/1_concurrent_vs_parallel.html)
|
||||
* [Async Basics - Async history](https://cfsamson.github.io/book-exploring-async-basics/2_async_history.html)
|
||||
* [Async Basics - Strategies for handling I/O](https://cfsamson.github.io/book-exploring-async-basics/5_strategies_for_handling_io.html)
|
||||
* [Async Basics - Epoll, Kqueue and IOCP](https://cfsamson.github.io/book-exploring-async-basics/6_epoll_kqueue_iocp.html)
|
||||
|
||||
Learning these concepts by studying futures is making it much harder than
|
||||
it needs to be, so go on and read these chapters if you feel a bit unsure.
|
||||
|
||||
I'll be right here when you're back.
|
||||
|
||||
However, if you feel that you have the basics covered, then let's get moving!
|
||||
|
||||
[async_std]: https://github.com/async-rs/async-std
|
||||
[tokio]: https://github.com/tokio-rs/tokio
|
||||
[compat_info]: https://rust-lang.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html
|
||||
[futures_rs]: https://github.com/rust-lang/futures-rs
|
||||
304
src/1_futures_in_rust.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# Futures in Rust
|
||||
|
||||
> **Overview:**
|
||||
>
|
||||
> - Get a high level introduction to concurrency in Rust
|
||||
> - Know what Rust provides and not when working with async code
|
||||
> - Get to know why we need a runtime-library in Rust
|
||||
> - Understand the difference between "leaf-future" and a "non-leaf-future"
|
||||
> - Get insight on how to handle CPU intensive tasks
|
||||
|
||||
## Futures
|
||||
|
||||
So what is a future?
|
||||
|
||||
A future is a representation of some operation which will complete in the
|
||||
future.
|
||||
|
||||
Async in Rust uses a `Poll` based approach, in which an asynchronous task will
|
||||
have three phases.
|
||||
|
||||
1. **The Poll phase.** A Future is polled which results in the task progressing until
|
||||
a point where it can no longer make progress. We often refer to the part of the
|
||||
runtime which polls a Future as an executor.
|
||||
2. **The Wait phase.** An event source, most often referred to as a reactor,
|
||||
registers that a Future is waiting for an event to happen and makes sure that it
|
||||
will wake the Future when that event is ready.
|
||||
3. **The Wake phase.** The event happens and the Future is woken up. It's now up
|
||||
to the executor which polled the Future in step 1 to schedule the future to be
|
||||
polled again and make further progress until it completes or reaches a new point
|
||||
where it can't make further progress and the cycle repeats.
|
||||
|
||||
Now, when we talk about futures I find it useful to make a distinction between
|
||||
**non-leaf** futures and **leaf** futures early on because in practice they're
|
||||
pretty different from one another.
|
||||
|
||||
### Leaf futures
|
||||
|
||||
Runtimes create _leaf futures_ which represent a resource like a socket.
|
||||
|
||||
```rust, ignore, noplaypen
|
||||
// stream is a **leaf-future**
|
||||
let mut stream = tokio::net::TcpStream::connect("127.0.0.1:3000");
|
||||
```
|
||||
|
||||
Operations on these resources, like a `Read` on a socket, will be non-blocking
|
||||
and return a future which we call a leaf future since it's the future which
|
||||
we're actually waiting on.
|
||||
|
||||
It's unlikely that you'll implement a leaf future yourself unless you're writing
|
||||
a runtime, but we'll go through how they're constructed in this book as well.
|
||||
|
||||
It's also unlikely that you'll pass a leaf-future to a runtime and run it to
|
||||
completion alone as you'll understand by reading the next paragraph.
|
||||
|
||||
### Non-leaf-futures
|
||||
|
||||
Non-leaf-futures are the kind of futures we as _users_ of a runtime write
|
||||
ourselves using the `async` keyword to create a **task** which can be run on the
|
||||
executor.
|
||||
|
||||
The bulk of an async program will consist of non-leaf-futures, which are a kind
|
||||
of pause-able computation. This is an important distinction since these futures represents a _set of operations_. Often, such a task will `await` a leaf future
|
||||
as one of many operations to complete the task.
|
||||
|
||||
```rust, ignore, noplaypen, edition2018
|
||||
// Non-leaf-future
|
||||
let non_leaf = async {
|
||||
let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap();// <- yield
|
||||
println!("connected!");
|
||||
let result = stream.write(b"hello world\n").await; // <- yield
|
||||
println!("message sent!");
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
The key to these tasks is that they're able to yield control to the runtime's
|
||||
scheduler and then resume execution again where it left off at a later point.
|
||||
|
||||
In contrast to leaf futures, these kind of futures do not themselves represent
|
||||
an I/O resource. When we poll them they will run until they get to a
|
||||
leaf-future which returns `Pending` and then yield control to the scheduler
|
||||
(which is a part of what we call the runtime).
|
||||
|
||||
## Runtimes
|
||||
|
||||
Languages like C#, JavaScript, Java, GO, and many others comes with a runtime
|
||||
for handling concurrency. So if you come from one of those languages this will
|
||||
seem a bit strange to you.
|
||||
|
||||
Rust is different from these languages in the sense that Rust doesn't come with
|
||||
a runtime for handling concurrency, so you need to use a library which provides
|
||||
this for you.
|
||||
|
||||
Quite a bit of complexity attributed to Futures is actually complexity rooted
|
||||
in runtimes; creating an efficient runtime is hard.
|
||||
|
||||
Learning how to use one correctly requires quite a bit of effort as well, but
|
||||
you'll see that there are several similarities between these kind of runtimes, so
|
||||
learning one makes learning the next much easier.
|
||||
|
||||
The difference between Rust and other languages is that you have to make an
|
||||
active choice when it comes to picking a runtime. Most often in other languages,
|
||||
you'll just use the one provided for you.
|
||||
|
||||
### A useful mental model of an async runtime
|
||||
|
||||
I find it easier to reason about how Futures work by creating a high level mental model we can use.
|
||||
To do that I have to introduce the concept of a runtime which will drive our Futures to completion.
|
||||
|
||||
>Please note that the mental model I create here is not the **only** way to drive Futures to
|
||||
completion and that Rust’s Futures does not impose any restrictions on how you actually accomplish
|
||||
this task.
|
||||
|
||||
**A fully working async system in Rust can be divided into three parts:**
|
||||
|
||||
1. Reactor
|
||||
2. Executor
|
||||
3. Future
|
||||
|
||||
So, how does these three parts work together? They do that through an object called the `Waker`.
|
||||
The `Waker` is how the reactor tells the executor that a specific Future is ready to run. Once you
|
||||
understand the life cycle and ownership of a Waker, you'll understand how futures work from a user's
|
||||
perspective. Here is the life cycle:
|
||||
|
||||
- A Waker is created by the **executor.**
|
||||
- When a future is polled the first time by the executor, it’s given a clone of the Waker
|
||||
object created by the executor. Since this is a shared object (e.g. an
|
||||
`Arc<T>`), all clones actually point to the same underlying object. Thus,
|
||||
anything that calls _any_ clone of the original Waker will wake the particular
|
||||
Future that was registered to it.
|
||||
- The future clones the Waker and passes it to the reactor, which stores it to
|
||||
use later.
|
||||
|
||||
You could think of a "future" like a channel for the `Waker`: The channel starts with the future that's polled the first time by the executor and is passed a handle to a `Waker`. It ends in a leaf-future which passes that handle to the reactor.
|
||||
|
||||
>Note that the `Waker` is wrapped in a rather uninteresting `Context` struct which we will learn more about later. The interesting part is the `Waker` that is passed on.
|
||||
|
||||
At some point in the future, the reactor will decide that the future is ready to run. It will wake the future via the Waker that it stored. This action will do what is necessary to get the executor in a position to poll the future. We'll go into more detail on Wakers in the [Waker and Context chapter.](3_waker_context.md#understanding-the-waker)
|
||||
|
||||
Since the interface is the same across all executors, reactors can _in theory_ be completely
|
||||
oblivious to the type of the executor, and vice-versa. **Executors and reactors never need to
|
||||
communicate with one another directly.**
|
||||
|
||||
This design is what gives the futures framework it's power and flexibility and allows the Rust
|
||||
standard library to provide an ergonomic, zero-cost abstraction for us to use.
|
||||
|
||||
In an effort to try to visualize how these parts work together I put together
|
||||
a set of slides in the next chapter that I hope will help.
|
||||
|
||||
The two most popular runtimes for Futures as of writing this is:
|
||||
|
||||
- [async-std](https://github.com/async-rs/async-std)
|
||||
- [Tokio](https://github.com/tokio-rs/tokio)
|
||||
|
||||
### What Rust's standard library takes care of
|
||||
|
||||
1. A common interface representing an operation which will be completed in the
|
||||
future through the `Future` trait.
|
||||
2. An ergonomic way of creating tasks which can be suspended and resumed through
|
||||
the `async` and `await` keywords.
|
||||
3. A defined interface to wake up a suspended task through the `Waker` type.
|
||||
|
||||
That's really what Rust's standard library does. As you see there is no definition
|
||||
of non-blocking I/O, how these tasks are created, or how they're run.
|
||||
|
||||
## I/O vs CPU intensive tasks
|
||||
|
||||
As you know now, what you normally write are called non-leaf futures. Let's
|
||||
take a look at this async block using pseudo-rust as example:
|
||||
|
||||
```rust, ignore
|
||||
let non_leaf = async {
|
||||
let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap(); // <-- yield
|
||||
|
||||
// request a large dataset
|
||||
let result = stream.write(get_dataset_request).await.unwrap(); // <-- yield
|
||||
|
||||
// wait for the dataset
|
||||
let mut response = vec![];
|
||||
stream.read(&mut response).await.unwrap(); // <-- yield
|
||||
|
||||
// do some CPU-intensive analysis on the dataset
|
||||
let report = analyzer::analyze_data(response).unwrap();
|
||||
|
||||
// send the results back
|
||||
stream.write(report).await.unwrap(); // <-- yield
|
||||
};
|
||||
```
|
||||
|
||||
Now, as you'll see when we go through how Futures work, the code we write between
|
||||
the yield points are run on the same thread as our executor.
|
||||
|
||||
That means that while our `analyzer` is working on the dataset, the executor
|
||||
is busy doing calculations instead of handling new requests.
|
||||
|
||||
Fortunately there are a few ways to handle this, and it's not difficult, but it's
|
||||
something you must be aware of:
|
||||
|
||||
1. We could create a new leaf future which sends our task to another thread and
|
||||
resolves when the task is finished. We could `await` this leaf-future like any
|
||||
other future.
|
||||
|
||||
2. The runtime could have some kind of supervisor that monitors how much time
|
||||
different tasks take, and move the executor itself to a different thread so it can
|
||||
continue to run even though our `analyzer` task is blocking the original executor thread.
|
||||
|
||||
3. You can create a reactor yourself which is compatible with the runtime which
|
||||
does the analysis any way you see fit, and returns a Future which can be awaited.
|
||||
|
||||
Now, #1 is the usual way of handling this, but some executors implement #2 as well.
|
||||
The problem with #2 is that if you switch runtime you need to make sure that it
|
||||
supports this kind of supervision as well or else you will end up blocking the
|
||||
executor.
|
||||
|
||||
And #3 is more of theoretical importance, normally you'd be happy by sending the task
|
||||
to the thread-pool most runtimes provide.
|
||||
|
||||
Most executors have a way to accomplish #1 using methods like `spawn_blocking`.
|
||||
|
||||
These methods send the task to a thread-pool created by the runtime where you
|
||||
can either perform CPU-intensive tasks or "blocking" tasks which are not supported
|
||||
by the runtime.
|
||||
|
||||
Now, armed with this knowledge you are already on a good way for understanding
|
||||
Futures, but we're not gonna stop yet, there are lots of details to cover.
|
||||
|
||||
Take a break or a cup of coffee and get ready as we go for a deep dive in the next chapters.
|
||||
|
||||
## Want to learn more about concurrency and async?
|
||||
|
||||
If you find the concepts of concurrency and async programming confusing in
|
||||
general, I know where you're coming from and I have written some resources to
|
||||
try to give a high-level overview that will make it easier to learn Rust's
|
||||
Futures afterwards:
|
||||
|
||||
- [Async Basics - The difference between concurrency and parallelism](https://cfsamson.github.io/book-exploring-async-basics/1_concurrent_vs_parallel.html)
|
||||
- [Async Basics - Async history](https://cfsamson.github.io/book-exploring-async-basics/2_async_history.html)
|
||||
- [Async Basics - Strategies for handling I/O](https://cfsamson.github.io/book-exploring-async-basics/5_strategies_for_handling_io.html)
|
||||
- [Async Basics - Epoll, Kqueue and IOCP](https://cfsamson.github.io/book-exploring-async-basics/6_epoll_kqueue_iocp.html)
|
||||
|
||||
Learning these concepts by studying futures is making it much harder than
|
||||
it needs to be, so go on and read these chapters if you feel a bit unsure.
|
||||
|
||||
I'll be right here when you're back.
|
||||
|
||||
However, if you feel that you have the basics covered, then let's get moving!
|
||||
|
||||
## Bonus section - additional notes on Futures and Wakers
|
||||
|
||||
> In this section we take a deeper look at some advantages of having a loose
|
||||
coupling between the Executor-part and Reactor-part of an async runtime.
|
||||
|
||||
Earlier in this chapter, I mentioned that it is common for the
|
||||
executor to create a new Waker for each Future that is registered with the
|
||||
executor, but that the Waker is a shared object similar to a `Arc<T>`. One of
|
||||
the reasons for this design is that it allows different Reactors the
|
||||
ability to Wake a Future.
|
||||
|
||||
As an example of how this can be used, consider how you could create a new type
|
||||
of Future that has the ability to be canceled:
|
||||
|
||||
One way to achieve this would be to add an
|
||||
[`AtomicBool`](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html)
|
||||
to the instance of the future, and an extra method called `cancel()`. The
|
||||
`cancel()` method will first set the
|
||||
[`AtomicBool`](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html)
|
||||
to signal that the future is now canceled, and then immediately call instance's
|
||||
own copy of the Waker.
|
||||
|
||||
Once the executor starts executing the Future, the
|
||||
_Future_ will know that it was canceled, and will do the appropriate cleanup
|
||||
actions to terminate itself.
|
||||
|
||||
The main reason for designing the Future in this manner is because we don't have
|
||||
to modify either the Executor or the other Reactors; they are all oblivious to
|
||||
the change.
|
||||
|
||||
The only possible issue is with the design of the Future itself; a
|
||||
Future that is canceled still needs to terminate correctly according to the
|
||||
rules outlined in the docs for
|
||||
[`Future`](https://doc.rust-lang.org/std/future/trait.Future.html). That means
|
||||
that it can't just delete it's resources and then sit there; it needs to return
|
||||
a value. It is up to you to decide if a canceled future will return
|
||||
[`Pending`](https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending)
|
||||
forever, or if it will return a value in
|
||||
[`Ready`](https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready). Just
|
||||
be aware that if other Futures are `await`ing it, they won't be able to start
|
||||
until [`Ready`](https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready)
|
||||
is returned.
|
||||
|
||||
A common technique for cancelable Futures is to have them return a
|
||||
Result with an error that signals the Future was canceled; that will permit any
|
||||
Futures that are awaiting the canceled Future a chance to progress, with the
|
||||
knowledge that the Future they depended on was canceled. There are additional
|
||||
concerns as well, but beyond the scope of this book. Read the documentation and
|
||||
code for the [`futures`](https://crates.io/crates/futures) crate for a better
|
||||
understanding of what the concerns are.
|
||||
|
||||
>_Thanks to [@ckaran](https://github.com/ckaran) for contributing this bonus segment._
|
||||
|
||||
[async_std]: https://github.com/async-rs/async-std
|
||||
[tokio]: https://github.com/tokio-rs/tokio
|
||||
[compat_info]: https://rust-lang.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html
|
||||
[futures_rs]: https://github.com/rust-lang/futures-rs
|
||||
75
src/2_a_mental_model_for_futures.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# A mental model of how Futures and runtimes work
|
||||
|
||||
The main goal in this part is to build a high level
|
||||
mental model of how the different pieces we read about in the previous chapter
|
||||
works together. I hope this will make it easier to understand the high level concepts
|
||||
before we take a deep dive into topics like trait objects and generators in the next
|
||||
few chapters.
|
||||
|
||||
This is not the only way to create a model of an async system since we're making
|
||||
assumptions on runtime specifics that can vary a great deal. It's the way I found
|
||||
it easiest to build upon and it's relevant for understanding a lot of real
|
||||
implementations you'll find in the async ecosystem.
|
||||
|
||||
Finally, please note that the code itself is "pseudo-rust" due to the need for brevity
|
||||
and clarity.
|
||||
|
||||
>Click on a page to open a larger view in a new tab.
|
||||
|
||||
<a href="./assets/slides/Slide1.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide1.PNG" alt="slide1"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide2.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide2.PNG" alt="slide2"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide3.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide3.PNG" alt="slide3"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide4.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide4.PNG" alt="slide4"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide5.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide5.PNG" alt="slide5"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide6.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide6.PNG" alt="slide6"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide7.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide7.PNG" alt="slide7"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide8.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide8.PNG" alt="slide8"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide9.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide9.PNG" alt="slide9"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide10.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide10.PNG" alt="slide10"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide11.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide11.PNG" alt="slide11"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide12.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide12.PNG" alt="slide12"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide13.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide13.PNG" alt="slide13"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide14.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide14.PNG" alt="slide14"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide15.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide15.PNG" alt="slide15"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide16.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide16.PNG" alt="slide16"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide17.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide17.PNG" alt="slide17"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide18.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide18.PNG" alt="slide18"/>
|
||||
</a>
|
||||
<a href="./assets/slides/Slide19.PNG" target="_blank">
|
||||
<img src="./assets/slides/Slide19.PNG" alt="slide19"/>
|
||||
</a>
|
||||
@@ -1,139 +0,0 @@
|
||||
# Trait objects and fat pointers
|
||||
|
||||
> **Relevant for:**
|
||||
>
|
||||
> - Understanding how the Waker object is constructed
|
||||
> - Getting a basic feel for "type erased" objects and what they are
|
||||
> - Learning the basics of dynamic dispatch
|
||||
|
||||
## Trait objects and dynamic dispatch
|
||||
|
||||
One of the most confusing things we encounter when implementing our own `Futures`
|
||||
is how we implement a `Waker` . Creating a `Waker` involves creating a `vtable`
|
||||
which allows us to use dynamic dispatch to call methods on a _type erased_ trait
|
||||
object we construct our selves.
|
||||
|
||||
>If you want to know more about dynamic dispatch in Rust I can recommend an article written by Adam Schwalm called [Exploring Dynamic Dispatch in Rust](https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/).
|
||||
|
||||
Let's explain this a bit more in detail.
|
||||
|
||||
## Fat pointers in Rust
|
||||
|
||||
Let's take a look at the size of some different pointer types in Rust. If we
|
||||
run the following code. _(You'll have to press "play" to see the output)_:
|
||||
|
||||
``` rust
|
||||
# use std::mem::size_of;
|
||||
trait SomeTrait { }
|
||||
|
||||
fn main() {
|
||||
println!("======== The size of different pointers in Rust: ========");
|
||||
println!("&dyn Trait:-----{}", size_of::<&dyn SomeTrait>());
|
||||
println!("&[&dyn Trait]:--{}", size_of::<&[&dyn SomeTrait]>());
|
||||
println!("Box<Trait>:-----{}", size_of::<Box<SomeTrait>>());
|
||||
println!("&i32:-----------{}", size_of::<&i32>());
|
||||
println!("&[i32]:---------{}", size_of::<&[i32]>());
|
||||
println!("Box<i32>:-------{}", size_of::<Box<i32>>());
|
||||
println!("&Box<i32>:------{}", size_of::<&Box<i32>>());
|
||||
println!("[&dyn Trait;4]:-{}", size_of::<[&dyn SomeTrait; 4]>());
|
||||
println!("[i32;4]:--------{}", size_of::<[i32; 4]>());
|
||||
}
|
||||
```
|
||||
|
||||
As you see from the output after running this, the sizes of the references varies.
|
||||
Many are 8 bytes (which is a pointer size on 64 bit systems), but some are 16
|
||||
bytes.
|
||||
|
||||
The 16 byte sized pointers are called "fat pointers" since they carry extra
|
||||
information.
|
||||
|
||||
**Example `&[i32]` :**
|
||||
|
||||
- The first 8 bytes is the actual pointer to the first element in the array (or part of an array the slice refers to)
|
||||
- The second 8 bytes is the length of the slice.
|
||||
|
||||
**Example `&dyn SomeTrait`:**
|
||||
|
||||
This is the type of fat pointer we'll concern ourselves about going forward.
|
||||
`&dyn SomeTrait` is a reference to a trait, or what Rust calls a _trait object_.
|
||||
|
||||
The layout for a pointer to a _trait object_ looks like this:
|
||||
|
||||
- The first 8 bytes points to the `data` for the trait object
|
||||
- The second 8 bytes points to the `vtable` for the trait object
|
||||
|
||||
The reason for this is to allow us to refer to an object we know nothing about
|
||||
except that it implements the methods defined by our trait. To accomplish this we use _dynamic dispatch_.
|
||||
|
||||
Let's explain this in code instead of words by implementing our own trait
|
||||
object from these parts:
|
||||
|
||||
>This is an example of _editable_ code. You can change everything in the example
|
||||
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.
|
||||
|
||||
```rust, editable
|
||||
// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr)
|
||||
trait Test {
|
||||
fn add(&self) -> i32;
|
||||
fn sub(&self) -> i32;
|
||||
fn mul(&self) -> i32;
|
||||
}
|
||||
|
||||
// This will represent our home brewn fat pointer to a trait object
|
||||
#[repr(C)]
|
||||
struct FatPointer<'a> {
|
||||
/// A reference is a pointer to an instantiated `Data` instance
|
||||
data: &'a mut Data,
|
||||
/// Since we need to pass in literal values like length and alignment it's
|
||||
/// easiest for us to convert pointers to usize-integers instead of the other way around.
|
||||
vtable: *const usize,
|
||||
}
|
||||
|
||||
// This is the data in our trait object. It's just two numbers we want to operate on.
|
||||
struct Data {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
// ====== function definitions ======
|
||||
fn add(s: &Data) -> i32 {
|
||||
s.a + s.b
|
||||
}
|
||||
fn sub(s: &Data) -> i32 {
|
||||
s.a - s.b
|
||||
}
|
||||
fn mul(s: &Data) -> i32 {
|
||||
s.a * s.b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut data = Data {a: 3, b: 2};
|
||||
// vtable is like special purpose array of pointer-length types with a fixed
|
||||
// format where the three first values has a special meaning like the
|
||||
// length of the array is encoded in the array itself as the second value.
|
||||
let vtable = vec![
|
||||
0, // pointer to `Drop` (which we're not implementing here)
|
||||
6, // lenght of vtable
|
||||
8, // alignment
|
||||
|
||||
// we need to make sure we add these in the same order as defined in the Trait.
|
||||
add as usize, // function pointer - try changing the order of `add`
|
||||
sub as usize, // function pointer - and `sub` to see what happens
|
||||
mul as usize, // function pointer
|
||||
];
|
||||
|
||||
let fat_pointer = FatPointer { data: &mut data, vtable: vtable.as_ptr()};
|
||||
let test = unsafe { std::mem::transmute::<FatPointer, &dyn Test>(fat_pointer) };
|
||||
|
||||
// And voalá, it's now a trait object we can call methods on
|
||||
println!("Add: 3 + 2 = {}", test.add());
|
||||
println!("Sub: 3 - 2 = {}", test.sub());
|
||||
println!("Mul: 3 * 2 = {}", test.mul());
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The reason we go through this will be clear later on when we implement our own
|
||||
`Waker` we'll actually set up a `vtable` like we do here to and knowing what
|
||||
it is will make this much less mysterious.
|
||||
@@ -1,562 +0,0 @@
|
||||
# Generators
|
||||
|
||||
>**Relevant for:**
|
||||
>
|
||||
>- Understanding how the async/await syntax works since it's how `await` is implemented
|
||||
>- Knowing why we need `Pin`
|
||||
>- Understanding why Rusts async model is very efficient
|
||||
>
|
||||
>The motivation for `Generators` can be found in [RFC#2033][rfc2033]. It's very
|
||||
>well written and I can recommend reading through it (it talks as much about
|
||||
>async/await as it does about generators).
|
||||
|
||||
The second difficult part is understanding Generators and the `Pin` type. Since
|
||||
they're related we'll start off by exploring generators first. By doing that
|
||||
we'll soon get to see why we need to be able to "pin" some data to a fixed
|
||||
location in memory and get an introduction to `Pin` as well.
|
||||
|
||||
Basically, there were three main options discussed when designing how Rust would
|
||||
handle concurrency:
|
||||
|
||||
1. Stackful coroutines, better known as green threads.
|
||||
2. Using combinators.
|
||||
3. Stackless coroutines, better known as generators.
|
||||
|
||||
### Stackful coroutines/green threads
|
||||
|
||||
I've written about green threads before. Go check out
|
||||
[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
|
||||
|
||||
`Futures 1.0` used combinators. If you've worked with `Promises` in JavaScript,
|
||||
you already know combinators. In Rust they look like this:
|
||||
|
||||
```rust,noplaypen,ignore
|
||||
let future = Connection::connect(conn_str).and_then(|conn| {
|
||||
conn.query("somerequest").map(|row|{
|
||||
SomeStruct::from(row)
|
||||
}).collect::<Vec<SomeStruct>>()
|
||||
});
|
||||
|
||||
let rows: Result<Vec<SomeStruct>, SomeLibraryError> = block_on(future).unwrap();
|
||||
|
||||
```
|
||||
|
||||
While an effective solution there are mainly three downsides I'll focus on:
|
||||
|
||||
1. The error messages produced could be extremely long and arcane
|
||||
2. Not optimal memory usage
|
||||
3. Did not allow to borrow across combinator steps.
|
||||
|
||||
Point #3, is actually a major drawback with `Futures 1.0`.
|
||||
|
||||
Not allowing borrows across suspension points ends up being very
|
||||
un-ergonomic and to accomplish some tasks it requires extra allocations or
|
||||
copying which is inefficient.
|
||||
|
||||
The reason for the higher than optimal memory usage is that this is basically
|
||||
a callback-based approach, where each closure stores all the data it needs
|
||||
for computation. This means that as we chain these, the memory required to store
|
||||
the needed state increases with each added step.
|
||||
|
||||
### Stackless coroutines/generators
|
||||
|
||||
This is the model used in Rust today. It has a few notable advantages:
|
||||
|
||||
1. It's easy to convert normal Rust code to a stackless coroutine using using
|
||||
async/await as keywords (it can even be done using a macro).
|
||||
2. No need for context switching and saving/restoring CPU state
|
||||
3. No need to handle dynamic stack allocation
|
||||
4. Very memory efficient
|
||||
5. Allows us to borrow across suspension points
|
||||
|
||||
The last point is in contrast to `Futures 1.0`. With async/await we can do this:
|
||||
|
||||
```rust, ignore
|
||||
async fn myfn() {
|
||||
let text = String::from("Hello world");
|
||||
let borrowed = &text[0..5];
|
||||
somefuture.await;
|
||||
println!("{}", borrowed);
|
||||
}
|
||||
```
|
||||
|
||||
Generators in Rust are implemented as state machines. The memory footprint of a
|
||||
chain of computations is only defined by the largest footprint of any single
|
||||
step require. That means that adding steps to a chain of computations might not
|
||||
require any increased memory at all.
|
||||
|
||||
## How generators work
|
||||
|
||||
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
|
||||
before we had a concept of `Pin`:
|
||||
|
||||
|
||||
```rust,noplaypen,ignore
|
||||
#![feature(generators, generator_trait)]
|
||||
use std::ops::{Generator, GeneratorState};
|
||||
|
||||
fn main() {
|
||||
let a: i32 = 4;
|
||||
let mut gen = move || {
|
||||
println!("Hello");
|
||||
yield a * 2;
|
||||
println!("world!");
|
||||
};
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Early on, before there was a consensus about the design of `Pin`, this
|
||||
compiled to something looking similar to this:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut gen = GeneratorA::start(4);
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
}
|
||||
|
||||
// If you've ever wondered why the parameters are called Y and R the naming from
|
||||
// the original rfc most likely holds the answer
|
||||
enum GeneratorState<Y, R> {
|
||||
Yielded(Y), // originally called `Yield(Y)`
|
||||
Complete(R), // originally called `Return(R)`
|
||||
}
|
||||
|
||||
trait Generator {
|
||||
type Yield;
|
||||
type Return;
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
}
|
||||
|
||||
enum GeneratorA {
|
||||
Enter(i32),
|
||||
Yield1(i32),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl GeneratorA {
|
||||
fn start(a1: i32) -> Self {
|
||||
GeneratorA::Enter(a1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator for GeneratorA {
|
||||
type Yield = i32;
|
||||
type Return = ();
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
// lets us get ownership over current state
|
||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||
GeneratorA::Enter(a1) => {
|
||||
|
||||
/*|---code before yield---|*/
|
||||
/*|*/ println!("Hello"); /*|*/
|
||||
/*|*/ let a = a1 * 2; /*|*/
|
||||
/*|------------------------|*/
|
||||
|
||||
*self = GeneratorA::Yield1(a);
|
||||
GeneratorState::Yielded(a)
|
||||
}
|
||||
GeneratorA::Yield1(_) => {
|
||||
|
||||
/*|----code after yield----|*/
|
||||
/*|*/ println!("world!"); /*|*/
|
||||
/*|-------------------------|*/
|
||||
|
||||
*self = GeneratorA::Exit;
|
||||
GeneratorState::Complete(())
|
||||
}
|
||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
>The `yield` keyword was discussed first in [RFC#1823][rfc1823] and in [RFC#1832][rfc1832].
|
||||
|
||||
Now that you know that the `yield` keyword in reality rewrites your code to become a state machine,
|
||||
you'll also know the basics of how `await` works. It's very similar.
|
||||
|
||||
Now, there are some limitations in our naive state machine above. What happens when you have a
|
||||
`borrow` across a `yield` point?
|
||||
|
||||
We could forbid that, but **one of the major design goals for the async/await syntax has been
|
||||
to allow this**. These kinds of borrows were not possible using `Futures 1.0` so we can't let this
|
||||
limitation just slip and call it a day yet.
|
||||
|
||||
Instead of discussing it in theory, let's look at some code.
|
||||
|
||||
> We'll use the optimized version of the state machines which is used in Rust today. For a more
|
||||
> in depth explanation see [Tyler Mandry's excellent article: How Rust optimizes async/await][optimizing-await]
|
||||
|
||||
```rust,noplaypen,ignore
|
||||
let mut gen = move || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
```
|
||||
|
||||
Now what does our rewritten state machine look like with this example?
|
||||
|
||||
```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> {
|
||||
# // originally called `CoResult`
|
||||
# Yielded(Y), // originally called `Yield(Y)`
|
||||
# Complete(R), // originally called `Return(R)`
|
||||
# }
|
||||
#
|
||||
# trait Generator {
|
||||
# type Yield;
|
||||
# type Return;
|
||||
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
# }
|
||||
|
||||
enum GeneratorA {
|
||||
Enter,
|
||||
Yield1 {
|
||||
to_borrow: String,
|
||||
borrowed: &String, // uh, what lifetime should this have?
|
||||
},
|
||||
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> {
|
||||
// lets us get ownership over current state
|
||||
match std::mem::replace(&mut *self, GeneratorA::Exit) {
|
||||
GeneratorA::Enter => {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
let res = borrowed.len();
|
||||
|
||||
*self = GeneratorA::Yield1 {to_borrow, borrowed};
|
||||
GeneratorState::Yielded(res)
|
||||
}
|
||||
|
||||
GeneratorA::Yield1 {to_borrow, borrowed} => {
|
||||
println!("Hello {}", borrowed);
|
||||
*self = GeneratorA::Exit;
|
||||
GeneratorState::Complete(())
|
||||
}
|
||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you try to compile this you'll get an error (just try it yourself by pressing play).
|
||||
|
||||
What is the lifetime of `&String`. It's not the same as the lifetime of `Self`. It's not `static`.
|
||||
Turns out that it's not possible for us in Rusts syntax to describe this lifetime, which means, that
|
||||
to make this work, we'll have to let the compiler know that _we_ control this correctly ourselves.
|
||||
|
||||
That means turning to unsafe.
|
||||
|
||||
Let's try to write an implementation that will compiler using `unsafe`. As you'll
|
||||
see we end up in a _self referential struct_. A struct which holds references
|
||||
into itself.
|
||||
|
||||
As you'll notice, this compiles just fine!
|
||||
|
||||
```rust,editable
|
||||
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> {
|
||||
Yielded(Y), // originally called `Yield(Y)`
|
||||
Complete(R), // originally called `Return(R)`
|
||||
}
|
||||
|
||||
trait Generator {
|
||||
type Yield;
|
||||
type Return;
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
}
|
||||
|
||||
enum GeneratorA {
|
||||
Enter,
|
||||
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
|
||||
}
|
||||
}
|
||||
impl Generator for GeneratorA {
|
||||
type Yield = usize;
|
||||
type Return = ();
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
// lets us get ownership over current state
|
||||
match self {
|
||||
GeneratorA::Enter => {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
let res = borrowed.len();
|
||||
|
||||
// Trick to actually get a self reference
|
||||
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
|
||||
match self {
|
||||
GeneratorA::Yield1{to_borrow, borrowed} => *borrowed = to_borrow,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
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!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Try to uncomment the line with `mem::swap` and see the results.
|
||||
|
||||
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!
|
||||
|
||||
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,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:
|
||||
|
||||
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
|
||||
|
||||
Thanks to [PR#45337][pr45337] you can actually run code like the one in our
|
||||
example in Rust today using the `static` keyword on nightly. Try it for
|
||||
yourself:
|
||||
|
||||
```rust
|
||||
#![feature(generators, generator_trait)]
|
||||
use std::ops::{Generator, GeneratorState};
|
||||
|
||||
|
||||
pub fn main() {
|
||||
let gen1 = static || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
|
||||
let gen2 = static || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
|
||||
let mut pinned1 = Box::pin(gen1);
|
||||
let mut pinned2 = Box::pin(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);
|
||||
};
|
||||
|
||||
let _ = pinned1.as_mut().resume();
|
||||
let _ = pinned2.as_mut().resume();
|
||||
}
|
||||
```
|
||||
|
||||
[rfc2033]: https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md
|
||||
[greenthreads]: https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/
|
||||
[rfc1823]: https://github.com/rust-lang/rfcs/pull/1823
|
||||
[rfc1832]: https://github.com/rust-lang/rfcs/pull/1832
|
||||
[optimizing-await]: https://tmandry.gitlab.io/blog/posts/optimizing-await-1/
|
||||
[pr45337]: https://github.com/rust-lang/rust/pull/45337/files
|
||||
185
src/3_waker_context.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Waker and Context
|
||||
|
||||
> **Overview:**
|
||||
>
|
||||
> - Understand how the Waker object is constructed
|
||||
> - Learn how the runtime knows when a leaf-future can resume
|
||||
> - Learn the basics of dynamic dispatch and trait objects
|
||||
>
|
||||
> The `Waker` type is described as part of [RFC#2592][rfc2592].
|
||||
|
||||
## The Waker
|
||||
|
||||
The `Waker` type allows for a loose coupling between the reactor-part and the executor-part of a runtime.
|
||||
|
||||
By having a wake up mechanism that is _not_ tied to the thing that executes
|
||||
the future, runtime-implementors can come up with interesting new wake-up
|
||||
mechanisms. An example of this can be spawning a thread to do some work that
|
||||
eventually notifies the future, completely independent of the current runtime.
|
||||
|
||||
Without a waker, the executor would be the _only_ way to notify a running
|
||||
task, whereas with the waker, we get a loose coupling where it's easy to
|
||||
extend the ecosystem with new leaf-level tasks.
|
||||
|
||||
> If you want to read more about the reasoning behind the `Waker` type I can
|
||||
> recommend [Withoutboats articles series about them](https://boats.gitlab.io/blog/post/wakers-i/).
|
||||
|
||||
## The Context type
|
||||
|
||||
As the docs state as of now this type only wraps a `Waker`, but it gives some
|
||||
flexibility for future evolutions of the API in Rust. The context can for example hold task-local storage and provide space for debugging hooks in later iterations.
|
||||
|
||||
## Understanding the `Waker`
|
||||
|
||||
One of the most confusing things we encounter when implementing our own `Future`s
|
||||
is how we implement a `Waker` . Creating a `Waker` involves creating a `vtable`
|
||||
which allows us to use dynamic dispatch to call methods on a _type erased_ trait
|
||||
object we construct ourselves.
|
||||
|
||||
The `Waker` implementation is specific to the type of executor in use, but all Wakers share a similar interface. It's useful to think of it as a `Trait`. It's not implemented as such since that would require us to treat it like a trait object like `&dyn Waker` or `Arc<dyn Waker>` which either restricts the API by requiring a `&dyn Waker` trait object, or would require an `Arc<dyn Waker>` which in turn requires a heap allocation which a lot of embedded-like systems can't do.
|
||||
|
||||
Having the Waker implemented the way it is supports users creating a statically-allocated wakers and even more exotic mechanisms to on platforms where that makes sense.
|
||||
|
||||
>If you want to know more about dynamic dispatch in Rust I can recommend an
|
||||
article written by Adam Schwalm called [Exploring Dynamic Dispatch in Rust](https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/).
|
||||
|
||||
Let's explain this a bit more in detail.
|
||||
|
||||
## Fat pointers in Rust
|
||||
|
||||
To get a better understanding of how we implement the `Waker` in Rust, we need
|
||||
to take a step back and talk about some fundamentals. Let's start by taking a
|
||||
look at the size of some different pointer types in Rust.
|
||||
|
||||
Run the following code _(You'll have to press "play" to see the output)_:
|
||||
|
||||
``` rust
|
||||
# use std::mem::size_of;
|
||||
trait SomeTrait { }
|
||||
|
||||
fn main() {
|
||||
println!("======== The size of different pointers in Rust: ========");
|
||||
println!("&dyn Trait:------{}", size_of::<&dyn SomeTrait>());
|
||||
println!("&[&dyn Trait]:---{}", size_of::<&[&dyn SomeTrait]>());
|
||||
println!("Box<Trait>:------{}", size_of::<Box<SomeTrait>>());
|
||||
println!("Box<Box<Trait>>:-{}", size_of::<Box<Box<SomeTrait>>>());
|
||||
println!("&i32:------------{}", size_of::<&i32>());
|
||||
println!("&[i32]:----------{}", size_of::<&[i32]>());
|
||||
println!("Box<i32>:--------{}", size_of::<Box<i32>>());
|
||||
println!("&Box<i32>:-------{}", size_of::<&Box<i32>>());
|
||||
println!("[&dyn Trait;4]:--{}", size_of::<[&dyn SomeTrait; 4]>());
|
||||
println!("[i32;4]:---------{}", size_of::<[i32; 4]>());
|
||||
}
|
||||
```
|
||||
|
||||
As you see from the output after running this, the sizes of the references varies.
|
||||
Many are 8 bytes (which is a pointer size on 64 bit systems), but some are 16
|
||||
bytes.
|
||||
|
||||
The 16 byte sized pointers are called "fat pointers" since they carry extra
|
||||
information.
|
||||
|
||||
**Example `&[i32]` :**
|
||||
|
||||
- The first 8 bytes is the actual pointer to the first element in the array (or part of an array the slice refers to)
|
||||
- The second 8 bytes is the length of the slice.
|
||||
|
||||
**Example `&dyn SomeTrait`:**
|
||||
|
||||
This is the type of fat pointer we'll concern ourselves about going forward.
|
||||
`&dyn SomeTrait` is a reference to a trait, or what Rust calls a _trait object_.
|
||||
|
||||
The layout for a pointer to a _trait object_ looks like this:
|
||||
|
||||
- The first 8 bytes points to the `data` for the trait object
|
||||
- The second 8 bytes points to the `vtable` for the trait object
|
||||
|
||||
The reason for this is to allow us to refer to an object we know nothing about
|
||||
except that it implements the methods defined by our trait. To accomplish this
|
||||
we use _dynamic dispatch_.
|
||||
|
||||
Let's explain this in code instead of words by implementing our own trait
|
||||
object from these parts:
|
||||
|
||||
```rust
|
||||
# use std::mem::{align_of, size_of};
|
||||
// A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr)
|
||||
trait Test {
|
||||
fn add(&self) -> i32;
|
||||
fn sub(&self) -> i32;
|
||||
fn mul(&self) -> i32;
|
||||
}
|
||||
|
||||
// This will represent our home-brewed fat pointer to a trait object
|
||||
#[repr(C)]
|
||||
struct FatPointer<'a> {
|
||||
/// A reference is a pointer to an instantiated `Data` instance
|
||||
data: &'a mut Data,
|
||||
/// Since we need to pass in literal values like length and alignment it's
|
||||
/// easiest for us to convert pointers to usize-integers instead of the other way around.
|
||||
vtable: *const usize,
|
||||
}
|
||||
|
||||
// This is the data in our trait object. It's just two numbers we want to operate on.
|
||||
struct Data {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
// ====== function definitions ======
|
||||
fn add(s: &Data) -> i32 {
|
||||
s.a + s.b
|
||||
}
|
||||
fn sub(s: &Data) -> i32 {
|
||||
s.a - s.b
|
||||
}
|
||||
fn mul(s: &Data) -> i32 {
|
||||
s.a * s.b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut data = Data {a: 3, b: 2};
|
||||
// vtable is like special purpose array of pointer-length types with a fixed
|
||||
// format where the three first values contains some general information like
|
||||
// a pointer to drop and the length and data alignment of `data`.
|
||||
let vtable = vec![
|
||||
0, // pointer to `Drop` (which we're not implementing here)
|
||||
size_of::<Data>(), // length of data
|
||||
align_of::<Data>(), // alignment of data
|
||||
|
||||
// we need to make sure we add these in the same order as defined in the Trait.
|
||||
add as usize, // function pointer - try changing the order of `add`
|
||||
sub as usize, // function pointer - and `sub` to see what happens
|
||||
mul as usize, // function pointer
|
||||
];
|
||||
|
||||
let fat_pointer = FatPointer { data: &mut data, vtable: vtable.as_ptr()};
|
||||
let test = unsafe { std::mem::transmute::<FatPointer, &dyn Test>(fat_pointer) };
|
||||
|
||||
// And voalá, it's now a trait object we can call methods on
|
||||
println!("Add: 3 + 2 = {}", test.add());
|
||||
println!("Sub: 3 - 2 = {}", test.sub());
|
||||
println!("Mul: 3 * 2 = {}", test.mul());
|
||||
}
|
||||
```
|
||||
|
||||
Later on, when we implement our own `Waker` we'll actually set up a `vtable`
|
||||
like we do here. The way we create it is slightly different, but now that you know
|
||||
how regular trait objects work you will probably recognize what we're doing which
|
||||
makes it much less mysterious.
|
||||
|
||||
## Bonus section
|
||||
|
||||
You might wonder why the `Waker` was implemented like this and not just as a
|
||||
normal trait?
|
||||
|
||||
The reason is flexibility. Implementing the Waker the way we do here gives a lot
|
||||
of flexibility of choosing what memory management scheme to use.
|
||||
|
||||
The "normal" way is by using an `Arc` to use reference count keep track of when
|
||||
a Waker object can be dropped. However, this is not the only way, you could also
|
||||
use purely global functions and state, or any other way you wish.
|
||||
|
||||
This leaves a lot of options on the table for runtime implementors.
|
||||
|
||||
[rfc2592]:https://github.com/rust-lang/rfcs/blob/master/text/2592-futures.md#waking-up
|
||||
659
src/4_generators_async_await.md
Normal file
@@ -0,0 +1,659 @@
|
||||
# Generators and async/await
|
||||
|
||||
>**Overview:**
|
||||
>
|
||||
>- Understand how the async/await syntax works under the hood
|
||||
>- See first hand why we need `Pin`
|
||||
>- Understand what makes Rust's async model very memory efficient
|
||||
>
|
||||
>The motivation for `Generator`s can be found in [RFC#2033][rfc2033]. It's very
|
||||
>well written and I can recommend reading through it (it talks as much about
|
||||
>async/await as it does about generators).
|
||||
|
||||
## Why learn about generators?
|
||||
|
||||
Generators/yield and async/await are so similar that once you understand one
|
||||
you should be able to understand the other.
|
||||
|
||||
It's much easier for me to provide runnable and short examples using Generators
|
||||
instead of Futures which require us to introduce a lot of concepts now that
|
||||
we'll cover later just to show an example.
|
||||
|
||||
Async/await works like generators but instead of returning a generator it returns
|
||||
a special object implementing the Future trait.
|
||||
|
||||
A small bonus is that you'll have a pretty good introduction to both Generators
|
||||
and Async/Await by the end of this chapter.
|
||||
|
||||
Basically, there were three main options discussed when designing how Rust would
|
||||
handle concurrency:
|
||||
|
||||
1. Stackful coroutines, better known as green threads.
|
||||
2. Using combinators.
|
||||
3. Stackless coroutines, better known as generators.
|
||||
|
||||
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
|
||||
coroutines which Rust uses today.
|
||||
|
||||
### Combinators
|
||||
|
||||
`Futures 0.1` used combinators. If you've worked with Promises in JavaScript,
|
||||
you already know combinators. In Rust they look like this:
|
||||
|
||||
```rust,noplaypen,ignore
|
||||
let future = Connection::connect(conn_str).and_then(|conn| {
|
||||
conn.query("somerequest").map(|row|{
|
||||
SomeStruct::from(row)
|
||||
}).collect::<Vec<SomeStruct>>()
|
||||
});
|
||||
|
||||
let rows: Result<Vec<SomeStruct>, SomeLibraryError> = block_on(future);
|
||||
|
||||
```
|
||||
|
||||
**There are mainly three downsides I'll focus on using this technique:**
|
||||
|
||||
1. The error messages produced could be extremely long and arcane
|
||||
2. Not optimal memory usage
|
||||
3. Did not allow borrowing across combinator steps.
|
||||
|
||||
Point #3, is actually a major drawback with `Futures 0.1`.
|
||||
|
||||
Not allowing borrows across suspension points ends up being very
|
||||
un-ergonomic and to accomplish some tasks it requires extra allocations or
|
||||
copying which is inefficient.
|
||||
|
||||
The reason for the higher than optimal memory usage is that this is basically
|
||||
a callback-based approach, where each closure stores all the data it needs
|
||||
for computation. This means that as we chain these, the memory required to store
|
||||
the needed state increases with each added step.
|
||||
|
||||
### Stackless coroutines/generators
|
||||
|
||||
This is the model used in Rust today. It has a few notable advantages:
|
||||
|
||||
1. It's easy to convert normal Rust code to a stackless coroutine using
|
||||
async/await as keywords (it can even be done using a macro).
|
||||
2. No need for context switching and saving/restoring CPU state
|
||||
3. No need to handle dynamic stack allocation
|
||||
4. Very memory efficient
|
||||
5. Allows us to borrow across suspension points
|
||||
|
||||
The last point is in contrast to `Futures 0.1`. With async/await we can do this:
|
||||
|
||||
```rust, ignore
|
||||
async fn myfn() {
|
||||
let text = String::from("Hello world");
|
||||
let borrowed = &text[0..5];
|
||||
somefuture.await;
|
||||
println!("{}", borrowed);
|
||||
}
|
||||
```
|
||||
|
||||
Async in Rust is implemented using Generators. So to understand how async really
|
||||
works we need to understand generators first. Generators in Rust are implemented
|
||||
as state machines.
|
||||
|
||||
The memory footprint of a chain of computations is defined by _the largest footprint
|
||||
that a single step requires_.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
before we had a concept of `Pin`:
|
||||
|
||||
```rust,noplaypen,ignore
|
||||
#![feature(generators, generator_trait)]
|
||||
use std::ops::{Generator, GeneratorState};
|
||||
|
||||
fn main() {
|
||||
let a: i32 = 4;
|
||||
let mut gen = move || {
|
||||
println!("Hello");
|
||||
yield a * 2;
|
||||
println!("world!");
|
||||
};
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Early on, before there was a consensus about the design of `Pin`, this
|
||||
compiled to something looking similar to this:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut gen = GeneratorA::start(4);
|
||||
|
||||
if let GeneratorState::Yielded(n) = gen.resume() {
|
||||
println!("Got value {}", n);
|
||||
}
|
||||
|
||||
if let GeneratorState::Complete(()) = gen.resume() {
|
||||
()
|
||||
};
|
||||
}
|
||||
|
||||
// If you've ever wondered why the parameters are called Y and R the naming from
|
||||
// the original rfc most likely holds the answer
|
||||
enum GeneratorState<Y, R> {
|
||||
Yielded(Y), // originally called `Yield(Y)`
|
||||
Complete(R), // originally called `Return(R)`
|
||||
}
|
||||
|
||||
trait Generator {
|
||||
type Yield;
|
||||
type Return;
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
}
|
||||
|
||||
enum GeneratorA {
|
||||
Enter(i32),
|
||||
Yield1(i32),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl GeneratorA {
|
||||
fn start(a1: i32) -> Self {
|
||||
GeneratorA::Enter(a1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator for GeneratorA {
|
||||
type Yield = i32;
|
||||
type Return = ();
|
||||
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
// lets us get ownership over current state
|
||||
match std::mem::replace(self, GeneratorA::Exit) {
|
||||
GeneratorA::Enter(a1) => {
|
||||
|
||||
/*----code before yield----*/
|
||||
println!("Hello");
|
||||
let a = a1 * 2;
|
||||
|
||||
*self = GeneratorA::Yield1(a);
|
||||
GeneratorState::Yielded(a)
|
||||
}
|
||||
|
||||
GeneratorA::Yield1(_) => {
|
||||
/*-----code after yield-----*/
|
||||
println!("world!");
|
||||
|
||||
*self = GeneratorA::Exit;
|
||||
GeneratorState::Complete(())
|
||||
}
|
||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
>The `yield` keyword was discussed first in [RFC#1823][rfc1823] and in [RFC#1832][rfc1832].
|
||||
|
||||
Now that you know that the `yield` keyword in reality rewrites your code to become a state machine,
|
||||
you'll also know the basics of how `await` works. It's very similar.
|
||||
|
||||
Now, there are some limitations in our naive state machine above. What happens when you have a
|
||||
`borrow` across a `yield` point?
|
||||
|
||||
We could forbid that, but **one of the major design goals for the async/await syntax has been
|
||||
to allow this**. These kinds of borrows were not possible using `Futures 0.1` so we can't let this
|
||||
limitation just slip and call it a day yet.
|
||||
|
||||
Instead of discussing it in theory, let's look at some code.
|
||||
|
||||
> We'll use the optimized version of the state machines which is used in Rust today. For a more
|
||||
> in depth explanation see [Tyler Mandry's excellent article: How Rust optimizes async/await][optimizing-await]
|
||||
|
||||
```rust,noplaypen,ignore
|
||||
let mut generator = move || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
```
|
||||
|
||||
We'll be hand-coding some versions of a state-machines representing a state
|
||||
machine for the generator defined above.
|
||||
|
||||
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, ignore
|
||||
while let Some(val) = generator.next() {
|
||||
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?
|
||||
|
||||
```rust,compile_fail
|
||||
# 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: &String, // uh, what lifetime should this have?
|
||||
},
|
||||
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> {
|
||||
// lets us get ownership over current state
|
||||
match std::mem::replace(self, GeneratorA::Exit) {
|
||||
GeneratorA::Enter => {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow; // <--- NB!
|
||||
let res = borrowed.len();
|
||||
|
||||
*self = GeneratorA::Yield1 {to_borrow, borrowed};
|
||||
GeneratorState::Yielded(res)
|
||||
}
|
||||
|
||||
GeneratorA::Yield1 {to_borrow, borrowed} => {
|
||||
println!("Hello {}", borrowed);
|
||||
*self = GeneratorA::Exit;
|
||||
GeneratorState::Complete(())
|
||||
}
|
||||
GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you try to compile this you'll get an error (just try it yourself by pressing play).
|
||||
|
||||
What is the lifetime of `&String`. It's not the same as the lifetime of `Self`. It's not `static`.
|
||||
Turns out that it's not possible for us in Rust's syntax to describe this lifetime, which means, that
|
||||
to make this work, we'll have to let the compiler know that _we_ control this correctly ourselves.
|
||||
|
||||
That means turning to unsafe.
|
||||
|
||||
Let's try to write an implementation that will compile using `unsafe`. As you'll
|
||||
see we end up in a _self-referential struct_. A struct which holds references
|
||||
into itself.
|
||||
|
||||
As you'll notice, this compiles just fine!
|
||||
|
||||
```rust
|
||||
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, // NB! This is now a raw pointer!
|
||||
},
|
||||
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()};
|
||||
|
||||
// NB! And we set the pointer to reference the to_borrow string here
|
||||
if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
|
||||
*borrowed = to_borrow;
|
||||
}
|
||||
|
||||
GeneratorState::Yielded(res)
|
||||
}
|
||||
|
||||
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!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Remember that our example is the generator we created which looked like this:
|
||||
|
||||
```rust,noplaypen,ignore
|
||||
let mut gen = move || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
```
|
||||
|
||||
Below is an example of how we could run this state-machine and as you see it
|
||||
does what we'd expect. 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 is that in safe Rust we can still do this:
|
||||
|
||||
_Run the code and compare the results. Do you see the problem?_
|
||||
|
||||
```rust, should_panic
|
||||
# #![feature(never_type)] // Force nightly compiler to be used in playground
|
||||
# // by betting on it's true that this type is named after it's stabilization date...
|
||||
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"? And why did our code segfault?
|
||||
|
||||
Turns out that while the example above compiles just fine, we expose consumers
|
||||
of this API to both possible undefined behavior and other memory errors
|
||||
while using just safe Rust. This is a big problem!
|
||||
|
||||
> I've actually forced the code above to use the nightly version of the compiler.
|
||||
> If you run [the example above on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5cbe9897c0e23a502afd2740c7e78b98),
|
||||
> you'll see that it runs without panicking on the current stable (1.42.0) but
|
||||
> panics on the current nightly (1.44.0). Scary!
|
||||
|
||||
We'll explain exactly what happened here using a slightly simpler example in the next
|
||||
chapter and we'll fix our generator using `Pin` so don't worry, you'll see exactly
|
||||
what goes wrong and see how `Pin` can help us deal with self-referential types safely in a
|
||||
second.
|
||||
|
||||
Before we go and explain the problem in detail, let's finish off this chapter
|
||||
by looking at how generators and the async keyword is related.
|
||||
|
||||
## Async and generators
|
||||
|
||||
Futures in Rust are implemented as state machines much the same way Generators
|
||||
are state machines.
|
||||
|
||||
You might have noticed the similarities in the syntax used in async blocks and
|
||||
the syntax used in generators:
|
||||
|
||||
```rust, ignore
|
||||
let mut gen = move || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
```
|
||||
|
||||
Compare that with a similar example using async blocks:
|
||||
|
||||
```rust, ignore
|
||||
let mut fut = async {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
SomeResource::some_task().await;
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
```
|
||||
|
||||
The difference is that Futures have different states than what a `Generator` would
|
||||
have.
|
||||
|
||||
An async block will return a `Future` instead of a `Generator`, however, the way
|
||||
a Future works and the way a Generator work internally is similar.
|
||||
|
||||
Instead of calling `Generator::resume` we call `Future::poll`, and instead of
|
||||
returning `Yielded` or `Complete` it returns `Pending` or `Ready`. Each `await`
|
||||
point in a future is like a `yield` point in a generator.
|
||||
|
||||
Do you see how they're connected now?
|
||||
|
||||
Thats why knowing how generators work and the challenges they pose also teaches
|
||||
you how futures work and the challenges we need to tackle when working with them.
|
||||
|
||||
The same goes for the challenges of borrowing across yield/await points.
|
||||
|
||||
## Bonus section - self referential generators in Rust today
|
||||
|
||||
Thanks to [PR#45337][pr45337] you can actually run code like the one in our
|
||||
example in Rust today using the `static` keyword on nightly. Try it for
|
||||
yourself:
|
||||
|
||||
>Beware that the API is changing rapidly. As I was writing this book, generators
|
||||
had an API change adding support for a "resume" argument to get passed into the
|
||||
generator closure.
|
||||
>
|
||||
>Follow the progress on the [tracking issue #4312][issue43122] for [RFC#033][rfc2033].
|
||||
|
||||
```rust
|
||||
#![feature(generators, generator_trait)]
|
||||
use std::ops::{Generator, GeneratorState};
|
||||
|
||||
|
||||
pub fn main() {
|
||||
let gen1 = static || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
|
||||
let gen2 = static || {
|
||||
let to_borrow = String::from("Hello");
|
||||
let borrowed = &to_borrow;
|
||||
yield borrowed.len();
|
||||
println!("{} world!", borrowed);
|
||||
};
|
||||
|
||||
let mut pinned1 = Box::pin(gen1);
|
||||
let mut pinned2 = Box::pin(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);
|
||||
};
|
||||
|
||||
let _ = pinned1.as_mut().resume(());
|
||||
let _ = pinned2.as_mut().resume(());
|
||||
}
|
||||
```
|
||||
|
||||
[rfc2033]: https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md
|
||||
[greenthreads]: https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/
|
||||
[rfc1823]: https://github.com/rust-lang/rfcs/pull/1823
|
||||
[rfc1832]: https://github.com/rust-lang/rfcs/pull/1832
|
||||
[optimizing-await]: https://tmandry.gitlab.io/blog/posts/optimizing-await-1/
|
||||
[pr45337]: https://github.com/rust-lang/rust/pull/45337/files
|
||||
[issue43122]: https://github.com/rust-lang/rust/issues/43122
|
||||
314
src/4_pin.md
@@ -1,314 +0,0 @@
|
||||
# Pin
|
||||
|
||||
> **Relevant for**
|
||||
>
|
||||
> 1. Understanding `Generators` and `Futures`
|
||||
> 2. Knowing how to use `Pin` is required when implementing your own `Future`
|
||||
> 3. Understanding how to make self-referential types safe to use in Rust
|
||||
> 4. Learning how borrowing across `await` points is accomplished
|
||||
>
|
||||
> `Pin` was suggested in [RFC#2349][rfc2349]
|
||||
|
||||
We already got a brief introduction of `Pin` in the previous chapters, so we'll
|
||||
start off without any further introduction.
|
||||
|
||||
Let's jump strait to some definitions and then create 10 rules to remember when
|
||||
we work with `Pin`.
|
||||
|
||||
## Definitions
|
||||
|
||||
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`.
|
||||
|
||||
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
|
||||
"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
|
||||
`!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
|
||||
> that were chosen. 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`:
|
||||
>
|
||||
> _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._
|
||||
|
||||
|
||||
For the next paragraph we'll rename these markers to:
|
||||
|
||||
> `!Unpin` = `MustStay` and `Unpin` = `CanMove`
|
||||
|
||||
It just makes it much easier to talk about them.
|
||||
|
||||
## Rules to remember
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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;
|
||||
|
||||
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)]
|
||||
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)}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's walk through this example since we'll be using it the rest of this chapter.
|
||||
|
||||
We have a self-referential struct `Test`. `Test` needs an `init` method to be
|
||||
created which is strange but we'll need that to keep this example as short as
|
||||
possible.
|
||||
|
||||
`Test` provides two methods to get a reference to the value of the fields
|
||||
`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.
|
||||
|
||||
In our main method we first instantiate two instances of `Test` and print out
|
||||
the value of the fields on `test1`. We get:
|
||||
|
||||
```rust, ignore
|
||||
a: test1, b: test1
|
||||
```
|
||||
|
||||
|
||||
Next 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.
|
||||
|
||||
We should expect that printing the fields of `test2` should display the same as
|
||||
`test1` (since the object we printed before the swap has moved there now).
|
||||
|
||||
```rust, ignore
|
||||
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
|
||||
a figure that i hope can help.
|
||||
|
||||
**Fig 1: Before and after swap**
|
||||

|
||||
|
||||
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.
|
||||
|
||||
If we change the example to using `Pin` instead:
|
||||
|
||||
```rust,editable
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
);
|
||||
|
||||
// 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())
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Now, what we've done here is pinning a stack address. That will always be
|
||||
`unsafe` if our type implements `!Unpin` (aka `MustStay`).
|
||||
|
||||
We use some 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.
|
||||
|
||||
> 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.
|
||||
|
||||
The next example solves some of our friction at the cost of a heap allocation.
|
||||
|
||||
```rust, editbable
|
||||
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) -> Pin<Box<Self>> {
|
||||
let a = String::from(txt);
|
||||
let t = Test {
|
||||
a,
|
||||
b: std::ptr::null(),
|
||||
_marker: PhantomPinned,
|
||||
};
|
||||
let mut boxed = Box::pin(t);
|
||||
let self_ptr: *const String = &boxed.as_ref().a;
|
||||
unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };
|
||||
|
||||
boxed
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let mut test1 = Test::new("test1");
|
||||
let mut test2 = Test::new("test2");
|
||||
|
||||
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());
|
||||
}
|
||||
```
|
||||
|
||||
The fact that boxing (heap allocating) a value that implements `!Unpin` is safe
|
||||
makes sense. Once the data is allocated on the heap it will have a stable address.
|
||||
|
||||
There is no need for us as users of the API to take special care and ensure
|
||||
that the self-referential pointer stays valid.
|
||||
|
||||
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.
|
||||
|
||||
### Projection/structural pinning
|
||||
|
||||
In short, projection is a programming language term. `mystruct.field1` is a
|
||||
projection. Structural pinning is using `Pin` on fields. This has several
|
||||
caveats and is not something you'll normally see so I refer to the documentation
|
||||
for that.
|
||||
|
||||
### Pin and Drop
|
||||
|
||||
The `Pin` guarantee exists from the moment the value is pinned until it's dropped.
|
||||
In the `Drop` implementation you take a mutable reference to `self`, which means
|
||||
extra care must be taken when implementing `Drop` for pinned types.
|
||||
|
||||
## Putting it all together
|
||||
|
||||
This is exactly what we'll do when we implement our own `Futures` stay tuned,
|
||||
we're soon finished.
|
||||
|
||||
[rfc2349]: https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md
|
||||
[pin_project]: https://docs.rs/pin-project/
|
||||
[internals_unpin]: https://internals.rust-lang.org/t/naming-pin-anchor-move/6864/12
|
||||
732
src/5_pin.md
Normal file
@@ -0,0 +1,732 @@
|
||||
# Pin
|
||||
|
||||
> **Overview**
|
||||
>
|
||||
> 1. Learn how to use `Pin` and why it's required when implementing your own `Future`
|
||||
> 2. Understand how to make self-referential types safe to use in Rust
|
||||
> 3. Learn 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]
|
||||
|
||||
Let's jump straight to it. Pinning is one of those subjects which is hard to wrap
|
||||
your head around in the start, but once you unlock a mental model for it
|
||||
it gets significantly easier to reason about.
|
||||
|
||||
## Definitions
|
||||
|
||||
Pin wraps a pointer. A reference to an object is a pointer. Pin gives some
|
||||
guarantees about the _pointee_ (the data it points to) which we'll explore further
|
||||
in this chapter.
|
||||
|
||||
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`.
|
||||
|
||||
Yep, you're right, that's double negation right there. `!Unpin` means
|
||||
"not-un-pin".
|
||||
|
||||
> _This naming scheme is one of Rust's safety features where it deliberately
|
||||
> tests if you're too tired to safely implement a type with this marker. If
|
||||
> 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 that were chosen. Naming is not easy, and I considered renaming
|
||||
`Unpin` and `!Unpin` in this book to make them easier to reason about.
|
||||
|
||||
However, an experienced member of the Rust community convinced me that there
|
||||
are just too many nuances and edge-cases to consider which are 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.
|
||||
|
||||
If you want to you can read a bit of the discussion from the
|
||||
[internals thread][internals_unpin].
|
||||
|
||||
## Pinning and self-referential structs
|
||||
|
||||
Let's start where we left off in the last chapter by making the problem we
|
||||
saw using a self-references in our generator a lot simpler by making
|
||||
some self-referential structs that are easier to reason about than our
|
||||
state machines:
|
||||
|
||||
For now our example will look like this:
|
||||
|
||||
```rust, ignore
|
||||
use std::pin::Pin;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Test {
|
||||
a: String,
|
||||
b: *const String,
|
||||
}
|
||||
|
||||
impl Test {
|
||||
fn new(txt: &str) -> Self {
|
||||
Test {
|
||||
a: String::from(txt),
|
||||
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)}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's walk through this example since we'll be using it the rest of this chapter.
|
||||
|
||||
We have a self-referential struct `Test`. `Test` needs an `init` method to be
|
||||
created which is strange but we'll need that to keep this example as short as
|
||||
possible.
|
||||
|
||||
`Test` provides two methods to get a reference to the value of the fields
|
||||
`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.
|
||||
|
||||
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
|
||||
the value of the fields on `test1`. We get what we'd expect:
|
||||
|
||||
```rust, ignore
|
||||
a: test1, b: test1
|
||||
a: test2, b: test2
|
||||
```
|
||||
|
||||
Let's see what happens if we swap the data stored at the memory location `test1` with the
|
||||
data stored at the memory location `test2` and vice a versa.
|
||||
|
||||
```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);
|
||||
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
|
||||
a: test1, b: test1
|
||||
a: test1, b: test1
|
||||
```
|
||||
|
||||
But instead we get:
|
||||
|
||||
```rust, ignore
|
||||
a: test1, b: test1
|
||||
a: test1, b: test2
|
||||
```
|
||||
|
||||
The pointer to `test2.b` still points to the old location which is inside `test1`
|
||||
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 you're 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 2: Before and after swap**
|
||||

|
||||
|
||||
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.
|
||||
|
||||
## Pinning to the stack
|
||||
|
||||
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::marker::PhantomPinned;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Test {
|
||||
a: String,
|
||||
b: *const String,
|
||||
_marker: PhantomPinned,
|
||||
}
|
||||
|
||||
|
||||
impl Test {
|
||||
fn new(txt: &str) -> Self {
|
||||
Test {
|
||||
a: String::from(txt),
|
||||
b: std::ptr::null(),
|
||||
_marker: PhantomPinned, // This makes our type `!Unpin`
|
||||
}
|
||||
}
|
||||
fn init<'a>(self: Pin<&'a mut Self>) {
|
||||
let self_ptr: *const String = &self.a;
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
this.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 an object to the stack. 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() {
|
||||
// test1 is safe to move before we initialize it
|
||||
let mut test1 = Test::new("test1");
|
||||
// Notice how we shadow `test1` to prevent it from being accessed again
|
||||
let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };
|
||||
Test::init(test1.as_mut());
|
||||
|
||||
let mut test2 = Test::new("test2");
|
||||
let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };
|
||||
Test::init(test2.as_mut());
|
||||
|
||||
println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));
|
||||
println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.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<'a>(self: Pin<&'a mut Self>) {
|
||||
# let self_ptr: *const String = &self.a;
|
||||
# let this = unsafe { self.get_unchecked_mut() };
|
||||
# this.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, if we try to pull the same trick which got us in to trouble the last time
|
||||
you'll get a compilation error.
|
||||
|
||||
```rust, compile_fail
|
||||
pub fn main() {
|
||||
let mut test1 = Test::new("test1");
|
||||
let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };
|
||||
Test::init(test1.as_mut());
|
||||
|
||||
let mut test2 = Test::new("test2");
|
||||
let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };
|
||||
Test::init(test2.as_mut());
|
||||
|
||||
println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));
|
||||
std::mem::swap(test1.get_mut(), test2.get_mut());
|
||||
println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.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 {
|
||||
# Test {
|
||||
# a: String::from(txt),
|
||||
# b: std::ptr::null(),
|
||||
# _marker: PhantomPinned, // This makes our type `!Unpin`
|
||||
# }
|
||||
# }
|
||||
# fn init<'a>(self: Pin<&'a mut Self>) {
|
||||
# let self_ptr: *const String = &self.a;
|
||||
# let this = unsafe { self.get_unchecked_mut() };
|
||||
# this.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) }
|
||||
# }
|
||||
# }
|
||||
```
|
||||
|
||||
As you see from the error you get by running the code the type system prevents
|
||||
us from swapping the pinned pointers.
|
||||
|
||||
> 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" are invalidated.
|
||||
>
|
||||
> It also puts a lot of responsibility in your hands if you pin an object to the
|
||||
> stack. A mistake that is easy to make is, forgetting to shadow the original variable
|
||||
> since you could drop the `Pin` and access the old value after it's initialized
|
||||
> like this:
|
||||
>
|
||||
> ```rust
|
||||
> fn main() {
|
||||
> let mut test1 = Test::new("test1");
|
||||
> let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
|
||||
> Test::init(test1_pin.as_mut());
|
||||
> drop(test1_pin);
|
||||
>
|
||||
> let mut test2 = Test::new("test2");
|
||||
> mem::swap(&mut test1, &mut test2);
|
||||
> println!("Not self referential anymore: {:?}", test1.b);
|
||||
> }
|
||||
> # use std::pin::Pin;
|
||||
> # use std::marker::PhantomPinned;
|
||||
> # use std::mem;
|
||||
> #
|
||||
> # #[derive(Debug)]
|
||||
> # struct Test {
|
||||
> # a: String,
|
||||
> # b: *const String,
|
||||
> # _marker: PhantomPinned,
|
||||
> # }
|
||||
> #
|
||||
> #
|
||||
> # impl Test {
|
||||
> # fn new(txt: &str) -> Self {
|
||||
> # Test {
|
||||
> # a: String::from(txt),
|
||||
> # b: std::ptr::null(),
|
||||
> # _marker: PhantomPinned, // This makes our type `!Unpin`
|
||||
> # }
|
||||
> # }
|
||||
> # fn init<'a>(self: Pin<&'a mut Self>) {
|
||||
> # let self_ptr: *const String = &self.a;
|
||||
> # let this = unsafe { self.get_unchecked_mut() };
|
||||
> # this.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) }
|
||||
> # }
|
||||
> # }
|
||||
> ```
|
||||
|
||||
## 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, edition2018
|
||||
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) -> Pin<Box<Self>> {
|
||||
let t = Test {
|
||||
a: String::from(txt),
|
||||
b: std::ptr::null(),
|
||||
_marker: PhantomPinned,
|
||||
};
|
||||
let mut boxed = Box::pin(t);
|
||||
let self_ptr: *const String = &boxed.as_ref().a;
|
||||
unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };
|
||||
|
||||
boxed
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let mut test1 = Test::new("test1");
|
||||
let mut test2 = Test::new("test2");
|
||||
|
||||
println!("a: {}, b: {}", test1.as_ref().a(), test1.as_ref().b());
|
||||
println!("a: {}, b: {}", test2.as_ref().a(), test2.as_ref().b());
|
||||
}
|
||||
```
|
||||
|
||||
The fact that it's safe to pin heap allocated data even if it is `!Unpin`
|
||||
makes sense. Once the data is allocated on the heap it will have a stable address.
|
||||
|
||||
There is no need for us as users of the API to take special care and ensure
|
||||
that the self-referential pointer stays valid.
|
||||
|
||||
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.
|
||||
|
||||
## 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 T 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 they choose 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 uses the type system to prevent
|
||||
certain operations on this value.
|
||||
|
||||
4. Most standard library types implement `Unpin`. The same goes for most
|
||||
"normal" types you encounter in Rust. `Future`s and `Generator`s 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.
|
||||
|
||||
6. The implementation behind objects that are `!Unpin` is most likely unsafe.
|
||||
Moving such a type after it has been pinned 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`
|
||||
(the only way to do it is to create a struct containing raw pointers to itself).
|
||||
|
||||
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 an object to the stack or to the heap.
|
||||
|
||||
9. Pinning a `!Unpin` object to the stack requires `unsafe`
|
||||
|
||||
10. Pinning a `!Unpin` object 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
|
||||
|
||||
In short, projection is a programming language term. `mystruct.field1` is a
|
||||
projection. Structural pinning is using `Pin` on fields. This has several
|
||||
caveats and is not something you'll normally see so I refer to the documentation
|
||||
for that.
|
||||
|
||||
### Pin and Drop
|
||||
|
||||
The `Pin` guarantee exists from the moment the value is pinned until it's dropped.
|
||||
In the `Drop` implementation you take a mutable reference to `self`, which means
|
||||
extra care must be taken when implementing `Drop` for pinned types.
|
||||
|
||||
## Putting it all together
|
||||
|
||||
This is exactly what we'll do when we implement our own `Future`, so stay tuned,
|
||||
we're soon finished.
|
||||
|
||||
[rfc2349]: https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md
|
||||
[pin_project]: https://docs.rs/pin-project/
|
||||
[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`. I've commented along the way to
|
||||
make it easier to spot and understand the changes we need to make.
|
||||
|
||||
```rust
|
||||
#![feature(auto_traits, negative_impls)] // needed to implement `!Unpin`
|
||||
use std::pin::Pin;
|
||||
|
||||
pub fn main() {
|
||||
let gen1 = GeneratorA::start();
|
||||
let gen2 = GeneratorA::start();
|
||||
// Before we pin the data, 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. An object 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 this object is not safe to move after pinning.
|
||||
// In this case, only we as implementors "feel" this, however, if someone is
|
||||
// relying on our Pinned data 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} = this {
|
||||
*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.
|
||||
227
src/7_finished_example.md
Normal file
@@ -0,0 +1,227 @@
|
||||
|
||||
# Our finished code
|
||||
|
||||
Here is the whole example. You can edit it right here in your browser and
|
||||
run it yourself. Have fun!
|
||||
|
||||
```rust,editable,edition2018
|
||||
fn main() {
|
||||
let start = Instant::now();
|
||||
let reactor = Reactor::new();
|
||||
|
||||
let fut1 = async {
|
||||
let val = Task::new(reactor.clone(), 1, 1).await;
|
||||
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||
};
|
||||
|
||||
let fut2 = async {
|
||||
let val = Task::new(reactor.clone(), 2, 2).await;
|
||||
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||
};
|
||||
|
||||
let mainfut = async {
|
||||
fut1.await;
|
||||
fut2.await;
|
||||
};
|
||||
|
||||
block_on(mainfut);
|
||||
}
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
future::Future,
|
||||
mem,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
mpsc::{channel, Sender},
|
||||
Arc, Condvar, Mutex,
|
||||
},
|
||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
|
||||
thread::{self, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
// ============================= EXECUTOR ====================================
|
||||
#[derive(Default)]
|
||||
struct Parker(Mutex<bool>, Condvar);
|
||||
|
||||
impl Parker {
|
||||
fn park(&self) {
|
||||
let mut resumable = self.0.lock().unwrap();
|
||||
while !*resumable {
|
||||
resumable = self.1.wait(resumable).unwrap();
|
||||
}
|
||||
*resumable = false;
|
||||
}
|
||||
|
||||
fn unpark(&self) {
|
||||
*self.0.lock().unwrap() = true;
|
||||
self.1.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||
let parker = Arc::new(Parker::default());
|
||||
let mywaker = Arc::new(MyWaker {
|
||||
parker: parker.clone(),
|
||||
});
|
||||
let waker = mywaker_into_waker(Arc::into_raw(mywaker));
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
// SAFETY: we shadow `future` so it can't be accessed again.
|
||||
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
||||
loop {
|
||||
match Future::poll(future.as_mut(), &mut cx) {
|
||||
Poll::Ready(val) => break val,
|
||||
Poll::Pending => parker.park(),
|
||||
};
|
||||
}
|
||||
}
|
||||
// ====================== FUTURE IMPLEMENTATION ==============================
|
||||
#[derive(Clone)]
|
||||
struct MyWaker {
|
||||
parker: Arc<Parker>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Task {
|
||||
id: usize,
|
||||
reactor: Arc<Mutex<Box<Reactor>>>,
|
||||
data: u64,
|
||||
}
|
||||
|
||||
fn mywaker_wake(s: &MyWaker) {
|
||||
let waker_arc = unsafe { Arc::from_raw(s) };
|
||||
waker_arc.parker.unpark();
|
||||
}
|
||||
|
||||
fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
||||
let arc = unsafe { Arc::from_raw(s) };
|
||||
std::mem::forget(arc.clone()); // increase ref count
|
||||
RawWaker::new(Arc::into_raw(arc) as *const (), &VTABLE)
|
||||
}
|
||||
|
||||
const VTABLE: RawWakerVTable = unsafe {
|
||||
RawWakerVTable::new(
|
||||
|s| mywaker_clone(&*(s as *const MyWaker)), // clone
|
||||
|s| mywaker_wake(&*(s as *const MyWaker)), // wake
|
||||
|s| (*(s as *const MyWaker)).parker.unpark(), // wake by ref (don't decrease refcount)
|
||||
|s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
|
||||
)
|
||||
};
|
||||
|
||||
fn mywaker_into_waker(s: *const MyWaker) -> Waker {
|
||||
let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
||||
unsafe { Waker::from_raw(raw_waker) }
|
||||
}
|
||||
|
||||
impl Task {
|
||||
fn new(reactor: Arc<Mutex<Box<Reactor>>>, data: u64, id: usize) -> Self {
|
||||
Task { id, reactor, data }
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Task {
|
||||
type Output = usize;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut r = self.reactor.lock().unwrap();
|
||||
if r.is_ready(self.id) {
|
||||
*r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
|
||||
Poll::Ready(self.id)
|
||||
} else if let std::collections::hash_map::Entry::Occupied(mut e) = r.tasks.entry(self.id) {
|
||||
e.insert(TaskState::NotReady(cx.waker().clone()));
|
||||
Poll::Pending
|
||||
} else {
|
||||
r.register(self.data, cx.waker().clone(), self.id);
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
// =============================== REACTOR ===================================
|
||||
enum TaskState {
|
||||
Ready,
|
||||
NotReady(Waker),
|
||||
Finished,
|
||||
}
|
||||
struct Reactor {
|
||||
dispatcher: Sender<Event>,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
tasks: HashMap<usize, TaskState>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Event {
|
||||
Close,
|
||||
Timeout(u64, usize),
|
||||
}
|
||||
|
||||
impl Reactor {
|
||||
fn new() -> Arc<Mutex<Box<Self>>> {
|
||||
let (tx, rx) = channel::<Event>();
|
||||
let reactor = Arc::new(Mutex::new(Box::new(Reactor {
|
||||
dispatcher: tx,
|
||||
handle: None,
|
||||
tasks: HashMap::new(),
|
||||
})));
|
||||
|
||||
let reactor_clone = Arc::downgrade(&reactor);
|
||||
let handle = thread::spawn(move || {
|
||||
let mut handles = vec![];
|
||||
for event in rx {
|
||||
let reactor = reactor_clone.clone();
|
||||
match event {
|
||||
Event::Close => break,
|
||||
Event::Timeout(duration, id) => {
|
||||
let event_handle = thread::spawn(move || {
|
||||
thread::sleep(Duration::from_secs(duration));
|
||||
let reactor = reactor.upgrade().unwrap();
|
||||
reactor.lock().map(|mut r| r.wake(id)).unwrap();
|
||||
});
|
||||
handles.push(event_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
handles
|
||||
.into_iter()
|
||||
.for_each(|handle| handle.join().unwrap());
|
||||
});
|
||||
reactor.lock().map(|mut r| r.handle = Some(handle)).unwrap();
|
||||
reactor
|
||||
}
|
||||
|
||||
fn wake(&mut self, id: usize) {
|
||||
let state = self.tasks.get_mut(&id).unwrap();
|
||||
match mem::replace(state, TaskState::Ready) {
|
||||
TaskState::NotReady(waker) => waker.wake(),
|
||||
TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn register(&mut self, duration: u64, waker: Waker, id: usize) {
|
||||
if self.tasks.insert(id, TaskState::NotReady(waker)).is_some() {
|
||||
panic!("Tried to insert a task with id: '{}', twice!", id);
|
||||
}
|
||||
self.dispatcher.send(Event::Timeout(duration, id)).unwrap();
|
||||
}
|
||||
|
||||
fn is_ready(&self, id: usize) -> bool {
|
||||
self.tasks
|
||||
.get(&id)
|
||||
.map(|state| matches!(state, TaskState::Ready))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Reactor {
|
||||
fn drop(&mut self) {
|
||||
self.dispatcher.send(Event::Close).unwrap();
|
||||
self.handle.take().map(|h| h.join().unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## A little side note
|
||||
|
||||
The comments delimiting the Executor, `Future` implementation and Reactor,
|
||||
emphasize on what is part of the language (Future and Waker) and what is not (runtime specifics).
|
||||
Therefore, the comments associate the `Waker` with the `Future` implementation,
|
||||
despite its strong relation with the Executor.
|
||||
@@ -1,208 +0,0 @@
|
||||
|
||||
# Our finished code
|
||||
|
||||
Here is the whole example. You can edit it right here in your browser and
|
||||
run it yourself. Have fun!
|
||||
|
||||
```rust,editable,edition2018
|
||||
use std::{
|
||||
future::Future, pin::Pin, sync::{mpsc::{channel, Sender}, Arc, Mutex},
|
||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
|
||||
thread::{self, JoinHandle}, time::{Duration, Instant}
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let start = Instant::now();
|
||||
let reactor = Reactor::new();
|
||||
let reactor = Arc::new(Mutex::new(reactor));
|
||||
let future1 = Task::new(reactor.clone(), 1, 1);
|
||||
let future2 = Task::new(reactor.clone(), 2, 2);
|
||||
|
||||
let fut1 = async {
|
||||
let val = future1.await;
|
||||
let dur = (Instant::now() - start).as_secs_f32();
|
||||
println!("Future got {} at time: {:.2}.", val, dur);
|
||||
};
|
||||
|
||||
let fut2 = async {
|
||||
let val = future2.await;
|
||||
let dur = (Instant::now() - start).as_secs_f32();
|
||||
println!("Future got {} at time: {:.2}.", val, dur);
|
||||
};
|
||||
|
||||
let mainfut = async {
|
||||
let handle1 = spawn(fut1);
|
||||
let handle2 = spawn(fut2);
|
||||
handle1.await;
|
||||
handle2.await;
|
||||
};
|
||||
|
||||
block_on(mainfut);
|
||||
reactor.lock().map(|mut r| r.close()).unwrap();
|
||||
}
|
||||
|
||||
// ============================= EXECUTOR ====================================
|
||||
fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
|
||||
let waker = waker_into_waker(Arc::into_raw(mywaker));
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
let val = loop {
|
||||
let pinned = unsafe { Pin::new_unchecked(&mut future) };
|
||||
match Future::poll(pinned, &mut cx) {
|
||||
Poll::Ready(val) => break val,
|
||||
Poll::Pending => thread::park(),
|
||||
};
|
||||
};
|
||||
val
|
||||
}
|
||||
|
||||
fn spawn<F: Future>(future: F) -> Pin<Box<F>> {
|
||||
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
|
||||
let waker = waker_into_waker(Arc::into_raw(mywaker));
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
let mut boxed = Box::pin(future);
|
||||
let _ = Future::poll(boxed.as_mut(), &mut cx);
|
||||
boxed
|
||||
}
|
||||
|
||||
// ====================== FUTURE IMPLEMENTATION ==============================
|
||||
#[derive(Clone)]
|
||||
struct MyWaker {
|
||||
thread: thread::Thread,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Task {
|
||||
id: usize,
|
||||
reactor: Arc<Mutex<Reactor>>,
|
||||
data: u64,
|
||||
is_registered: bool,
|
||||
}
|
||||
|
||||
fn mywaker_wake(s: &MyWaker) {
|
||||
let waker_ptr: *const MyWaker = s;
|
||||
let waker_arc = unsafe {Arc::from_raw(waker_ptr)};
|
||||
waker_arc.thread.unpark();
|
||||
}
|
||||
|
||||
fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
||||
let arc = unsafe { Arc::from_raw(s).clone() };
|
||||
std::mem::forget(arc.clone()); // increase ref count
|
||||
RawWaker::new(Arc::into_raw(arc) as *const (), &VTABLE)
|
||||
}
|
||||
|
||||
const VTABLE: RawWakerVTable = unsafe {
|
||||
RawWakerVTable::new(
|
||||
|s| mywaker_clone(&*(s as *const MyWaker)), // clone
|
||||
|s| mywaker_wake(&*(s as *const MyWaker)), // wake
|
||||
|s| mywaker_wake(*(s as *const &MyWaker)), // wake by ref
|
||||
|s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
|
||||
)
|
||||
};
|
||||
|
||||
fn waker_into_waker(s: *const MyWaker) -> Waker {
|
||||
let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
||||
unsafe { Waker::from_raw(raw_waker) }
|
||||
}
|
||||
|
||||
impl Task {
|
||||
fn new(reactor: Arc<Mutex<Reactor>>, data: u64, id: usize) -> Self {
|
||||
Task {
|
||||
id,
|
||||
reactor,
|
||||
data,
|
||||
is_registered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Task {
|
||||
type Output = usize;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut r = self.reactor.lock().unwrap();
|
||||
if r.is_ready(self.id) {
|
||||
Poll::Ready(self.id)
|
||||
} else if self.is_registered {
|
||||
Poll::Pending
|
||||
} else {
|
||||
r.register(self.data, cx.waker().clone(), self.id);
|
||||
drop(r);
|
||||
self.is_registered = true;
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================== REACTOR ===================================
|
||||
struct Reactor {
|
||||
dispatcher: Sender<Event>,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
readylist: Arc<Mutex<Vec<usize>>>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
enum Event {
|
||||
Close,
|
||||
Timeout(Waker, u64, usize),
|
||||
}
|
||||
|
||||
impl Reactor {
|
||||
fn new() -> Self {
|
||||
let (tx, rx) = channel::<Event>();
|
||||
let readylist = Arc::new(Mutex::new(vec![]));
|
||||
let rl_clone = readylist.clone();
|
||||
let mut handles = vec![];
|
||||
let handle = thread::spawn(move || {
|
||||
// This simulates some I/O resource
|
||||
for event in rx {
|
||||
println!("REACTOR: {:?}", event);
|
||||
let rl_clone = rl_clone.clone();
|
||||
match event {
|
||||
Event::Close => break,
|
||||
Event::Timeout(waker, duration, id) => {
|
||||
let event_handle = thread::spawn(move || {
|
||||
thread::sleep(Duration::from_secs(duration));
|
||||
rl_clone.lock().map(|mut rl| rl.push(id)).unwrap();
|
||||
waker.wake();
|
||||
});
|
||||
|
||||
handles.push(event_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
Reactor {
|
||||
readylist,
|
||||
dispatcher: tx,
|
||||
handle: Some(handle),
|
||||
}
|
||||
}
|
||||
|
||||
fn register(&mut self, duration: u64, waker: Waker, data: usize) {
|
||||
self.dispatcher
|
||||
.send(Event::Timeout(waker, duration, data))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.dispatcher.send(Event::Close).unwrap();
|
||||
}
|
||||
|
||||
fn is_ready(&self, id_to_check: usize) -> bool {
|
||||
self.readylist
|
||||
.lock()
|
||||
.map(|rl| rl.iter().any(|id| *id == id_to_check))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Reactor {
|
||||
fn drop(&mut self) {
|
||||
self.handle.take().map(|h| h.join().unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
[Introduction](./introduction.md)
|
||||
|
||||
- [Some background information](./1_background_information.md)
|
||||
- [Trait objects and fat pointers](./2_trait_objects.md)
|
||||
- [Generators](./3_generators_pin.md)
|
||||
- [Pin](./4_pin.md)
|
||||
- [Futures - our main example](./6_future_example.md)
|
||||
- [Finished example (editable)](./8_finished_example.md)
|
||||
- [Background information](./0_background_information.md)
|
||||
- [Futures in Rust](./1_futures_in_rust.md)
|
||||
- [A mental model of how Futures work](./2_a_mental_model_for_futures.md)
|
||||
- [Waker and Context](./3_waker_context.md)
|
||||
- [Generators and async/await](./4_generators_async_await.md)
|
||||
- [Pin](./5_pin.md)
|
||||
- [Implementing Futures](./6_future_example.md)
|
||||
- [Finished example (editable)](./7_finished_example.md)
|
||||
|
||||
[Conclusion and exercises](./conclusion.md)
|
||||
|
||||
BIN
src/assets/futures_animation.gif
Normal file
|
After Width: | Height: | Size: 611 KiB |
BIN
src/assets/slides/Slide1.PNG
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src/assets/slides/Slide10.PNG
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
src/assets/slides/Slide11.PNG
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
src/assets/slides/Slide12.PNG
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
src/assets/slides/Slide13.PNG
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
src/assets/slides/Slide14.PNG
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
src/assets/slides/Slide15.PNG
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
src/assets/slides/Slide16.PNG
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
src/assets/slides/Slide17.PNG
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
src/assets/slides/Slide18.PNG
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/assets/slides/Slide19.PNG
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
src/assets/slides/Slide2.PNG
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src/assets/slides/Slide3.PNG
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/slides/Slide4.PNG
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
src/assets/slides/Slide5.PNG
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
src/assets/slides/Slide6.PNG
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
src/assets/slides/Slide7.PNG
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/assets/slides/Slide8.PNG
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/slides/Slide9.PNG
Normal file
|
After Width: | Height: | Size: 45 KiB |
@@ -1,97 +1,86 @@
|
||||
# Conclusion and exercises
|
||||
|
||||
Congratulations. Good job! If you got this far you must have stayed with me
|
||||
Congratulations. Good job! If you got this far, you must have stayed with me
|
||||
all the way. I hope you enjoyed the ride!
|
||||
|
||||
I'll leave you with some predictions and a set of exercises I'm suggesting for
|
||||
those interested.
|
||||
Remember that you can always leave feedback, suggest improvements or ask questions
|
||||
in the [issue_tracker](https://github.com/cfsamson/books-futures-explained/issues) for this book.
|
||||
I'll try my best to respond to each one of them.
|
||||
|
||||
Futures will be more ergonomic to use with time. For example, instead of having
|
||||
to create a `RawWaker` and so on, the `Waker` will also be possible to implement
|
||||
as a normal `Trait`. It's probably going to be pretty similar to
|
||||
[ArcWake][arcwake].
|
||||
I'll leave you with some suggestions for exercises if you want to explore a little further below.
|
||||
|
||||
There will probably be several more improvements like this, but since relatively
|
||||
few people will actually need implement leaf Futures compared to those that use
|
||||
them, focus will first and foremost be on how ergonomic it's to work with
|
||||
futures inside async/await functions and blocks.
|
||||
Until next time!
|
||||
|
||||
It will still take some time for the ecosystem to migrate over to `Futures 3.0`
|
||||
but since the advantages are so huge, it will not be a split between libraries
|
||||
using `Futures 1.0` and libraries using `Futures 3.0` for long.
|
||||
## Reader exercises
|
||||
|
||||
# Reader exercises
|
||||
So our implementation has taken some obvious shortcuts and could use some improvement.
|
||||
Actually, digging into the code and trying things yourself is a good way to learn. Here are
|
||||
some good exercises if you want to explore more:
|
||||
|
||||
So our implementation has taken some obvious shortcuts and could use some improvement. Actually digging into the code and try things yourself is a good
|
||||
way to learn. Here are some good exercises if you want to explore more:
|
||||
### Avoid wrapping the whole `Reactor` in a mutex and pass it around
|
||||
|
||||
## Avoid `thread::park`
|
||||
First of all, protecting the whole `Reactor` and passing it around is overkill. We're only
|
||||
interested in synchronizing some parts of the information it contains. Try to refactor that
|
||||
out and only synchronize access to what's really needed.
|
||||
|
||||
The big problem using `Thread::park` and `Thread::unpark` is that the user can access these same methods from their own code. Try to use another method to
|
||||
suspend our thread and wake it up again on our command. Some hints:
|
||||
|
||||
* Check out `CondVars`, here are two sources [Wikipedia][condvar_wiki] and the
|
||||
docs for [`CondVar`][condvar_std]
|
||||
* Take a look at crates that help you with this exact problem like [Crossbeam ](https://github.com/crossbeam-rs/crossbeam)\(specifically the [`Parker`](https://docs.rs/crossbeam/0.7.3/crossbeam/sync/struct.Parker.html)\)
|
||||
|
||||
## Avoid wrapping the whole `Reactor` in a mutex and pass it around
|
||||
|
||||
First of all, protecting the whole `Reactor` and passing it around is overkill. We're only interested in synchronizing some parts of the information it contains. Try to refactor that out and only synchronize access to what's really needed.
|
||||
I'd encourage you to have a look at how async_std solves this with a global [runtime](https://github.com/async-rs/async-std/blob/b446cd023084a157b8a531cff65b8df37750be58/src/rt/mod.rs#L15-L23) which includes the [reactor](https://github.com/async-rs/async-std/blob/b446cd023084a157b8a531cff65b8df37750be58/src/rt/runtime.rs#L39-L70)
|
||||
and [how tokio's rutime](https://github.com/tokio-rs/tokio/blob/19a87e090ed528001e0363a30f6165304a710d49/tokio/src/runtime/context.rs#L2-L12) solves the same
|
||||
thing in a slightly different way to get some inspiration.
|
||||
|
||||
* Do you want to pass around a reference to this information using an `Arc`?
|
||||
* Do you want to make a global `Reactor` so it can be accessed from anywhere?
|
||||
|
||||
Next , using a `Mutex` as a synchronization mechanism might be overkill since many methods only reads data.
|
||||
### Building a better executor
|
||||
|
||||
* Could an [`RwLock`](https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html) be more efficient some places?
|
||||
* Could you use any of the synchronization mechanisms in [Crossbeam](https://github.com/crossbeam-rs/crossbeam)?
|
||||
* Do you want to dig into [atomics in Rust and implement a synchronization mechanism](https://cfsamsonbooks.gitbook.io/epoll-kqueue-iocp-explained/appendix-1/atomics-in-rust) of your own?
|
||||
|
||||
## Avoid creating a new Waker for every event
|
||||
|
||||
Right now we create a new instance of a Waker for every event we create. Is this really needed?
|
||||
|
||||
* Could we create one instance and then cache it \(see [this article from `u/sjepang`](https://stjepang.github.io/2020/01/25/build-your-own-block-on.html)\)?
|
||||
* Should we cache it in `thread_local!` storage?
|
||||
* Or should be cache it using a global constant?
|
||||
|
||||
## Could we implement more methods on our executor?
|
||||
|
||||
What about CPU intensive tasks? Right now they'll prevent our executor thread from progressing an handling events. Could you create a thread pool and create a method to send such tasks to the thread pool instead together with a Waker which will wake up the executor thread once the CPU intensive task is done?
|
||||
|
||||
In both `async_std` and `tokio` this method is called `spawn_blocking`, a good place to start is to read the documentation and the code thy use to implement that.
|
||||
|
||||
## Building a better exectuor
|
||||
|
||||
Right now, we can only run one and one future. Most runtimes has a `spawn`
|
||||
Right now, we can only run one and one future only. Most runtimes have a `spawn`
|
||||
function which let's you start off a future and `await` it later so you
|
||||
can run multiple futures concurrently.
|
||||
|
||||
As I'm writing this [@stjepan](https://github.com/stjepang) is writing a blog
|
||||
series about implementing your own executors, and he just released a post
|
||||
on how to accomplish just this you can visit [here](https://stjepang.github.io/2020/01/31/build-your-own-executor.html).
|
||||
He knows what he's talking about so I recommend following that.
|
||||
As I suggested in the start of this book, visiting [@stjepan'sblog series about implementing your own executors](https://web.archive.org/web/20200207092849/https://stjepang.github.io/2020/01/31/build-your-own-executor.html)
|
||||
is the place I would start and take it from there. You could further examine the source code of [smol - A small and fast async runtime](https://github.com/smol-rs/smol) which is a good project to learn from.
|
||||
|
||||
In the [bonus_spawn](https://github.com/cfsamson/examples-futures/tree/bonus_spawn)
|
||||
branch of the example repository you can also find an extremely simplified
|
||||
(and worse) way of accomplishing the same in only a few lines of code.
|
||||
### Create a unique Id for each task
|
||||
|
||||
As we discussed at the end of the main example. What happens if the user pass in
|
||||
the same Id for both events?
|
||||
|
||||
```rust, ignore
|
||||
let future1 = Task::new(reactor.clone(), 1, 1);
|
||||
let future2 = Task::new(reactor.clone(), 2, 1);
|
||||
```
|
||||
|
||||
Right now, it will deadlock since our `poll` method thinks the first poll of
|
||||
`future2` is `future1` getting polled again and swaps out the waker with the
|
||||
one from `future2`. This waker never gets called since the task is never
|
||||
registered.
|
||||
|
||||
It's probably a bad idea to expose the user to this behavior, so we
|
||||
should have a unique Id for each task which we use internally. There are many ways to solve this.
|
||||
Below is two suggestions to get going:
|
||||
|
||||
1. Let the reactor have a `usize` which is incremented on every task creation
|
||||
2. Use a crate like `Guid` to generate an unique Id for each task
|
||||
|
||||
## Further reading
|
||||
|
||||
There are many great resources for further study. In addition to the RFCs and
|
||||
articles I've already linked to in the book, here are some of my suggestions:
|
||||
There are many great resources. In addition to the RFCs and articles I've already
|
||||
linked to in the book, here are some of my suggestions:
|
||||
|
||||
[The official Asyc book](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html)
|
||||
[The official Async book](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html)
|
||||
|
||||
[Tokio tutorial](https://tokio.rs/tokio/tutorial)
|
||||
|
||||
[The async_std book](https://book.async.rs/)
|
||||
|
||||
[smol - a small and fast async runtime](https://github.com/smol-rs/async-executor/blob/master/src/lib.rs)
|
||||
|
||||
[Aron Turon: Designing futures for Rust](https://aturon.github.io/blog/2016/09/07/futures-design/)
|
||||
|
||||
[Steve Klabnik's presentation: Rust's journey to Async/Await](https://www.infoq.com/presentations/rust-2019/)
|
||||
|
||||
[The Tokio Blog](https://tokio.rs/blog/2019-10-scheduler/)
|
||||
|
||||
[Stjepan's blog with a series where he implements an Executor](https://stjepang.github.io/)
|
||||
[Stjepan's blog with a series where he implements an Executor](https://web.archive.org/web/20200207092849/https://stjepang.github.io/2020/01/31/build-your-own-executor.html)
|
||||
|
||||
[Jon Gjengset's video on The Why, What and How of Pinning in Rust](https://youtu.be/DkMwYxfSYNQ)
|
||||
|
||||
@@ -99,4 +88,4 @@ articles I've already linked to in the book, here are some of my suggestions:
|
||||
|
||||
[condvar_std]: https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html
|
||||
[condvar_wiki]: https://en.wikipedia.org/wiki/Monitor_(synchronization)#Condition_variables
|
||||
[arcwake]: https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.13/futures/task/trait.ArcWake.html
|
||||
[arcwake]: https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.13/futures/task/trait.ArcWake.html
|
||||
|
||||
@@ -1,51 +1,77 @@
|
||||
# Futures Explained in 200 Lines of Rust
|
||||
|
||||
This book aims to explain `Futures` in Rust using an example driven approach.
|
||||
This book aims to explain Futures in Rust using an example driven approach,
|
||||
exploring why they're designed the way they are, and how they work. We'll also
|
||||
take a look at some of the alternatives we have when dealing with concurrency
|
||||
in programming.
|
||||
|
||||
The goal is to get a better understanding of `Futures` by implementing a toy
|
||||
`Reactor`, a very simple `Executor` and our own `Futures`.
|
||||
Going into the level of detail I do in this book is not needed to use futures
|
||||
or async/await in Rust. It's for the curious out there that want to know _how_
|
||||
it all works.
|
||||
|
||||
We'll start off a bit differently than most other explanations. Instead of
|
||||
deferring some of the details about what's special about futures in Rust we
|
||||
try to tackle that head on first. We'll be as brief as possible, but as thorough
|
||||
as needed. This way, most questions will be answered and explored up front.
|
||||
## What this book covers
|
||||
|
||||
We'll end up with futures that can run an any executor like `tokio` and `async_str`.
|
||||
This book will try to explain everything you might wonder about up until the
|
||||
topic of different types of executors and runtimes. We'll just implement a very
|
||||
simple runtime in this book introducing some concepts but it's enough to get
|
||||
started.
|
||||
|
||||
In the end I've made some reader exercises you can do if you want to fix some
|
||||
of the most glaring omissions and shortcuts we took and create a slightly better
|
||||
example yourself.
|
||||
[Stjepan Glavina](https://web.archive.org/web/20200812203230/https://github.com/stjepang)
|
||||
has made an excellent series of articles about async runtimes and executors.
|
||||
|
||||
The way you should go about it is to read this book first, then continue
|
||||
reading [Stjepan's articles](https://web.archive.org/web/20200610130514/https://stjepang.github.io/)
|
||||
to learn more about runtimes and how they work, especially:
|
||||
|
||||
1. [Build your own block_on()](https://web.archive.org/web/20200511234503/https://stjepang.github.io/2020/01/25/build-your-own-block-on.html)
|
||||
2. [Build your own executor](https://web.archive.org/web/20200207092849/https://stjepang.github.io/2020/01/31/build-your-own-executor.html)
|
||||
|
||||
You should also check out the [smol](https://github.com/smol-rs/smol) runtime
|
||||
as it's a real runtime made by the same author. It's well commented and made to be easy to learn from.
|
||||
|
||||
I've limited myself to a 200 line main example (hence the title) to limit the
|
||||
scope and introduce an example that can easily be explored further.
|
||||
|
||||
However, there is a lot to digest and it's not what I would call easy, but we'll
|
||||
take everything step by step so get a cup of tea and relax.
|
||||
|
||||
I hope you enjoy the ride.
|
||||
|
||||
> This book is developed in the open, and contributions are welcome. You'll find
|
||||
> [the repository for the book itself here][book_repo]. The final example which
|
||||
> you can clone, fork or copy [can be found here][example_repo]
|
||||
> you can clone, fork or copy [can be found here][example_repo]. Any suggestions
|
||||
> or improvements can be filed as a PR or in the issue tracker for the book.
|
||||
>
|
||||
> As always, all kinds of feedback is welcome.
|
||||
|
||||
## What does this book give you that isn't covered elsewhere?
|
||||
## Reader exercises and further reading
|
||||
|
||||
There are many good resources and examples already. First
|
||||
of all, this book will focus on `Futures` and `async/await` specifically and
|
||||
not in the context of any specific runtime.
|
||||
In the last [chapter](conclusion.md) I've taken the liberty to suggest some
|
||||
small exercises if you want to explore a little further.
|
||||
|
||||
Secondly, I've always found small runnable examples very exiting to learn from.
|
||||
Thanks to [Mdbook][mdbook] the examples can even be edited and explored further
|
||||
by uncommenting certain lines or adding new ones yourself. I use that quite a
|
||||
but throughout so keep an eye out when reading through editable code segments.
|
||||
|
||||
It's all code that you can download, play with and learn from.
|
||||
|
||||
We'll and end up with an understandable example including a `Future`
|
||||
implementation, an `Executor` and a `Reactor` in less than 200 lines of code.
|
||||
We don't rely on any dependencies or real I/O which means it's very easy to
|
||||
explore further and try your own ideas.
|
||||
This book is also the fourth book I have written about concurrent programming
|
||||
in Rust. If you like it, you might want to check out the others as well:
|
||||
|
||||
- [Green Threads Explained in 200 lines of rust](https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/)
|
||||
- [The Node Experiment - Exploring Async Basics with Rust](https://cfsamson.github.io/book-exploring-async-basics/)
|
||||
- [Epoll, Kqueue and IOCP Explained with Rust](https://cfsamsonbooks.gitbook.io/epoll-kqueue-iocp-explained/)
|
||||
|
||||
## Credits and thanks
|
||||
|
||||
I'll like to take the chance of thanking the people behind `mio`, `tokio`,
|
||||
`async_std`, `Futures`, `libc`, `crossbeam` and many other libraries which so
|
||||
much is built upon. Even the RFCs that much of the design is built upon is
|
||||
very well written and very helpful. So thanks!
|
||||
I'd like to take this chance to thank the people behind `mio`, `tokio`,
|
||||
`async_std`, `futures`, `libc`, `crossbeam` which underpins so much of the
|
||||
async ecosystem and and rarely gets enough praise in my eyes.
|
||||
|
||||
A special thanks to [jonhoo](https://twitter.com/jonhoo) who was kind enough to
|
||||
give me some valuable feedback on a very early draft of this book. He has not
|
||||
read the finished product, but a big thanks is definitely due.
|
||||
|
||||
Thanks to [@ckaran](https://github.com/ckaran) for suggestions and contributions in chapter 2 and 3.
|
||||
|
||||
## Translations
|
||||
|
||||
[This book has been translated to Chinese](https://stevenbai.top/rust/futures_explained_in_200_lines_of_rust/) by [nkbai](https://github.com/nkbai).
|
||||
|
||||
[mdbook]: https://github.com/rust-lang/mdBook
|
||||
[book_repo]: https://github.com/cfsamson/books-futures-explained
|
||||
[example_repo]: https://github.com/cfsamson/examples-futures
|
||||
[example_repo]: https://github.com/cfsamson/examples-futures
|
||||
|
||||
@@ -1,64 +1,68 @@
|
||||
/*
|
||||
/* Base16 Atelier Dune Light - Theme */
|
||||
/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */
|
||||
/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */
|
||||
|
||||
Visual Studio-like style based on original C# coloring by Jason Diamond <jason@diamond.name>
|
||||
/* Atelier-Dune Comment */
|
||||
.hljs-comment {
|
||||
color: rgb(160, 160, 160);;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-quote {
|
||||
color: #AAA;
|
||||
}
|
||||
|
||||
/* Atelier-Dune Red */
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #d73737;
|
||||
}
|
||||
|
||||
/* Atelier-Dune Orange */
|
||||
.hljs-number,
|
||||
.hljs-meta,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #b65611;
|
||||
}
|
||||
|
||||
/* Atelier-Dune Green */
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet {
|
||||
color: #60ac39;
|
||||
}
|
||||
|
||||
/* Atelier-Dune Blue */
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #6684e1;
|
||||
}
|
||||
|
||||
/* Atelier-Dune Purple */
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #b854d4;
|
||||
}
|
||||
|
||||
*/
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
background: #f1f1f1;
|
||||
color: #6e6b5e;
|
||||
padding: 0.5em;
|
||||
background: rgba(253, 153, 3, 0.027);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote,
|
||||
.hljs-variable {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-built_in,
|
||||
.hljs-name,
|
||||
.hljs-tag {
|
||||
color: #00f;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-attribute,
|
||||
.hljs-literal,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-addition {
|
||||
color: #a31515;
|
||||
}
|
||||
|
||||
.hljs-deletion,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-meta {
|
||||
color: #2b91af;
|
||||
}
|
||||
|
||||
.hljs-doctag {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.hljs-attr {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-link {
|
||||
color: #00b0e8;
|
||||
}
|
||||
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -66,3 +70,13 @@ Visual Studio-like style based on original C# coloring by Jason Diamond <jason@d
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #22863a;
|
||||
background-color: #f0fff4;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #b31d28;
|
||||
background-color: #ffeef0;
|
||||
}
|
||||
|
||||