├── .gitignore ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── SUMMARY.md ├── book.toml ├── book ├── .nojekyll ├── 0_background_information.html ├── 1_futures_in_rust.html ├── 2_waker_context.html ├── 3_generators_async_await.html ├── 4_pin.html ├── 6_future_example.html ├── 8_finished_example.html ├── FontAwesome │ ├── css │ │ └── font-awesome.css │ └── fonts │ │ ├── FontAwesome.ttf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── ace.js ├── assets │ ├── reactorexecutor.png │ └── swap_problem.jpg ├── ayu-highlight.css ├── book.js ├── clipboard.min.js ├── conclusion.html ├── css │ ├── chrome.css │ ├── general.css │ ├── print.css │ └── variables.css ├── editor.js ├── elasticlunr.min.js ├── favicon.png ├── highlight.css ├── highlight.js ├── index.html ├── introduction.html ├── mark.min.js ├── mode-rust.js ├── print.html ├── searcher.js ├── searchindex.js ├── searchindex.json ├── theme-dawn.js ├── theme-tomorrow_night.js └── tomorrow-night.css ├── examples └── bonus_example │ ├── cargo.toml │ └── src │ └── main.rs ├── scrapped_chapters ├── 5_reactor_executor.md └── introduction.md ├── src ├── 0_background_information.md ├── 1_futures_in_rust.md ├── 2_waker_context.md ├── 3_generators_async_await.md ├── 4_pin.md ├── 6_future_example.md ├── 8_finished_example.md ├── SUMMARY.md ├── assets │ ├── reactorexecutor.png │ └── swap_problem.jpg ├── conclusion.md └── introduction.md └── theme └── highlight.css /.gitignore: -------------------------------------------------------------------------------- 1 | ./book 2 | ./.vscode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | cache: 5 | - cargo 6 | 7 | rust: 8 | - nightly 9 | 10 | before_script: 11 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 12 | - (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook) 13 | - cargo install-update -a 14 | 15 | script: 16 | - mdbook build ./ && mdbook test ./ 17 | 18 | deploy: 19 | provider: pages 20 | skip-cleanup: true 21 | github-token: $GITHUB_TOKEN 22 | local-dir: ./book 23 | keep-history: false 24 | on: 25 | branch: master -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "spellright.language": [], 3 | "spellright.documentTypes": [ 4 | "latex", 5 | "plaintext" 6 | ] 7 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2019 Carl Fredrik Samson (cfsamson@gmail.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 200行代码讲透RUST FUTURES 3 | 本书发布地址: [https://stevenbai.top/books-futures-explained/book/](https://stevenbai.top/books-futures-explained/book/) 4 | 5 | 本书旨在通过示例驱动的方法来解释Rust中的“ Futures”,探讨为什么要以自己的方式设计它们,以及它们如何工作。我们还将介绍处理编程并发时的一些替代方法。 6 | 7 | 如果只是在Rust中使用Future或`async/await`,是不需要深入阅读本书的。它是为那些想知道Future是如何工作的好奇猫设计的。 8 | 9 | ## 本书涵盖的内容 10 | 11 | 本书将尝试解释您可能想知道的所有内容,包括不同类型的执行器和运行时。我们将在本书中实现一个非常简单的运行时,介绍一些概念,但这足以入门。 12 | 13 | [Stjepan Glavina](https://github.com/stjepang)撰写了一系列有关异步运行时和执行器的出色文章,传闻他将会有更大的动作(Stjepan大神实现的异步运行时[smol](https://github.com/stjepang/smol)已经上线了)。 14 | 15 | 建议先阅读本书,然后继续阅读Stjepan的文章(https://stjepang.github.io/),以了解有关运行时及其运行方式的更多信息,尤其是: 16 | 17 | 1. [构建自己的block_on](https://stevenbai.top/rust/build_your_own_block_on/) [原文地址](https://stjepang.github.io/2020/01/25/build-your-own-block-on.html) 18 | 2. [构建自己的执行程序](https://stevenbai.top/rust/build_your_own_executor/) [原文地址](https://stjepang.github.io/2020/01/31/build-your-own-executor.html) 19 | 20 | 21 | ## 贡献 22 | 23 | 欢迎各种贡献。包括但不限于拼写,措辞,语法,语义等. 24 | 25 | 非常欢迎您对内容进行更改,如果您能通过PR做出贡献,我将不胜感激。 26 | 27 | 欢迎在Issues中提供反馈,问题或讨论。 28 | 29 | 30 | ## License 31 | 32 | This book is MIT licensed. 33 | 34 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Thought dump](README.md) 4 | * [Introduction](introduction.md) 5 | * [Reader excercises](reader-excercises.md) 6 | 7 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Carl Fredrik Samson"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Futures Explained in 200 Lines of Rust" 7 | description = "This book aims to explain Futures in Rust using an example driven approach." 8 | 9 | 10 | [output.html] 11 | git-repository-url = "https://github.com/cfsamson/books-futures-explained" 12 | google-analytics = "UA-157536992-1" 13 | 14 | [output.html.playpen] 15 | editable = true 16 | copy-js = true 17 | line-numbers = true -------------------------------------------------------------------------------- /book/.nojekyll: -------------------------------------------------------------------------------- 1 | This file makes sure that Github Pages doesn't process mdBook's output. -------------------------------------------------------------------------------- /book/FontAwesome/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/book/FontAwesome/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /book/FontAwesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/book/FontAwesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /book/FontAwesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/book/FontAwesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /book/FontAwesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/book/FontAwesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /book/FontAwesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/book/FontAwesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /book/assets/reactorexecutor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/book/assets/reactorexecutor.png -------------------------------------------------------------------------------- /book/assets/swap_problem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/book/assets/swap_problem.jpg -------------------------------------------------------------------------------- /book/ayu-highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | Based off of the Ayu theme 3 | Original by Dempfi (https://github.com/dempfi/ayu) 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | overflow-x: auto; 9 | background: #191f26; 10 | color: #e6e1cf; 11 | padding: 0.5em; 12 | } 13 | 14 | .hljs-comment, 15 | .hljs-quote { 16 | color: #5c6773; 17 | font-style: italic; 18 | } 19 | 20 | .hljs-variable, 21 | .hljs-template-variable, 22 | .hljs-attribute, 23 | .hljs-attr, 24 | .hljs-regexp, 25 | .hljs-link, 26 | .hljs-selector-id, 27 | .hljs-selector-class { 28 | color: #ff7733; 29 | } 30 | 31 | .hljs-number, 32 | .hljs-meta, 33 | .hljs-builtin-name, 34 | .hljs-literal, 35 | .hljs-type, 36 | .hljs-params { 37 | color: #ffee99; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-bullet { 42 | color: #b8cc52; 43 | } 44 | 45 | .hljs-title, 46 | .hljs-built_in, 47 | .hljs-section { 48 | color: #ffb454; 49 | } 50 | 51 | .hljs-keyword, 52 | .hljs-selector-tag, 53 | .hljs-symbol { 54 | color: #ff7733; 55 | } 56 | 57 | .hljs-name { 58 | color: #36a3d9; 59 | } 60 | 61 | .hljs-tag { 62 | color: #00568d; 63 | } 64 | 65 | .hljs-emphasis { 66 | font-style: italic; 67 | } 68 | 69 | .hljs-strong { 70 | font-weight: bold; 71 | } 72 | 73 | .hljs-addition { 74 | color: #91b362; 75 | } 76 | 77 | .hljs-deletion { 78 | color: #d96c75; 79 | } 80 | -------------------------------------------------------------------------------- /book/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.4 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n 2 | 3 | 4 | 5 | 6 | 结论和练习 - Futures Explained in 200 Lines of Rust 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 41 | 42 | 43 | 57 | 58 | 59 | 69 | 70 | 71 | 81 | 82 | 88 | 89 |
90 | 91 |
92 | 93 | 94 | 129 | 130 | 131 | 141 | 142 | 143 | 144 | 151 | 152 |
153 |
154 |

结论和练习

155 |

我们的实现采取了一些明显的捷径,可以使用一些改进。 实际上,深入研究代码并自己尝试一些事情是一个很好的学习方式。 如果你想探索更多,这里有一些很好的练习

156 |

编码park

157 |

使用 Thread: : park 和 Thread: : unpark 的大问题是用户可以从自己的代码访问这些相同的方法。 尝试使用另一种方法来挂起线程并根据我们的命令再次唤醒它。 一些提示:

158 | 162 |

编码传递reactor

163 |

避免包装整个Reactor in a mutex and pass it around 在互斥对象中传递

164 |

首先,保护整个reactor并传递它是过分的。 我们只对同步它包含的部分信息感兴趣。 尝试将其重构出来,只同步访问真正需要的内容。

165 |

我建议您看看async_std驱动程序是如何实现的,以及tokio调度程序是如何实现的,以获得一些灵感。

166 |
    167 |
  • 你想使用Arc来传递这些信息的引用?
  • 168 |
  • 你是否想创建一个全局的reactor,这样他可以随时随地被访问?
  • 169 |
170 |

创建一个更好的执行器

171 |

现在我们只支持一个一个Future运行. 大多数运行时都有一个spawn函数,让你能够启动一个future,然后await它. 这样你就可以同时运行多个Future.

172 |

正如我在本书开头所建议的那样,访问stjepan 的博客系列,了解如何实现你自己的执行者,是我将要开始的地方,并从那里开始。

173 |

进一步阅读

174 | 200 | 201 |
202 | 203 | 215 |
216 |
217 | 218 | 227 | 228 |
229 | 230 | 231 | 232 | 233 | 234 | 249 | 250 | 251 | 252 | 255 | 256 | 257 | 258 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /book/css/chrome.css: -------------------------------------------------------------------------------- 1 | /* CSS for UI elements (a.k.a. chrome) */ 2 | 3 | @import 'variables.css'; 4 | 5 | ::-webkit-scrollbar { 6 | background: var(--bg); 7 | } 8 | ::-webkit-scrollbar-thumb { 9 | background: var(--scrollbar); 10 | } 11 | html { 12 | scrollbar-color: var(--scrollbar) var(--bg); 13 | } 14 | #searchresults a, 15 | .content a:link, 16 | a:visited, 17 | a > .hljs { 18 | color: var(--links); 19 | } 20 | 21 | /* Menu Bar */ 22 | 23 | #menu-bar, 24 | #menu-bar-hover-placeholder { 25 | z-index: 101; 26 | margin: auto calc(0px - var(--page-padding)); 27 | } 28 | #menu-bar { 29 | position: relative; 30 | display: flex; 31 | flex-wrap: wrap; 32 | background-color: var(--bg); 33 | border-bottom-color: var(--bg); 34 | border-bottom-width: 1px; 35 | border-bottom-style: solid; 36 | } 37 | #menu-bar.sticky, 38 | .js #menu-bar-hover-placeholder:hover + #menu-bar, 39 | .js #menu-bar:hover, 40 | .js.sidebar-visible #menu-bar { 41 | position: -webkit-sticky; 42 | position: sticky; 43 | top: 0 !important; 44 | } 45 | #menu-bar-hover-placeholder { 46 | position: sticky; 47 | position: -webkit-sticky; 48 | top: 0; 49 | height: var(--menu-bar-height); 50 | } 51 | #menu-bar.bordered { 52 | border-bottom-color: var(--table-border-color); 53 | } 54 | #menu-bar i, #menu-bar .icon-button { 55 | position: relative; 56 | padding: 0 8px; 57 | z-index: 10; 58 | line-height: var(--menu-bar-height); 59 | cursor: pointer; 60 | transition: color 0.5s; 61 | } 62 | @media only screen and (max-width: 420px) { 63 | #menu-bar i, #menu-bar .icon-button { 64 | padding: 0 5px; 65 | } 66 | } 67 | 68 | .icon-button { 69 | border: none; 70 | background: none; 71 | padding: 0; 72 | color: inherit; 73 | } 74 | .icon-button i { 75 | margin: 0; 76 | } 77 | 78 | .right-buttons { 79 | margin: 0 15px; 80 | } 81 | .right-buttons a { 82 | text-decoration: none; 83 | } 84 | 85 | .left-buttons { 86 | display: flex; 87 | margin: 0 5px; 88 | } 89 | .no-js .left-buttons { 90 | display: none; 91 | } 92 | 93 | .menu-title { 94 | display: inline-block; 95 | font-weight: 200; 96 | font-size: 2rem; 97 | line-height: var(--menu-bar-height); 98 | text-align: center; 99 | margin: 0; 100 | flex: 1; 101 | white-space: nowrap; 102 | overflow: hidden; 103 | text-overflow: ellipsis; 104 | } 105 | .js .menu-title { 106 | cursor: pointer; 107 | } 108 | 109 | .menu-bar, 110 | .menu-bar:visited, 111 | .nav-chapters, 112 | .nav-chapters:visited, 113 | .mobile-nav-chapters, 114 | .mobile-nav-chapters:visited, 115 | .menu-bar .icon-button, 116 | .menu-bar a i { 117 | color: var(--icons); 118 | } 119 | 120 | .menu-bar i:hover, 121 | .menu-bar .icon-button:hover, 122 | .nav-chapters:hover, 123 | .mobile-nav-chapters i:hover { 124 | color: var(--icons-hover); 125 | } 126 | 127 | /* Nav Icons */ 128 | 129 | .nav-chapters { 130 | font-size: 2.5em; 131 | text-align: center; 132 | text-decoration: none; 133 | 134 | position: fixed; 135 | top: 0; 136 | bottom: 0; 137 | margin: 0; 138 | max-width: 150px; 139 | min-width: 90px; 140 | 141 | display: flex; 142 | justify-content: center; 143 | align-content: center; 144 | flex-direction: column; 145 | 146 | transition: color 0.5s, background-color 0.5s; 147 | } 148 | 149 | .nav-chapters:hover { 150 | text-decoration: none; 151 | background-color: var(--theme-hover); 152 | transition: background-color 0.15s, color 0.15s; 153 | } 154 | 155 | .nav-wrapper { 156 | margin-top: 50px; 157 | display: none; 158 | } 159 | 160 | .mobile-nav-chapters { 161 | font-size: 2.5em; 162 | text-align: center; 163 | text-decoration: none; 164 | width: 90px; 165 | border-radius: 5px; 166 | background-color: var(--sidebar-bg); 167 | } 168 | 169 | .previous { 170 | float: left; 171 | } 172 | 173 | .next { 174 | float: right; 175 | right: var(--page-padding); 176 | } 177 | 178 | @media only screen and (max-width: 1080px) { 179 | .nav-wide-wrapper { display: none; } 180 | .nav-wrapper { display: block; } 181 | } 182 | 183 | @media only screen and (max-width: 1380px) { 184 | .sidebar-visible .nav-wide-wrapper { display: none; } 185 | .sidebar-visible .nav-wrapper { display: block; } 186 | } 187 | 188 | /* Inline code */ 189 | 190 | :not(pre) > .hljs { 191 | display: inline; 192 | padding: 0.1em 0.3em; 193 | border-radius: 3px; 194 | } 195 | 196 | :not(pre):not(a) > .hljs { 197 | color: var(--inline-code-color); 198 | overflow-x: initial; 199 | } 200 | 201 | a:hover > .hljs { 202 | text-decoration: underline; 203 | } 204 | 205 | pre { 206 | position: relative; 207 | } 208 | pre > .buttons { 209 | position: absolute; 210 | z-index: 100; 211 | right: 5px; 212 | top: 5px; 213 | 214 | color: var(--sidebar-fg); 215 | cursor: pointer; 216 | } 217 | pre > .buttons :hover { 218 | color: var(--sidebar-active); 219 | } 220 | pre > .buttons i { 221 | margin-left: 8px; 222 | } 223 | pre > .buttons button { 224 | color: inherit; 225 | background: transparent; 226 | border: none; 227 | cursor: inherit; 228 | } 229 | pre > .result { 230 | margin-top: 10px; 231 | } 232 | 233 | /* Search */ 234 | 235 | #searchresults a { 236 | text-decoration: none; 237 | } 238 | 239 | mark { 240 | border-radius: 2px; 241 | padding: 0 3px 1px 3px; 242 | margin: 0 -3px -1px -3px; 243 | background-color: var(--search-mark-bg); 244 | transition: background-color 300ms linear; 245 | cursor: pointer; 246 | } 247 | 248 | mark.fade-out { 249 | background-color: rgba(0,0,0,0) !important; 250 | cursor: auto; 251 | } 252 | 253 | .searchbar-outer { 254 | margin-left: auto; 255 | margin-right: auto; 256 | max-width: var(--content-max-width); 257 | } 258 | 259 | #searchbar { 260 | width: 100%; 261 | margin: 5px auto 0px auto; 262 | padding: 10px 16px; 263 | transition: box-shadow 300ms ease-in-out; 264 | border: 1px solid var(--searchbar-border-color); 265 | border-radius: 3px; 266 | background-color: var(--searchbar-bg); 267 | color: var(--searchbar-fg); 268 | } 269 | #searchbar:focus, 270 | #searchbar.active { 271 | box-shadow: 0 0 3px var(--searchbar-shadow-color); 272 | } 273 | 274 | .searchresults-header { 275 | font-weight: bold; 276 | font-size: 1em; 277 | padding: 18px 0 0 5px; 278 | color: var(--searchresults-header-fg); 279 | } 280 | 281 | .searchresults-outer { 282 | margin-left: auto; 283 | margin-right: auto; 284 | max-width: var(--content-max-width); 285 | border-bottom: 1px dashed var(--searchresults-border-color); 286 | } 287 | 288 | ul#searchresults { 289 | list-style: none; 290 | padding-left: 20px; 291 | } 292 | ul#searchresults li { 293 | margin: 10px 0px; 294 | padding: 2px; 295 | border-radius: 2px; 296 | } 297 | ul#searchresults li.focus { 298 | background-color: var(--searchresults-li-bg); 299 | } 300 | ul#searchresults span.teaser { 301 | display: block; 302 | clear: both; 303 | margin: 5px 0 0 20px; 304 | font-size: 0.8em; 305 | } 306 | ul#searchresults span.teaser em { 307 | font-weight: bold; 308 | font-style: normal; 309 | } 310 | 311 | /* Sidebar */ 312 | 313 | .sidebar { 314 | position: fixed; 315 | left: 0; 316 | top: 0; 317 | bottom: 0; 318 | width: var(--sidebar-width); 319 | font-size: 0.875em; 320 | box-sizing: border-box; 321 | -webkit-overflow-scrolling: touch; 322 | overscroll-behavior-y: contain; 323 | background-color: var(--sidebar-bg); 324 | color: var(--sidebar-fg); 325 | } 326 | .sidebar-resizing { 327 | -moz-user-select: none; 328 | -webkit-user-select: none; 329 | -ms-user-select: none; 330 | user-select: none; 331 | } 332 | .js:not(.sidebar-resizing) .sidebar { 333 | transition: transform 0.3s; /* Animation: slide away */ 334 | } 335 | .sidebar code { 336 | line-height: 2em; 337 | } 338 | .sidebar .sidebar-scrollbox { 339 | overflow-y: auto; 340 | position: absolute; 341 | top: 0; 342 | bottom: 0; 343 | left: 0; 344 | right: 0; 345 | padding: 10px 10px; 346 | } 347 | .sidebar .sidebar-resize-handle { 348 | position: absolute; 349 | cursor: col-resize; 350 | width: 0; 351 | right: 0; 352 | top: 0; 353 | bottom: 0; 354 | } 355 | .js .sidebar .sidebar-resize-handle { 356 | cursor: col-resize; 357 | width: 5px; 358 | } 359 | .sidebar-hidden .sidebar { 360 | transform: translateX(calc(0px - var(--sidebar-width))); 361 | } 362 | .sidebar::-webkit-scrollbar { 363 | background: var(--sidebar-bg); 364 | } 365 | .sidebar::-webkit-scrollbar-thumb { 366 | background: var(--scrollbar); 367 | } 368 | 369 | .sidebar-visible .page-wrapper { 370 | transform: translateX(var(--sidebar-width)); 371 | } 372 | @media only screen and (min-width: 620px) { 373 | .sidebar-visible .page-wrapper { 374 | transform: none; 375 | margin-left: var(--sidebar-width); 376 | } 377 | } 378 | 379 | .chapter { 380 | list-style: none outside none; 381 | padding-left: 0; 382 | line-height: 2.2em; 383 | } 384 | 385 | .chapter ol { 386 | width: 100%; 387 | } 388 | 389 | .chapter li { 390 | display: flex; 391 | color: var(--sidebar-non-existant); 392 | } 393 | .chapter li a { 394 | display: block; 395 | padding: 0; 396 | text-decoration: none; 397 | color: var(--sidebar-fg); 398 | } 399 | 400 | .chapter li a:hover { 401 | color: var(--sidebar-active); 402 | } 403 | 404 | .chapter li a.active { 405 | color: var(--sidebar-active); 406 | } 407 | 408 | .chapter li > a.toggle { 409 | cursor: pointer; 410 | display: block; 411 | margin-left: auto; 412 | padding: 0 10px; 413 | user-select: none; 414 | opacity: 0.68; 415 | } 416 | 417 | .chapter li > a.toggle div { 418 | transition: transform 0.5s; 419 | } 420 | 421 | /* collapse the section */ 422 | .chapter li:not(.expanded) + li > ol { 423 | display: none; 424 | } 425 | 426 | .chapter li.chapter-item { 427 | line-height: 1.5em; 428 | margin-top: 0.6em; 429 | } 430 | 431 | .chapter li.expanded > a.toggle div { 432 | transform: rotate(90deg); 433 | } 434 | 435 | .spacer { 436 | width: 100%; 437 | height: 3px; 438 | margin: 5px 0px; 439 | } 440 | .chapter .spacer { 441 | background-color: var(--sidebar-spacer); 442 | } 443 | 444 | @media (-moz-touch-enabled: 1), (pointer: coarse) { 445 | .chapter li a { padding: 5px 0; } 446 | .spacer { margin: 10px 0; } 447 | } 448 | 449 | .section { 450 | list-style: none outside none; 451 | padding-left: 20px; 452 | line-height: 1.9em; 453 | } 454 | 455 | /* Theme Menu Popup */ 456 | 457 | .theme-popup { 458 | position: absolute; 459 | left: 10px; 460 | top: var(--menu-bar-height); 461 | z-index: 1000; 462 | border-radius: 4px; 463 | font-size: 0.7em; 464 | color: var(--fg); 465 | background: var(--theme-popup-bg); 466 | border: 1px solid var(--theme-popup-border); 467 | margin: 0; 468 | padding: 0; 469 | list-style: none; 470 | display: none; 471 | } 472 | .theme-popup .default { 473 | color: var(--icons); 474 | } 475 | .theme-popup .theme { 476 | width: 100%; 477 | border: 0; 478 | margin: 0; 479 | padding: 2px 10px; 480 | line-height: 25px; 481 | white-space: nowrap; 482 | text-align: left; 483 | cursor: pointer; 484 | color: inherit; 485 | background: inherit; 486 | font-size: inherit; 487 | } 488 | .theme-popup .theme:hover { 489 | background-color: var(--theme-hover); 490 | } 491 | .theme-popup .theme:hover:first-child, 492 | .theme-popup .theme:hover:last-child { 493 | border-top-left-radius: inherit; 494 | border-top-right-radius: inherit; 495 | } 496 | -------------------------------------------------------------------------------- /book/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | @import 'variables.css'; 4 | 5 | :root { 6 | /* Browser default font-size is 16px, this way 1 rem = 10px */ 7 | font-size: 62.5%; 8 | } 9 | 10 | html { 11 | font-family: "Open Sans", sans-serif; 12 | color: var(--fg); 13 | background-color: var(--bg); 14 | text-size-adjust: none; 15 | } 16 | 17 | body { 18 | margin: 0; 19 | font-size: 1.6rem; 20 | overflow-x: hidden; 21 | } 22 | 23 | code { 24 | font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; 25 | font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ 26 | } 27 | 28 | /* Don't change font size in headers. */ 29 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 30 | font-size: unset; 31 | } 32 | 33 | .left { float: left; } 34 | .right { float: right; } 35 | .boring { opacity: 0.6; } 36 | .hide-boring .boring { display: none; } 37 | .hidden { display: none; } 38 | 39 | h2, h3 { margin-top: 2.5em; } 40 | h4, h5 { margin-top: 2em; } 41 | 42 | .header + .header h3, 43 | .header + .header h4, 44 | .header + .header h5 { 45 | margin-top: 1em; 46 | } 47 | 48 | h1 a.header:target::before, 49 | h2 a.header:target::before, 50 | h3 a.header:target::before, 51 | h4 a.header:target::before { 52 | display: inline-block; 53 | content: "»"; 54 | margin-left: -30px; 55 | width: 30px; 56 | } 57 | 58 | h1 a.header:target, 59 | h2 a.header:target, 60 | h3 a.header:target, 61 | h4 a.header:target { 62 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); 63 | } 64 | 65 | .page { 66 | outline: 0; 67 | padding: 0 var(--page-padding); 68 | margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ 69 | } 70 | .page-wrapper { 71 | box-sizing: border-box; 72 | } 73 | .js:not(.sidebar-resizing) .page-wrapper { 74 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 75 | } 76 | 77 | .content { 78 | overflow-y: auto; 79 | padding: 0 15px; 80 | padding-bottom: 50px; 81 | } 82 | .content main { 83 | margin-left: auto; 84 | margin-right: auto; 85 | max-width: var(--content-max-width); 86 | } 87 | .content p { line-height: 1.45em; } 88 | .content ol { line-height: 1.45em; } 89 | .content ul { line-height: 1.45em; } 90 | .content a { text-decoration: none; } 91 | .content a:hover { text-decoration: underline; } 92 | .content img { max-width: 100%; } 93 | .content .header:link, 94 | .content .header:visited { 95 | color: var(--fg); 96 | } 97 | .content .header:link, 98 | .content .header:visited:hover { 99 | text-decoration: none; 100 | } 101 | 102 | table { 103 | margin: 0 auto; 104 | border-collapse: collapse; 105 | } 106 | table td { 107 | padding: 3px 20px; 108 | border: 1px var(--table-border-color) solid; 109 | } 110 | table thead { 111 | background: var(--table-header-bg); 112 | } 113 | table thead td { 114 | font-weight: 700; 115 | border: none; 116 | } 117 | table thead th { 118 | padding: 3px 20px; 119 | } 120 | table thead tr { 121 | border: 1px var(--table-header-bg) solid; 122 | } 123 | /* Alternate background colors for rows */ 124 | table tbody tr:nth-child(2n) { 125 | background: var(--table-alternate-bg); 126 | } 127 | 128 | 129 | blockquote { 130 | margin: 20px 0; 131 | padding: 0 20px; 132 | color: var(--fg); 133 | background-color: var(--quote-bg); 134 | border-top: .1em solid var(--quote-border); 135 | border-bottom: .1em solid var(--quote-border); 136 | } 137 | 138 | 139 | :not(.footnote-definition) + .footnote-definition, 140 | .footnote-definition + :not(.footnote-definition) { 141 | margin-top: 2em; 142 | } 143 | .footnote-definition { 144 | font-size: 0.9em; 145 | margin: 0.5em 0; 146 | } 147 | .footnote-definition p { 148 | display: inline; 149 | } 150 | 151 | .tooltiptext { 152 | position: absolute; 153 | visibility: hidden; 154 | color: #fff; 155 | background-color: #333; 156 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 157 | left: -8px; /* Half of the width of the icon */ 158 | top: -35px; 159 | font-size: 0.8em; 160 | text-align: center; 161 | border-radius: 6px; 162 | padding: 5px 8px; 163 | margin: 5px; 164 | z-index: 1000; 165 | } 166 | .tooltipped .tooltiptext { 167 | visibility: visible; 168 | } 169 | -------------------------------------------------------------------------------- /book/css/print.css: -------------------------------------------------------------------------------- 1 | 2 | #sidebar, 3 | #menu-bar, 4 | .nav-chapters, 5 | .mobile-nav-chapters { 6 | display: none; 7 | } 8 | 9 | #page-wrapper.page-wrapper { 10 | transform: none; 11 | margin-left: 0px; 12 | overflow-y: initial; 13 | } 14 | 15 | #content { 16 | max-width: none; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .page { 22 | overflow-y: initial; 23 | } 24 | 25 | code { 26 | background-color: #666666; 27 | border-radius: 5px; 28 | 29 | /* Force background to be printed in Chrome */ 30 | -webkit-print-color-adjust: exact; 31 | } 32 | 33 | pre > .buttons { 34 | z-index: 2; 35 | } 36 | 37 | a, a:visited, a:active, a:hover { 38 | color: #4183c4; 39 | text-decoration: none; 40 | } 41 | 42 | h1, h2, h3, h4, h5, h6 { 43 | page-break-inside: avoid; 44 | page-break-after: avoid; 45 | } 46 | 47 | pre, code { 48 | page-break-inside: avoid; 49 | white-space: pre-wrap; 50 | } 51 | 52 | .fa { 53 | display: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /book/css/variables.css: -------------------------------------------------------------------------------- 1 | 2 | /* Globals */ 3 | 4 | :root { 5 | --sidebar-width: 300px; 6 | --page-padding: 15px; 7 | --content-max-width: 750px; 8 | --menu-bar-height: 50px; 9 | } 10 | 11 | /* Themes */ 12 | 13 | .ayu { 14 | --bg: hsl(210, 25%, 8%); 15 | --fg: #c5c5c5; 16 | 17 | --sidebar-bg: #14191f; 18 | --sidebar-fg: #c8c9db; 19 | --sidebar-non-existant: #5c6773; 20 | --sidebar-active: #ffb454; 21 | --sidebar-spacer: #2d334f; 22 | 23 | --scrollbar: var(--sidebar-fg); 24 | 25 | --icons: #737480; 26 | --icons-hover: #b7b9cc; 27 | 28 | --links: #0096cf; 29 | 30 | --inline-code-color: #ffb454; 31 | 32 | --theme-popup-bg: #14191f; 33 | --theme-popup-border: #5c6773; 34 | --theme-hover: #191f26; 35 | 36 | --quote-bg: hsl(226, 15%, 17%); 37 | --quote-border: hsl(226, 15%, 22%); 38 | 39 | --table-border-color: hsl(210, 25%, 13%); 40 | --table-header-bg: hsl(210, 25%, 28%); 41 | --table-alternate-bg: hsl(210, 25%, 11%); 42 | 43 | --searchbar-border-color: #848484; 44 | --searchbar-bg: #424242; 45 | --searchbar-fg: #fff; 46 | --searchbar-shadow-color: #d4c89f; 47 | --searchresults-header-fg: #666; 48 | --searchresults-border-color: #888; 49 | --searchresults-li-bg: #252932; 50 | --search-mark-bg: #e3b171; 51 | } 52 | 53 | .coal { 54 | --bg: hsl(200, 7%, 8%); 55 | --fg: #98a3ad; 56 | 57 | --sidebar-bg: #292c2f; 58 | --sidebar-fg: #a1adb8; 59 | --sidebar-non-existant: #505254; 60 | --sidebar-active: #3473ad; 61 | --sidebar-spacer: #393939; 62 | 63 | --scrollbar: var(--sidebar-fg); 64 | 65 | --icons: #43484d; 66 | --icons-hover: #b3c0cc; 67 | 68 | --links: #2b79a2; 69 | 70 | --inline-code-color: #c5c8c6;; 71 | 72 | --theme-popup-bg: #141617; 73 | --theme-popup-border: #43484d; 74 | --theme-hover: #1f2124; 75 | 76 | --quote-bg: hsl(234, 21%, 18%); 77 | --quote-border: hsl(234, 21%, 23%); 78 | 79 | --table-border-color: hsl(200, 7%, 13%); 80 | --table-header-bg: hsl(200, 7%, 28%); 81 | --table-alternate-bg: hsl(200, 7%, 11%); 82 | 83 | --searchbar-border-color: #aaa; 84 | --searchbar-bg: #b7b7b7; 85 | --searchbar-fg: #000; 86 | --searchbar-shadow-color: #aaa; 87 | --searchresults-header-fg: #666; 88 | --searchresults-border-color: #98a3ad; 89 | --searchresults-li-bg: #2b2b2f; 90 | --search-mark-bg: #355c7d; 91 | } 92 | 93 | .light { 94 | --bg: hsl(0, 0%, 100%); 95 | --fg: #333333; 96 | 97 | --sidebar-bg: #fafafa; 98 | --sidebar-fg: #364149; 99 | --sidebar-non-existant: #aaaaaa; 100 | --sidebar-active: #008cff; 101 | --sidebar-spacer: #f4f4f4; 102 | 103 | --scrollbar: #cccccc; 104 | 105 | --icons: #cccccc; 106 | --icons-hover: #333333; 107 | 108 | --links: #4183c4; 109 | 110 | --inline-code-color: #6e6b5e; 111 | 112 | --theme-popup-bg: #fafafa; 113 | --theme-popup-border: #cccccc; 114 | --theme-hover: #e6e6e6; 115 | 116 | --quote-bg: hsl(197, 37%, 96%); 117 | --quote-border: hsl(197, 37%, 91%); 118 | 119 | --table-border-color: hsl(0, 0%, 95%); 120 | --table-header-bg: hsl(0, 0%, 80%); 121 | --table-alternate-bg: hsl(0, 0%, 97%); 122 | 123 | --searchbar-border-color: #aaa; 124 | --searchbar-bg: #fafafa; 125 | --searchbar-fg: #000; 126 | --searchbar-shadow-color: #aaa; 127 | --searchresults-header-fg: #666; 128 | --searchresults-border-color: #888; 129 | --searchresults-li-bg: #e4f2fe; 130 | --search-mark-bg: #a2cff5; 131 | } 132 | 133 | .navy { 134 | --bg: hsl(226, 23%, 11%); 135 | --fg: #bcbdd0; 136 | 137 | --sidebar-bg: #282d3f; 138 | --sidebar-fg: #c8c9db; 139 | --sidebar-non-existant: #505274; 140 | --sidebar-active: #2b79a2; 141 | --sidebar-spacer: #2d334f; 142 | 143 | --scrollbar: var(--sidebar-fg); 144 | 145 | --icons: #737480; 146 | --icons-hover: #b7b9cc; 147 | 148 | --links: #2b79a2; 149 | 150 | --inline-code-color: #c5c8c6;; 151 | 152 | --theme-popup-bg: #161923; 153 | --theme-popup-border: #737480; 154 | --theme-hover: #282e40; 155 | 156 | --quote-bg: hsl(226, 15%, 17%); 157 | --quote-border: hsl(226, 15%, 22%); 158 | 159 | --table-border-color: hsl(226, 23%, 16%); 160 | --table-header-bg: hsl(226, 23%, 31%); 161 | --table-alternate-bg: hsl(226, 23%, 14%); 162 | 163 | --searchbar-border-color: #aaa; 164 | --searchbar-bg: #aeaec6; 165 | --searchbar-fg: #000; 166 | --searchbar-shadow-color: #aaa; 167 | --searchresults-header-fg: #5f5f71; 168 | --searchresults-border-color: #5c5c68; 169 | --searchresults-li-bg: #242430; 170 | --search-mark-bg: #a2cff5; 171 | } 172 | 173 | .rust { 174 | --bg: hsl(60, 9%, 87%); 175 | --fg: #262625; 176 | 177 | --sidebar-bg: #3b2e2a; 178 | --sidebar-fg: #c8c9db; 179 | --sidebar-non-existant: #505254; 180 | --sidebar-active: #e69f67; 181 | --sidebar-spacer: #45373a; 182 | 183 | --scrollbar: var(--sidebar-fg); 184 | 185 | --icons: #737480; 186 | --icons-hover: #262625; 187 | 188 | --links: #2b79a2; 189 | 190 | --inline-code-color: #6e6b5e; 191 | 192 | --theme-popup-bg: #e1e1db; 193 | --theme-popup-border: #b38f6b; 194 | --theme-hover: #99908a; 195 | 196 | --quote-bg: hsl(60, 5%, 75%); 197 | --quote-border: hsl(60, 5%, 70%); 198 | 199 | --table-border-color: hsl(60, 9%, 82%); 200 | --table-header-bg: #b3a497; 201 | --table-alternate-bg: hsl(60, 9%, 84%); 202 | 203 | --searchbar-border-color: #aaa; 204 | --searchbar-bg: #fafafa; 205 | --searchbar-fg: #000; 206 | --searchbar-shadow-color: #aaa; 207 | --searchresults-header-fg: #666; 208 | --searchresults-border-color: #888; 209 | --searchresults-li-bg: #dec2a2; 210 | --search-mark-bg: #e69f67; 211 | } 212 | 213 | @media (prefers-color-scheme: dark) { 214 | .light.no-js { 215 | --bg: hsl(200, 7%, 8%); 216 | --fg: #98a3ad; 217 | 218 | --sidebar-bg: #292c2f; 219 | --sidebar-fg: #a1adb8; 220 | --sidebar-non-existant: #505254; 221 | --sidebar-active: #3473ad; 222 | --sidebar-spacer: #393939; 223 | 224 | --scrollbar: var(--sidebar-fg); 225 | 226 | --icons: #43484d; 227 | --icons-hover: #b3c0cc; 228 | 229 | --links: #2b79a2; 230 | 231 | --inline-code-color: #c5c8c6;; 232 | 233 | --theme-popup-bg: #141617; 234 | --theme-popup-border: #43484d; 235 | --theme-hover: #1f2124; 236 | 237 | --quote-bg: hsl(234, 21%, 18%); 238 | --quote-border: hsl(234, 21%, 23%); 239 | 240 | --table-border-color: hsl(200, 7%, 13%); 241 | --table-header-bg: hsl(200, 7%, 28%); 242 | --table-alternate-bg: hsl(200, 7%, 11%); 243 | 244 | --searchbar-border-color: #aaa; 245 | --searchbar-bg: #b7b7b7; 246 | --searchbar-fg: #000; 247 | --searchbar-shadow-color: #aaa; 248 | --searchresults-header-fg: #666; 249 | --searchresults-border-color: #98a3ad; 250 | --searchresults-li-bg: #2b2b2f; 251 | --search-mark-bg: #355c7d; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /book/editor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | window.editors = []; 3 | (function(editors) { 4 | if (typeof(ace) === 'undefined' || !ace) { 5 | return; 6 | } 7 | 8 | Array.from(document.querySelectorAll('.editable')).forEach(function(editable) { 9 | let display_line_numbers = window.playpen_line_numbers || false; 10 | 11 | let editor = ace.edit(editable); 12 | editor.setOptions({ 13 | highlightActiveLine: false, 14 | showPrintMargin: false, 15 | showLineNumbers: display_line_numbers, 16 | showGutter: display_line_numbers, 17 | maxLines: Infinity, 18 | fontSize: "0.875em" // please adjust the font size of the code in general.css 19 | }); 20 | 21 | editor.$blockScrolling = Infinity; 22 | 23 | editor.getSession().setMode("ace/mode/rust"); 24 | 25 | editor.originalCode = editor.getValue(); 26 | 27 | editors.push(editor); 28 | }); 29 | })(window.editors); 30 | -------------------------------------------------------------------------------- /book/elasticlunr.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * elasticlunr - http://weixsong.github.io 3 | * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 4 | * 5 | * Copyright (C) 2017 Oliver Nightingale 6 | * Copyright (C) 2017 Wei Song 7 | * MIT Licensed 8 | * @license 9 | */ 10 | !function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o 2 | 3 | 4 | 5 | 6 | 引言 - Futures Explained in 200 Lines of Rust 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 41 | 42 | 43 | 57 | 58 | 59 | 69 | 70 | 71 | 81 | 82 | 88 | 89 |
90 | 91 |
92 | 93 | 94 | 129 | 130 | 131 | 141 | 142 | 143 | 144 | 151 | 152 |
153 |
154 |

引言

155 |

本书github地址 欢迎star,以及提出任何问题

156 |

原书地址

157 |

这本书的目的是用一个例子驱动的方法来解释Rust中的Futures,探索为什么他们被设计成这样,以及他们如何工作。 我们还将介绍在编程中处理并发性时的一些替代方案。

158 |

理解这本书中描述的细节, 不需要使用Rust中的 futures或async/await。 这是为那些好奇的人准备的,他们想知道这一切是如何运作的。

159 |

这本书涵盖的内容

160 |

这本书将试图解释你可能想知道的一切,包括不同类型的执行器(executor)和运行时(runtime)的主题。 我们将在本书中实现一个非常简单的运行时,介绍一些概念,但这已经足够开始了。

161 |

Stjepan Glavina 发表了一系列关于异步运行时和执行器的优秀文章,如果谣言属实,那么在不久的将来他会发表更多的文章。

162 |

你应该做的是先读这本书,然后继续阅读 stejpang 的文章,了解更多关于运行时以及它们是如何工作的,特别是:

163 |
    164 |
  1. 构建自的block_on
  2. 165 |
  3. 构建自己的executor
  4. 166 |
167 |

我将自己限制在一个200行的主示例(因此才有了这个标题)中,以限制范围并介绍一个可以轻松进一步研究的示例。

168 |

然而,有很多东西需要消化,这并不像我所说的那么简单,但是我们会一步一步来,所以喝杯茶,放松一下。

169 |
170 |

这本书是在开放的,并欢迎贡献。 你可以在这里找到这本书。 最后的例子,你可以克隆或复制可以在这里找到。 任何建议或改进可以归档为一个公关或在问题追踪的书。 171 | 一如既往,我们欢迎各种各样的反馈。

172 |
173 |

阅读练习和进一步阅读

174 |

最后一章)中,我冒昧地提出了一些小练习,如果你想进一步探索的话。

175 |

这本书也是我在 Rust 中写的关于并发编程的第四本书。 如果你喜欢它,你可能也想看看其他的:

176 | 181 |

感谢

182 |

我想借此机会感谢 mio,tokio,async std,Futures,libc,crossbeam 背后的人们,他们支撑着这个异步生态系统,却很少在我眼中得到足够的赞扬。

183 |

特别感谢 jonhoo,他对我这本书的初稿给予了一些有价值的反馈。 他还没有读完最终的成品,但是我们应该向他表示感谢。

184 | 185 |
186 | 187 | 199 |
200 |
201 | 202 | 211 | 212 |
213 | 214 | 215 | 216 | 217 | 218 | 233 | 234 | 235 | 236 | 239 | 240 | 241 | 242 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /book/introduction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 引言 - Futures Explained in 200 Lines of Rust 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 41 | 42 | 43 | 57 | 58 | 59 | 69 | 70 | 71 | 81 | 82 | 88 | 89 |
90 | 91 |
92 | 93 | 94 | 129 | 130 | 131 | 141 | 142 | 143 | 144 | 151 | 152 |
153 |
154 |

引言

155 |

本书github地址 欢迎star,以及提出任何问题

156 |

原书地址

157 |

这本书的目的是用一个例子驱动的方法来解释Rust中的Futures,探索为什么他们被设计成这样,以及他们如何工作。 我们还将介绍在编程中处理并发性时的一些替代方案。

158 |

理解这本书中描述的细节, 不需要使用Rust中的 futures或async/await。 这是为那些好奇的人准备的,他们想知道这一切是如何运作的。

159 |

这本书涵盖的内容

160 |

这本书将试图解释你可能想知道的一切,包括不同类型的执行器(executor)和运行时(runtime)的主题。 我们将在本书中实现一个非常简单的运行时,介绍一些概念,但这已经足够开始了。

161 |

Stjepan Glavina 发表了一系列关于异步运行时和执行器的优秀文章,如果谣言属实,那么在不久的将来他会发表更多的文章。

162 |

你应该做的是先读这本书,然后继续阅读 stejpang 的文章,了解更多关于运行时以及它们是如何工作的,特别是:

163 |
    164 |
  1. 构建自的block_on
  2. 165 |
  3. 构建自己的executor
  4. 166 |
167 |

我将自己限制在一个200行的主示例(因此才有了这个标题)中,以限制范围并介绍一个可以轻松进一步研究的示例。

168 |

然而,有很多东西需要消化,这并不像我所说的那么简单,但是我们会一步一步来,所以喝杯茶,放松一下。

169 |
170 |

这本书是在开放的,并欢迎贡献。 你可以在这里找到这本书。 最后的例子,你可以克隆或复制可以在这里找到。 任何建议或改进可以归档为一个公关或在问题追踪的书。 171 | 一如既往,我们欢迎各种各样的反馈。

172 |
173 |

阅读练习和进一步阅读

174 |

最后一章)中,我冒昧地提出了一些小练习,如果你想进一步探索的话。

175 |

这本书也是我在 Rust 中写的关于并发编程的第四本书。 如果你喜欢它,你可能也想看看其他的:

176 | 181 |

感谢

182 |

我想借此机会感谢 mio,tokio,async std,Futures,libc,crossbeam 背后的人们,他们支撑着这个异步生态系统,却很少在我眼中得到足够的赞扬。

183 |

特别感谢 jonhoo,他对我这本书的初稿给予了一些有价值的反馈。 他还没有读完最终的成品,但是我们应该向他表示感谢。

184 | 185 |
186 | 187 | 199 |
200 |
201 | 202 | 211 | 212 |
213 | 214 | 215 | 216 | 217 | 218 | 233 | 234 | 235 | 236 | 239 | 240 | 241 | 242 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /book/mark.min.js: -------------------------------------------------------------------------------- 1 | /*!*************************************************** 2 | * mark.js v8.11.1 3 | * https://markjs.io/ 4 | * Copyright (c) 2014–2018, Julian Kühnel 5 | * Released under the MIT license https://git.io/vwTVl 6 | *****************************************************/ 7 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(function(t){var n=e.filter(function(e){return e.contains(t)}).length>0;-1!==e.indexOf(t)||n||e.push(t)}),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,a=function a(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",a),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",a),o=setTimeout(a,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,function(){return!0},function(e){r++,n.waitForIframes(e.querySelector("html"),function(){--r||t()})},function(e){e||t()})}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},a=t.querySelectorAll("iframe"),s=a.length,c=0;a=Array.prototype.slice.call(a);var u=function(){--s<=0&&o(c)};s||u(),a.forEach(function(t){e.matches(t,i.exclude)?u():i.onIframeReady(t,function(e){n(t)&&(c++,r(e)),u()},u)})}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:null===t?e.nextNode():e.nextNode()&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach(function(e,t){e.val===n&&(i=t,o=e.handled)}),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach(function(e){e.handled||i.getIframeContents(e.val,function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)})})}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o,a=this,s=this.createIterator(t,e,r),c=[],u=[],l=void 0,h=void 0;void 0,o=a.getIteratorNode(s),h=o.prevNode,l=o.node;)this.iframes&&this.forEachIframe(t,function(e){return a.checkIframeFilter(l,h,e,c)},function(t){a.createInstanceOnIframe(t).forEachNode(e,function(e){return u.push(e)},r)}),u.push(l);u.forEach(function(e){n(e)}),this.iframes&&this.handleOpenIframes(c,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),a=o.length;a||i(),o.forEach(function(o){var s=function(){r.iterateThroughNodes(e,o,t,n,function(){--a<=0&&i()})};r.iframes?r.waitForIframes(o,s):s()})}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every(function(t){return!r.call(e,t)||(i=!0,!1)}),i}return!1}}]),e}(),o=function(){function e(n){t(this,e),this.opt=r({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},n)}return n(e,[{key:"create",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),new RegExp(e,"gm"+(this.opt.caseSensitive?"":"i"))}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==a&&""!==s&&(e=e.replace(new RegExp("("+this.escapeStr(a)+"|"+this.escapeStr(s)+")","gm"+n),r+"("+this.processSynonyms(a)+"|"+this.processSynonyms(s)+")"+r))}return e}},{key:"processSynonyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,function(e){return"\\"===e.charAt(0)?"?":""})).replace(/(?:\\)*\*/g,function(e){return"\\"===e.charAt(0)?"*":""})}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"})}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach(function(i){n.every(function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0})}),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="";switch(("string"==typeof n?[]:n.limiters).forEach(function(e){i+="|"+t.escapeStr(e)}),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}}]),e}(),a=function(){function a(e){t(this,a),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(a,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(" ").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,a=i.end;i.valid&&(e.start=o,e.length=a-o,n.push(e),r=a)}),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,a=t-o,s=parseInt(e.start,10)-a;return(r=(s=s>o?o:s)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),s<0||r-s<0||s>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(s,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),a=document.createElement(r);return a.setAttribute("data-markjs","true"),this.opt.className&&a.setAttribute("class",this.opt.className),a.textContent=i.textContent,i.parentNode.replaceChild(a,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=o.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,i(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:"wrapGroups",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:"separateGroups",value:function(e,t,n,r,i){for(var o=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,i))}return e}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[a];){if(o.opt.separateGroups)t=o.separateGroups(t,i,a,n,r);else{if(!n(i[a],t))continue;var s=i.index;if(0!==a)for(var c=1;c=n[1]?(e.length>n[1]&&(r="invalid"),n.shift(),n.shift(),this.next=n.shift()):this.next="",r},regex:/"#*/,next:"start"},{defaultToken:"string.quoted.raw.source.rust"}]},{token:"string.quoted.double.source.rust",regex:'"',push:[{token:"string.quoted.double.source.rust",regex:'"',next:"pop"},{token:"constant.character.escape.source.rust",regex:s},{defaultToken:"string.quoted.double.source.rust"}]},{token:["keyword.source.rust","text","entity.name.function.source.rust"],regex:"\\b(fn)(\\s+)((?:r#)?[a-zA-Z_][a-zA-Z0-9_]*)"},{token:"support.constant",regex:"\\b[a-zA-Z_][\\w\\d]*::"},{token:"keyword.source.rust",regex:"\\b(?:abstract|alignof|as|become|box|break|catch|continue|const|crate|default|do|dyn|else|enum|extern|for|final|if|impl|in|let|loop|macro|match|mod|move|mut|offsetof|override|priv|proc|pub|pure|ref|return|self|sizeof|static|struct|super|trait|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\\b"},{token:"storage.type.source.rust",regex:"\\b(?:Self|isize|usize|char|bool|u8|u16|u32|u64|u128|f16|f32|f64|i8|i16|i32|i64|i128|str|option|either|c_float|c_double|c_void|FILE|fpos_t|DIR|dirent|c_char|c_schar|c_uchar|c_short|c_ushort|c_int|c_uint|c_long|c_ulong|size_t|ptrdiff_t|clock_t|time_t|c_longlong|c_ulonglong|intptr_t|uintptr_t|off_t|dev_t|ino_t|pid_t|mode_t|ssize_t)\\b"},{token:"variable.language.source.rust",regex:"\\bself\\b"},{token:"comment.line.doc.source.rust",regex:"//!.*$"},{token:"comment.line.double-dash.source.rust",regex:"//.*$"},{token:"comment.start.block.source.rust",regex:"/\\*",stateName:"comment",push:[{token:"comment.start.block.source.rust",regex:"/\\*",push:"comment"},{token:"comment.end.block.source.rust",regex:"\\*/",next:"pop"},{defaultToken:"comment.block.source.rust"}]},{token:"keyword.operator",regex:/\$|[-=]>|[-+%^=!&|<>]=?|[*/](?![*/])=?/},{token:"punctuation.operator",regex:/[?:,;.]/},{token:"paren.lparen",regex:/[\[({]/},{token:"paren.rparen",regex:/[\])}]/},{token:"constant.language.source.rust",regex:"\\b(?:true|false|Some|None|Ok|Err)\\b"},{token:"support.constant.source.rust",regex:"\\b(?:EXIT_FAILURE|EXIT_SUCCESS|RAND_MAX|EOF|SEEK_SET|SEEK_CUR|SEEK_END|_IOFBF|_IONBF|_IOLBF|BUFSIZ|FOPEN_MAX|FILENAME_MAX|L_tmpnam|TMP_MAX|O_RDONLY|O_WRONLY|O_RDWR|O_APPEND|O_CREAT|O_EXCL|O_TRUNC|S_IFIFO|S_IFCHR|S_IFBLK|S_IFDIR|S_IFREG|S_IFMT|S_IEXEC|S_IWRITE|S_IREAD|S_IRWXU|S_IXUSR|S_IWUSR|S_IRUSR|F_OK|R_OK|W_OK|X_OK|STDIN_FILENO|STDOUT_FILENO|STDERR_FILENO)\\b"},{token:"meta.preprocessor.source.rust",regex:"\\b\\w\\(\\w\\)*!|#\\[[\\w=\\(\\)_]+\\]\\b"},{token:"constant.numeric.source.rust",regex:/\b(?:0x[a-fA-F0-9_]+|0o[0-7_]+|0b[01_]+|[0-9][0-9_]*(?!\.))(?:[iu](?:size|8|16|32|64|128))?\b/},{token:"constant.numeric.source.rust",regex:/\b(?:[0-9][0-9_]*)(?:\.[0-9][0-9_]*)?(?:[Ee][+-][0-9][0-9_]*)?(?:f32|f64)?\b/}]},this.normalizeRules()};o.metaData={fileTypes:["rs","rc"],foldingStartMarker:"^.*\\bfn\\s*(\\w+\\s*)?\\([^\\)]*\\)(\\s*\\{[^\\}]*)?\\s*$",foldingStopMarker:"^\\s*\\}",name:"Rust",scopeName:"source.rust"},r.inherits(o,i),t.RustHighlightRules=o}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),ace.define("ace/mode/rust",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/rust_highlight_rules","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./rust_highlight_rules").RustHighlightRules,o=e("./folding/cstyle").FoldMode,u=function(){this.HighlightRules=s,this.foldingRules=new o,this.$behaviour=this.$defaultBehaviour};r.inherits(u,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/",nestable:!0},this.$quotes={'"':'"'},this.$id="ace/mode/rust"}.call(u.prototype),t.Mode=u}); (function() { 2 | ace.require(["ace/mode/rust"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /book/theme-dawn.js: -------------------------------------------------------------------------------- 1 | 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() { 2 | ace.require(["ace/theme/dawn"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /book/theme-tomorrow_night.js: -------------------------------------------------------------------------------- 1 | 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() { 2 | ace.require(["ace/theme/tomorrow_night"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /book/tomorrow-night.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Night Theme */ 2 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 3 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 4 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 5 | 6 | /* Tomorrow Comment */ 7 | .hljs-comment { 8 | color: #969896; 9 | } 10 | 11 | /* Tomorrow Red */ 12 | .hljs-variable, 13 | .hljs-attribute, 14 | .hljs-tag, 15 | .hljs-regexp, 16 | .ruby .hljs-constant, 17 | .xml .hljs-tag .hljs-title, 18 | .xml .hljs-pi, 19 | .xml .hljs-doctype, 20 | .html .hljs-doctype, 21 | .css .hljs-id, 22 | .css .hljs-class, 23 | .css .hljs-pseudo { 24 | color: #cc6666; 25 | } 26 | 27 | /* Tomorrow Orange */ 28 | .hljs-number, 29 | .hljs-preprocessor, 30 | .hljs-pragma, 31 | .hljs-built_in, 32 | .hljs-literal, 33 | .hljs-params, 34 | .hljs-constant { 35 | color: #de935f; 36 | } 37 | 38 | /* Tomorrow Yellow */ 39 | .ruby .hljs-class .hljs-title, 40 | .css .hljs-rule .hljs-attribute { 41 | color: #f0c674; 42 | } 43 | 44 | /* Tomorrow Green */ 45 | .hljs-string, 46 | .hljs-value, 47 | .hljs-inheritance, 48 | .hljs-header, 49 | .hljs-name, 50 | .ruby .hljs-symbol, 51 | .xml .hljs-cdata { 52 | color: #b5bd68; 53 | } 54 | 55 | /* Tomorrow Aqua */ 56 | .hljs-title, 57 | .css .hljs-hexcolor { 58 | color: #8abeb7; 59 | } 60 | 61 | /* Tomorrow Blue */ 62 | .hljs-function, 63 | .python .hljs-decorator, 64 | .python .hljs-title, 65 | .ruby .hljs-function .hljs-title, 66 | .ruby .hljs-title .hljs-keyword, 67 | .perl .hljs-sub, 68 | .javascript .hljs-title, 69 | .coffeescript .hljs-title { 70 | color: #81a2be; 71 | } 72 | 73 | /* Tomorrow Purple */ 74 | .hljs-keyword, 75 | .javascript .hljs-function { 76 | color: #b294bb; 77 | } 78 | 79 | .hljs { 80 | display: block; 81 | overflow-x: auto; 82 | background: #1d1f21; 83 | color: #c5c8c6; 84 | padding: 0.5em; 85 | -webkit-text-size-adjust: none; 86 | } 87 | 88 | .coffeescript .javascript, 89 | .javascript .xml, 90 | .tex .hljs-formula, 91 | .xml .javascript, 92 | .xml .vbscript, 93 | .xml .css, 94 | .xml .hljs-cdata { 95 | opacity: 0.5; 96 | } 97 | 98 | .hljs-addition { 99 | color: #718c00; 100 | } 101 | 102 | .hljs-deletion { 103 | color: #c82829; 104 | } 105 | -------------------------------------------------------------------------------- /examples/bonus_example/cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "futures_example" 3 | version = "0.1.0" 4 | authors = ["Carl Fredrik Samson "] 5 | edition = "2018" 6 | 7 | -------------------------------------------------------------------------------- /examples/bonus_example/src/main.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/examples/bonus_example/src/main.rs -------------------------------------------------------------------------------- /scrapped_chapters/5_reactor_executor.md: -------------------------------------------------------------------------------- 1 | # Reactor/Executor Pattern 2 | 3 | > **Relevant for:** 4 | > 5 | > - Getting a high level overview of a common runtime model in Rust 6 | > - Introducing these terms so we're on the same page when referring to them 7 | > - Getting pointers on where to get more information about this pattern 8 | 9 | If you don't know what this is, you should take a few minutes and read about 10 | it. You will encounter the term `Reactor` and `Executor` a lot when working 11 | with async code in Rust. 12 | 13 | I have written a quick introduction explaining this pattern before which you 14 | can take a look at here: 15 | 16 | 17 | [![homepage][1]][2] 18 | 19 | 22 | 23 | I'll re-iterate the most important parts here. 24 | 25 | **This pattern consists of at least 2 parts:** 26 | 27 | 1. **A reactor** 28 | - handles some kind of event queue 29 | - has the responsibility of respoonding to events 30 | 2. **An executor** 31 | - Often has a scheduler 32 | - Holds a set of suspended tasks, and has the responsibility of resuming 33 | them when an event has occurred 34 | 3. **The concept of a task** 35 | - A set of operations that can be stopped half way and resumed later on 36 | 37 | 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. 38 | 39 | ## The Reactor 40 | 41 | Since concurrency mostly makes sense when interacting with the outside world (or 42 | at least some peripheral), we need something to actually abstract over this 43 | interaction in an asynchronous way. 44 | 45 | This is the `Reactors` job. Most often you'll 46 | see reactors in rust use a library called [Mio][mio], which provides non 47 | blocking APIs and event notification for several platforms. 48 | 49 | 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 50 | is a `Future`. 51 | 52 | We can call this kind of `Future` a "leaf Future`, since it's some operation 53 | we'll actually wait on and that we can chain operations on which are performed 54 | once the leaf future is ready. 55 | 56 | ## The Task 57 | 58 | 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 59 | these `Futures` so that once a "leaf future" is ready we'll perform a set of 60 | operations. 61 | 62 | These operations can spawn new leaf futures themselves. 63 | 64 | ## The executor 65 | 66 | The executors task is to take one or more futures and run them to completion. 67 | 68 | The first thing an `executor` does when it get's a `Future` is polling it. 69 | 70 | **When polled one of three things can happen:** 71 | 72 | - The future returns `Ready` and we schedule whatever chained operations to run 73 | - The future hasn't been polled before so we pass it a `Waker` and suspend it 74 | - The futures has been polled before but is not ready and returns `Pending` 75 | 76 | Rust provides a way for the Reactor and Executor to communicate through the `Waker`. The reactor stores this `Waker` and calls `Waker::wake()` on it once 77 | a `Future` has resolved and should be polled again. 78 | 79 | We'll get to know these concepts better in the following chapters. 80 | 81 | Providing these pieces let's Rust take care a lot of the ergonomic "friction" 82 | programmers meet when faced with async code, and still not dictate any 83 | preferred runtime to actually do the scheduling and I/O queues. 84 | 85 | 86 | With that out of the way, let's move on to actually implement all this in our 87 | example. 88 | 89 | [1]: ./assets/reactorexecutor.png 90 | [2]: https://cfsamsonbooks.gitbook.io/epoll-kqueue-iocp-explained/appendix-1/reactor-executor-pattern 91 | [mio]: https://github.com/tokio-rs/mio -------------------------------------------------------------------------------- /scrapped_chapters/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | In this short book we'll explore Rust's `Futures`and implement a self contained example including a fake `Reactor`, a simple `Executor`and our own `Futures`. This books aims to give an introduction to `Futures` and the basics of how you can implement your own. 4 | 5 | ### Futures in Rust 6 | 7 | In contrast to many other languages, Rust doesn't come with a large runtime. The reason for this is easy to understand when you look at some of Rusts goals: 8 | 9 | * Provide zero cost abstractions \(a runtime is never zero cost\) 10 | * Usable in embedded scenarios 11 | 12 | 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. 13 | 14 | `Futures` in Rust comes in several versions, and that can be a source of some confusion for new users. 15 | 16 | #### Futures 0.1 17 | 18 | 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. 19 | 20 | An example of this could look like: 21 | 22 | ```rust 23 | let future = SomeAction::new(); 24 | let fut_value = future.and_then(|object| { 25 | object.new_action().and_then(|secondaction| { 26 | Ok(secondaction.get_data()) 27 | }) 28 | }; 29 | 30 | let value = executor.block_on(fut_value).unwrap(); 31 | 32 | println!("{}", value); 33 | ``` 34 | 35 | As you can see, these chains quickly become long and hard to work with. Callback-hell comes to mind. Though you could try un-nest them in a way similar to using `Promises` in JavaScript, the type signatures you have to work with quickly become so long and unwieldy \(think multiple lines of code just for the signature\) that the ergonomics discouraged it. The error messages could fill a whole screen. 36 | 37 | There were other issues as well, but the lack of ergonomics was one of the major ones. 38 | 39 | #### Futures 0.2 40 | 41 | #### Futures 0.3 42 | 43 | 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. 44 | 45 | The `async/await` syntax was designed in tandem with `Futures 3.0` and provides a much more ergonomic way to work with `Futures`: 46 | 47 | ```rust 48 | async fn asyncfunc() -> i32 { 49 | let object = SomeAction::new().await.unwrap(); 50 | let secondaction = object.new_action().await; 51 | secondaction.get_data().await 52 | } 53 | 54 | let value = executor.block_on(asyncfunc()).unwrap(); 55 | println!("{}", value); 56 | ``` 57 | 58 | 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. 59 | 60 | #### How \`Futures\` are implemented in the language 61 | 62 | 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. 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/0_background_information.md: -------------------------------------------------------------------------------- 1 | ## 背景资料 2 | 3 | 在我们深入研究 Futures in Rust 的细节之前,让我们快速了解一下处理并发编程的各种方法,以及每种方法的优缺点。 4 | 5 | 同时当涉及到并发性时,我们也会解释为什么这么做,这将使我们更容易深入理解Futures. 6 | 7 | > 为了好玩,我在大多数示例中添加了一小段可运行代码。 如果你像我一样,事情会变得更有趣,也许你会看到一些你从未见过的东西。 8 | 9 | ### 线程 10 | 11 | 现在,实现这一点的一个方法就是让操作系统为我们处理好一切。 我们只需为每个要完成的任务生成一个新的操作系统线程,并像通常那样编写代码。 12 | 13 | 我们用来处理并发性的运行时就是操作系统本身。 14 | 15 | 优点: 16 | 17 | - 简单 18 | - 易用 19 | - 在任务之间切换相当快 20 | - 不需要付出即可得到并行支持 21 | 22 | 缺点: 23 | 24 | - 操作系统级线程的堆栈相当大。 如果同时有许多任务等待(就像在负载很重的 web 服务器中那样) ,那么内存将很快耗尽 25 | - 这涉及到很多系统调用。当任务数量很高时,这可能会非常昂贵 26 | - 操作系统有很多事情需要处理。 它可能不会像你希望的那样快速地切换回线程 27 | - 某些系统可能不支持线程 28 | 29 | **在 Rust 中使用操作系统线程看起来像这样:** 30 | ```rust 31 | use std::thread; 32 | 33 | fn main() { 34 | println!("So we start the program here!"); 35 | let t1 = thread::spawn(move || { 36 | thread::sleep(std::time::Duration::from_millis(200)); 37 | println!("We create tasks which gets run when they're finished!"); 38 | }); 39 | 40 | let t2 = thread::spawn(move || { 41 | thread::sleep(std::time::Duration::from_millis(100)); 42 | println!("We can even chain callbacks..."); 43 | let t3 = thread::spawn(move || { 44 | thread::sleep(std::time::Duration::from_millis(50)); 45 | println!("...like this!"); 46 | }); 47 | t3.join().unwrap(); 48 | }); 49 | println!("While our tasks are executing we can do other stuff here."); 50 | 51 | t1.join().unwrap(); 52 | t2.join().unwrap(); 53 | } 54 | ``` 55 | 56 | 操作系统线程肯定有一些相当大的优势。 这也是为什么所有这些讨论“异步”和并发性把线程摆在首位? 57 | 58 | 首先。 为了使计算机有效率,它们需要多任务处理。 一旦你开始深入研究(比如[操作系统是如何工作的](https://os.phil-opp.com/async-await/)) ,你就会发现并发无处不在。 这是我们做任何事情的基础。 59 | 60 | 61 | 其次,我们有网络。 62 | 63 | Webservers 是关于I/O和处理小任务(请求)的。 当小任务的数量很大时,由于它们所需的内存和创建新线程所涉及的开销,就不适合今天的操作系统线程。 64 | 65 | 如果负载是可变的,那么问题就更大了,这意味着程序在任何时间点的当前任务数量都是不可预测的。 这就是为什么今天你会看到如此多的异步web框架和数据库驱动程序。 66 | 67 | 然而有大量的问题,标准的线程通常是正确的解决方案。 因此在使用异步库之前,请三思而后行。 68 | 69 | 现在,让我们来看看多任务处理的其他选项。 它们都有一个共同点,那就是它们实现了一种多任务处理的方式,即拥有一个“用户界面”运行时: 70 | 71 | ### 绿色线程(Green threads) 72 | 73 | 绿色线程使用与操作系统相同的机制,为每个任务创建一个线程,设置一个堆栈,保存 CPU 状态,并通过“上下文切换”从一个任务(线程)跳转到另一个任务(线程)。 74 | 75 | 我们将控制权交给调度程序(在这样的系统中,调度程序是运行时的核心部分) ,然后调度程序继续运行不同的任务。 76 | 77 | Rust曾经支持绿色线程,但他们它达到1.0之前被删除了, 执行状态存储在每个栈中,因此在这样的解决方案中不需要`async`,`await`,`Futures` 或者`Pin`。 78 | 79 | 典型的流程是这样的: 80 | 1. 运行一些非阻塞代码 81 | 2. 对某些外部资源进行阻塞调用 82 | 3. 跳转到main”线程,该线程调度一个不同的线程来运行,并“跳转”到该栈中 83 | 4. 在新线程上运行一些非阻塞代码,直到新的阻塞调用或任务完成 84 | 5. “跳转”回到“main"线程 ,调度一个新线程,这个新线程的状态已经是`Ready`,然后跳转到该线程 85 | 86 | 这些“跳转”被称为上下文切换,当你阅读这篇文章的时候,你的操作系统每秒钟都会做很多次。 87 | 88 | 优点: 89 | 90 | 1. 栈大小可能需要增长,解决这个问题不容易,并且会有成本.[^go中的栈] 91 | [^go中的栈]: 栈拷贝,指针等问题 92 | 2. 它不是一个零成本抽象(这也是Rust早期有绿色线程,后来删除的原因之一) 93 | 3. 如果您想要支持许多不同的平台,就很难正确实现 94 | 95 | 一个绿色线程的例子可以是这样的: 96 | > 下面的例子是一个改编的例子,来自我之前写的一本[200行Rust说清绿色线程](https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/)的 gitbook。 如果你想知道发生了什么,你会发现书中详细地解释了一切。 下面的代码非常不安全,只是为了展示一个真实的例子。 这绝不是为了展示“最佳实践”。 这样我们就能达成共识了。 97 | 98 | ```rust 99 | #![feature(asm, naked_functions)] 100 | use std::ptr; 101 | 102 | const DEFAULT_STACK_SIZE: usize = 1024 * 1024 * 2; 103 | const MAX_THREADS: usize = 4; 104 | static mut RUNTIME: usize = 0; 105 | 106 | pub struct Runtime { 107 | threads: Vec, 108 | current: usize, 109 | } 110 | 111 | #[derive(PartialEq, Eq, Debug)] 112 | enum State { 113 | Available, 114 | Running, 115 | Ready, 116 | } 117 | 118 | struct Thread { 119 | id: usize, 120 | stack: Vec, 121 | ctx: ThreadContext, 122 | state: State, 123 | task: Option>, 124 | } 125 | 126 | #[derive(Debug, Default)] 127 | #[repr(C)] 128 | struct ThreadContext { 129 | rsp: u64, 130 | r15: u64, 131 | r14: u64, 132 | r13: u64, 133 | r12: u64, 134 | rbx: u64, 135 | rbp: u64, 136 | thread_ptr: u64, 137 | } 138 | 139 | impl Thread { 140 | fn new(id: usize) -> Self { 141 | Thread { 142 | id, 143 | stack: vec![0_u8; DEFAULT_STACK_SIZE], 144 | ctx: ThreadContext::default(), 145 | state: State::Available, 146 | task: None, 147 | } 148 | } 149 | } 150 | 151 | impl Runtime { 152 | pub fn new() -> Self { 153 | let base_thread = Thread { 154 | id: 0, 155 | stack: vec![0_u8; DEFAULT_STACK_SIZE], 156 | ctx: ThreadContext::default(), 157 | state: State::Running, 158 | task: None, 159 | }; 160 | 161 | let mut threads = vec![base_thread]; 162 | threads[0].ctx.thread_ptr = &threads[0] as *const Thread as u64; 163 | let mut available_threads: Vec = (1..MAX_THREADS).map(|i| Thread::new(i)).collect(); 164 | threads.append(&mut available_threads); 165 | 166 | Runtime { 167 | threads, 168 | current: 0, 169 | } 170 | } 171 | 172 | pub fn init(&self) { 173 | unsafe { 174 | let r_ptr: *const Runtime = self; 175 | RUNTIME = r_ptr as usize; 176 | } 177 | } 178 | 179 | pub fn run(&mut self) -> ! { 180 | while self.t_yield() {} 181 | std::process::exit(0); 182 | } 183 | 184 | fn t_return(&mut self) { 185 | if self.current != 0 { 186 | self.threads[self.current].state = State::Available; 187 | self.t_yield(); 188 | } 189 | } 190 | 191 | fn t_yield(&mut self) -> bool { 192 | let mut pos = self.current; 193 | while self.threads[pos].state != State::Ready { 194 | pos += 1; 195 | if pos == self.threads.len() { 196 | pos = 0; 197 | } 198 | if pos == self.current { 199 | return false; 200 | } 201 | } 202 | 203 | if self.threads[self.current].state != State::Available { 204 | self.threads[self.current].state = State::Ready; 205 | } 206 | 207 | self.threads[pos].state = State::Running; 208 | let old_pos = self.current; 209 | self.current = pos; 210 | 211 | unsafe { 212 | switch(&mut self.threads[old_pos].ctx, &self.threads[pos].ctx); 213 | } 214 | true 215 | } 216 | 217 | pub fn spawn(f: F){ 218 | unsafe { 219 | let rt_ptr = RUNTIME as *mut Runtime; 220 | let available = (*rt_ptr) 221 | .threads 222 | .iter_mut() 223 | .find(|t| t.state == State::Available) 224 | .expect("no available thread."); 225 | 226 | let size = available.stack.len(); 227 | let s_ptr = available.stack.as_mut_ptr(); 228 | available.task = Some(Box::new(f)); 229 | available.ctx.thread_ptr = available as *const Thread as u64; 230 | ptr::write(s_ptr.offset((size - 8) as isize) as *mut u64, guard as u64); 231 | ptr::write(s_ptr.offset((size - 16) as isize) as *mut u64, call as u64); 232 | available.ctx.rsp = s_ptr.offset((size - 16) as isize) as u64; 233 | available.state = State::Ready; 234 | } 235 | } 236 | } 237 | 238 | fn call(thread: u64) { 239 | let thread = unsafe { &*(thread as *const Thread) }; 240 | if let Some(f) = &thread.task { 241 | f(); 242 | } 243 | } 244 | 245 | #[naked] 246 | fn guard() { 247 | unsafe { 248 | let rt_ptr = RUNTIME as *mut Runtime; 249 | let rt = &mut *rt_ptr; 250 | println!("THREAD {} FINISHED.", rt.threads[rt.current].id); 251 | rt.t_return(); 252 | }; 253 | } 254 | 255 | pub fn yield_thread() { 256 | unsafe { 257 | let rt_ptr = RUNTIME as *mut Runtime; 258 | (*rt_ptr).t_yield(); 259 | }; 260 | } 261 | 262 | #[naked] 263 | #[inline(never)] 264 | unsafe fn switch(old: *mut ThreadContext, new: *const ThreadContext) { 265 | asm!(" 266 | mov %rsp, 0x00($0) 267 | mov %r15, 0x08($0) 268 | mov %r14, 0x10($0) 269 | mov %r13, 0x18($0) 270 | mov %r12, 0x20($0) 271 | mov %rbx, 0x28($0) 272 | mov %rbp, 0x30($0) 273 | 274 | mov 0x00($1), %rsp 275 | mov 0x08($1), %r15 276 | mov 0x10($1), %r14 277 | mov 0x18($1), %r13 278 | mov 0x20($1), %r12 279 | mov 0x28($1), %rbx 280 | mov 0x30($1), %rbp 281 | mov 0x38($1), %rdi 282 | ret 283 | " 284 | : 285 | : "r"(old), "r"(new) 286 | : 287 | : "alignstack" 288 | ); 289 | } 290 | #[cfg(not(windows))] 291 | fn main() { 292 | let mut runtime = Runtime::new(); 293 | runtime.init(); 294 | Runtime::spawn(|| { 295 | println!("I haven't implemented a timer in this example."); 296 | yield_thread(); 297 | println!("Finally, notice how the tasks are executed concurrently."); 298 | }); 299 | Runtime::spawn(|| { 300 | println!("But we can still nest tasks..."); 301 | Runtime::spawn(|| { 302 | println!("...like this!"); 303 | }) 304 | }); 305 | runtime.run(); 306 | } 307 | #[cfg(windows)] 308 | fn main() { 309 | let mut runtime = Runtime::new(); 310 | runtime.init(); 311 | Runtime::spawn(|| { 312 | println!("I haven't implemented a timer in this example."); 313 | yield_thread(); 314 | println!("Finally, notice how the tasks are executed concurrently."); 315 | }); 316 | Runtime::spawn(|| { 317 | println!("But we can still nest tasks..."); 318 | Runtime::spawn(|| { 319 | println!("...like this!"); 320 | }) 321 | }); 322 | runtime.run(); 323 | } 324 | 325 | ``` 326 | 327 | 328 | 还在坚持阅读本书? 很好。 如果上面的代码很难理解,不要感到沮丧。 如果不是我自己写的,我可能也会有同样的感觉。 你随时可以回去读,稍后我还会解释。 329 | 330 | ### 基于回调的方法 331 | 332 | 你可能已经知道我们接下来要谈论Javascript,我想大多数人都知道。 333 | 334 | > 如果你接触过 Javascript 回调会让你更早患上 PTSD,那么现在闭上眼睛,向下滚动2-3秒。 你会在那里找到一个链接,带你到安全的地方。 335 | 336 | 基于回调的方法背后的整个思想就是保存一个指向一组指令的指针,这些指令我们希望以后在以后需要的时候运行。 针对Rust,这将是一个闭包。 在下面的示例中,我们将此信息保存在`HashMap`中,但这并不是唯一的选项。 337 | 338 | 不涉及线程作为实现并发性的主要方法的基本思想是其余方法的共同点。 包括我们很快就会讲到的 Rust 今天使用的那个。 339 | 340 | 优点: 341 | 1. 大多数语言中易于实现 342 | 2. 没有上下文切换 343 | 3. 相对较低的内存开销(在大多数情况下) 344 | 345 | 缺点: 346 | 347 | 1. 每个任务都必须保存它以后需要的状态,内存使用量将随着一系列计算中回调的数量线性增长 348 | 2. 很难理解,很多人已经知道这就是“回调地狱” 349 | 3. 这是一种非常不同的编写程序的方式,需要大量的重写才能从“正常”的程序流转变为使用“基于回调”的程序流 350 | 4. 在 Rust 使用这种方法时,任务之间的状态共享是一个难题,因为它的所有权模型 351 | 352 | 一个极其简单的基于回调方法的例子是: 353 | 354 | ```rust 355 | fn program_main() { 356 | println!("So we start the program here!"); 357 | set_timeout(200, || { 358 | println!("We create tasks with a callback that runs once the task finished!"); 359 | }); 360 | set_timeout(100, || { 361 | println!("We can even chain sub-tasks..."); 362 | set_timeout(50, || { 363 | println!("...like this!"); 364 | }) 365 | }); 366 | println!("While our tasks are executing we can do other stuff instead of waiting."); 367 | } 368 | 369 | fn main() { 370 | RT.with(|rt| rt.run(program_main)); 371 | } 372 | 373 | use std::sync::mpsc::{channel, Receiver, Sender}; 374 | use std::{cell::RefCell, collections::HashMap, thread}; 375 | 376 | thread_local! { 377 | static RT: Runtime = Runtime::new(); 378 | } 379 | 380 | struct Runtime { 381 | callbacks: RefCell ()>>>, 382 | next_id: RefCell, 383 | evt_sender: Sender, 384 | evt_reciever: Receiver, 385 | } 386 | 387 | fn set_timeout(ms: u64, cb: impl FnOnce() + 'static) { 388 | RT.with(|rt| { 389 | let id = *rt.next_id.borrow(); 390 | *rt.next_id.borrow_mut() += 1; 391 | rt.callbacks.borrow_mut().insert(id, Box::new(cb)); 392 | let evt_sender = rt.evt_sender.clone(); 393 | thread::spawn(move || { 394 | thread::sleep(std::time::Duration::from_millis(ms)); 395 | evt_sender.send(id).unwrap(); 396 | }); 397 | }); 398 | } 399 | 400 | impl Runtime { 401 | fn new() -> Self { 402 | let (evt_sender, evt_reciever) = channel(); 403 | Runtime { 404 | callbacks: RefCell::new(HashMap::new()), 405 | next_id: RefCell::new(1), 406 | evt_sender, 407 | evt_reciever, 408 | } 409 | } 410 | 411 | fn run(&self, program: fn()) { 412 | program(); 413 | for evt_id in &self.evt_reciever { 414 | let cb = self.callbacks.borrow_mut().remove(&evt_id).unwrap(); 415 | cb(); 416 | if self.callbacks.borrow().is_empty() { 417 | break; 418 | } 419 | } 420 | } 421 | } 422 | ``` 423 | 424 | 我们保持这种超级简单的方法,您可能想知道这种方法和使用 OS 线程直接将回调传递给 OS 线程的方法之间有什么区别。 425 | 不同之处在于,回调是在同一个线程上运行的。 这个例子中,我们创建的 OS 线程基本上只是用作计时器,但可以表示任何类型的我们将不得不等待的资源。 426 | 427 | ### 从回调到承诺 (promises) 428 | 429 | 你现在可能会想,我们什么时候才能谈论未来? 430 | 431 | 好吧,我们就快到了。你看,`promises`、`futures`和其他延迟计算的名称经常被交替使用。 432 | 433 | 它们之间有形式上的区别,但是我们在这里不会涉及,但是值得解释一下`promises`,因为它们被广泛使用在 Javascript 中,并且与 Rusts Futures 有很多共同之处。 434 | 435 | 首先,许多语言都有`promises`的概念,但我将在下面的例子中使用来自 Javascript 的概念。 436 | 437 | 承诺是解决回调带来的复杂性的一种方法。 438 | 439 | 比如,下面的例子: 440 | ```js 441 | setTimer(200, () => { 442 | setTimer(100, () => { 443 | setTimer(50, () => { 444 | console.log("I'm the last one"); 445 | }); 446 | }); 447 | }); 448 | ``` 449 | 450 | 可以替换为promise: 451 | 452 | ```js 453 | function timer(ms) { 454 | return new Promise((resolve) => setTimeout(resolve, ms)) 455 | } 456 | 457 | timer(200) 458 | .then(() => return timer(100)) 459 | .then(() => return timer(50)) 460 | .then(() => console.log("I'm the last one)); 461 | 462 | ``` 463 | 464 | 深入原理可以看到变化更为显著。 您可以看到,promises 返回的状态机可以处于以下三种状态之一: `pending`、 `fulfilled` 或 `rejected`。 465 | 466 | 当我们在上面的例子中调用`timer (200)`时,我们得到一个状态`pending`的承诺。 467 | 468 | 由于承诺被重写为状态机,它们还提供了一种更好的语法,允许我们像下面这样编写最后一个示例: 469 | 470 | ```js 471 | async function run() { 472 | await timer(200); 473 | await timer(100); 474 | await timer(50); 475 | console.log("I'm the last one"); 476 | } 477 | ``` 478 | 479 | 可以将 run 函数视为一个由几个子任务组成的可执行任务。 在每个`await`点上,它都将控制权交给调度程序(在本例中是众所周知的 Javascript 事件循环)。 480 | 481 | 一旦其中一个子任务将状态更改为`fulfilled`或`rejected`,则计划继续执行下一步。 482 | 483 | 从语法上讲,Rusts Futures 0.1很像上面的承诺示例,Rusts Futures 0.3很像我们上一个示例中的 async / await。 484 | 485 | 这也是与 Rusts Futures 相似的地方。 我们这样做的原因是通过上面的介绍,更加深刻的理解Rust的Futures。 486 | 487 | > 为了避免以后的混淆: 有一点你应该知道。 Java script的承诺是立即执行(early evaluated)的。 这意味着一旦它被创建,它就开始运行一个任务。 与此相反,Rust的Futures是延迟执行(lazy evaluated)。 除非轮询(poll)一次,否则什么事都不会发生。 488 | 489 | 490 | -------------------------------------------------------------------------------- /src/1_futures_in_rust.md: -------------------------------------------------------------------------------- 1 | ## Rust中的Futures 2 | 3 | ### 概述 4 | 5 | 1. Rust中并发性的高级介绍 6 | 2. 了解 Rust 在使用异步代码时能提供什么,不能提供什么 7 | 3. 了解为什么我们需要 Rust 的运行时库 8 | 4. 理解“leaf-future”和“non-leaf-future”的区别 9 | 5. 了解如何处理 CPU 密集型任务 10 | 11 | 12 | ### Futures 13 | 14 | 什么是`Future`? 15 | `Future`是一些将在未来完成的操作。 16 | Rust中的异步实现基于轮询,每个异步任务分成三个阶段: 17 | 1. 轮询阶段(The Poll phase). 一个`Future`被轮询后,会开始执行,直到被阻塞. 我们经常把轮询一个Future这部分称之为执行器(executor) 18 | 2. 等待阶段. 事件源(通常称为reactor)注册等待一个事件发生,并确保当该事件准备好时唤醒相应的`Future` 19 | 3. 唤醒阶段. 事件发生,相应的`Future`被唤醒。 现在轮到执行器(executor),就是第一步中的那个执行器,调度`Future`再次被轮询,并向前走一步,直到它完成或达到一个阻塞点,不能再向前走, 如此往复,直到最终完成. 20 | 21 | 当我们谈论`Future`的时候,我发现在早期区分`non-leaf-future`和`leaf-future`是很有用的,因为实际上它们彼此很不一样。 22 | 23 | 24 | ### Leaf futures 25 | 26 | 由运行时创建`leaf futures`,它就像套接字一样,代表着一种资源. 27 | ```rust 28 | // stream is a **leaf-future** 29 | let mut stream = tokio::net::TcpStream::connect("127.0.0.1:3000"); 30 | ``` 31 | 对这些资源的操作,比如套接字上的 Read 操作,将是非阻塞的,并返回一个我们称之为`leaf-future`的Future.之所以称之为`leaf-future`,是因为这是我们实际上正在等待的Future. 32 | 33 | 除非你正在编写一个运行时,否则你不太可能自己实现一个`leaf-future`,但是我们将在本书中详细介绍它们是如何构造的。 34 | 35 | 您也不太可能将 `leaf-future` 传递给运行时,然后单独运行它直到完成,这一点您可以通过阅读下一段来理解。 36 | 37 | ### Non-leaf-futures 38 | 39 | Non-leaf-futures指的是那些我们用`async`关键字创建的Future. 40 | 41 | 异步程序的大部分是Non-leaf-futures,这是一种可暂停的计算。 这是一个重要的区别,因为这些`Future`代表一组操作。 通常,这样的任务由`await` 一系列`leaf-future`组成. 42 | 43 | ```rust 44 | // Non-leaf-future 45 | let non_leaf = async { 46 | let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap();// <- yield 47 | println!("connected!"); 48 | let result = stream.write(b"hello world\n").await; // <- yield 49 | println!("message sent!"); 50 | ... 51 | }; 52 | ``` 53 | 54 | 这些任务的关键是,它们能够将控制权交给运行时的调度程序,然后在稍后停止的地方继续执行。 55 | 与`leaf-future`相比,这些Future本身并不代表I/O资源。 当我们对这些Future进行轮询时, 有可能会运行一段时间或者因为等待相关资源而让度给调度器,然后等待相关资源ready的时候唤醒自己. 56 | 57 | 58 | 59 | ### 运行时(Runtimes) 60 | 61 | 像 c # ,JavaScript,Java,GO 和许多其他语言都有一个处理并发的运行时。 所以如果你来自这些语言中的一种,这对你来说可能会有点奇怪。 62 | 63 | Rust 与这些语言的不同之处在于 Rust 没有处理并发性的运行时,因此您需要使用一个为您提供此功能的库。 64 | 65 | 很多复杂性归因于 Futures 实际上是来源于运行时的复杂性,创建一个有效的运行时是困难的。 66 | 学习如何正确使用一个也需要相当多的努力,但是你会看到这些类型的运行时之间有几个相似之处,所以学习一个可以使学习下一个更容易。 67 | 68 | Rust 和其他语言的区别在于,在选择运行时时,您必须进行主动选择。大多数情况下,在其他语言中,你只会使用提供给你的那一种。 69 | 70 | 异步运行时可以分为两部分: 71 | 1. 执行器(The Executor) 72 | 2. reactor (The Reactor) 73 | 74 | 当 Rusts Futures 被设计出来的时候,有一个愿望,那就是将通知`Future`它可以做更多工作的工作与`Future`实际做工作分开。 75 | 76 | 你可以认为前者是reactor的工作,后者是执行器的工作。 运行时的这两个部分使用 `Waker`进行交互。 77 | 78 | 写这篇文章的时候,未来最受欢迎的两个运行时是: 79 | 80 | 1. [async-std](https://github.com/async-rs/async-std) 81 | 2. [Tokio](https://github.com/tokio-rs/tokio) 82 | 83 | ### Rust 的标准库做了什么 84 | 85 | 1. 一个公共接口,`Future trait` 86 | 2. 一个符合人体工程学的方法创建任务, 可以通过async和await关键字进行暂停和恢复`Future` 87 | 3. `Waker`接口, 可以唤醒暂停的`Future` 88 | 89 | 这就是Rust标准库所做的。 正如你所看到的,不包括异步I/O的定义,这些异步任务是如何被创建的,如何运行的。 90 | 91 | ### I/O密集型 VS CPU密集型任务 92 | 93 | 正如你们现在所知道的,你们通常所写的是`Non-leaf-futures`。 让我们以 pseudo-rust 为例来看一下这个异步块: 94 | ```rust 95 | let non_leaf = async { 96 | let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap(); // <-- yield 97 | 98 | // request a large dataset 99 | let result = stream.write(get_dataset_request).await.unwrap(); // <-- yield 100 | 101 | // wait for the dataset 102 | let mut response = vec![]; 103 | stream.read(&mut response).await.unwrap(); // <-- yield 104 | 105 | // do some CPU-intensive analysis on the dataset 106 | let report = analyzer::analyze_data(response).unwrap(); 107 | 108 | // send the results back 109 | stream.write(report).await.unwrap(); // <-- yield 110 | }; 111 | 112 | ``` 113 | 114 | 现在,正如您将看到的,当我们介绍 Futures 的工作原理时,两个`yield`之间的代码与我们的执行器在同一个线程上运行。 115 | 116 | 这意味着当我们分析器处理数据集时,执行器忙于计算而不是处理新的请求。 117 | 118 | 幸运的是,有几种方法可以解决这个问题,这并不困难,但是你必须意识到: 119 | 1. 我们可以创建一个新的`leaf future`,它将我们的任务发送到另一个线程,并在任务完成时解析。 我们可以像等待其他Future一样等待这个`leaf-future`。 120 | 2. 运行时可以有某种类型的管理程序来监视不同的任务占用多少时间,并将执行器本身移动到不同的线程,这样即使我们的分析程序任务阻塞了原始的执行程序线程,它也可以继续运行。 121 | 3. 您可以自己创建一个与运行时兼容的`reactor`,以您认为合适的任何方式进行分析,并返回一个可以等待的未来。 122 | 123 | 现在,#1是通常的处理方式,但是一些执行器也实现了#2。 2的问题是,如果你切换运行时,你需要确保它也支持这种监督,否则你最终会阻塞执行者。 124 | 125 | 方式#3更多的是理论上的重要性,通常您会很乐意将任务发送到多数运行时提供的线程池。 126 | 127 | 大多数执行器都可以使用诸如 spawn blocking 之类的方法来完成#1。 128 | 129 | 这些方法将任务发送到运行时创建的线程池,在该线程池中,您可以执行 cpu 密集型任务,也可以执行运行时不支持的“阻塞”任务。 130 | 131 | 现在,有了这些知识,你已经在一个很好的方式来理解`Future`,但我们不会停止,有很多细节需要讨论。 132 | 133 | 休息一下或喝杯咖啡,准备好我们进入下一章的深度探索。 134 | 135 | ### 奖励部分 136 | 137 | 138 | 如果你发现并发和异步编程的概念一般来说令人困惑,我知道你是从哪里来的,我已经写了一些资源,试图给出一个高层次的概述,这将使之后更容易学习 Rusts Futures: 139 | 140 | - [Async Basics - The difference between concurrency and parallelism 异步基础-并发和并行之间的区别](https://cfsamson.github.io/book-exploring-async-basics/1_concurrent_vs_parallel.html) 141 | - [Async Basics - Async history 异步基础-异步历史](https://cfsamson.github.io/book-exploring-async-basics/2_async_history.html) 142 | - [Async Basics - Strategies for handling I/O 异步基础-处理 i / o 的策略](https://cfsamson.github.io/book-exploring-async-basics/5_strategies_for_handling_io.html) 143 | - [Async Basics - Epoll, Kqueue and IOCP 异步基础-Epoll,Kqueue 和 IOCP](https://cfsamson.github.io/book-exploring-async-basics/6_epoll_kqueue_iocp.html) 144 | 145 | 146 | 通过研究`Future`来学习这些概念会让它变得比实际需要难得多,所以如果你有点不确定的话,继续读这些章节。 147 | 148 | 你回来的时候我就在这儿。 149 | 150 | 如果你觉得你已经掌握了基本知识,那么让我们开始行动吧! 151 | -------------------------------------------------------------------------------- /src/2_waker_context.md: -------------------------------------------------------------------------------- 1 | ## 唤醒器和上下文(Waker and Context) 2 | 3 | ### 概述 4 | 5 | 1. 了解 Waker 对象是如何构造的 6 | 2. 了解运行时如何知道`leaf-future`何时可以恢复 7 | 3. 了解动态分发的基础知识和trait对象 8 | 9 | `Waker`类型在[RFC#2592](https://github.com/rust-lang/rfcs/blob/master/text/2592-futures.md#waking-up)中介绍. 10 | 11 | 12 | ### 唤醒器 13 | 14 | `Waker`类型允许在运行时的reactor 部分和执行器部分之间进行松散耦合。 15 | 16 | 通过使用不与`Future`执行绑定的唤醒机制,运行时实现者可以提出有趣的新唤醒机制。 例如,可以生成一个线程来执行一些工作,这些工作结束时通知`Future`,这完全独立于当前的运行时。 17 | 18 | 如果没有唤醒程序,执行程序将是通知正在运行的任务的唯一方式,而使用唤醒程序,我们将得到一个松散耦合,其中很容易使用新的`leaf-future`来扩展生态系统。 19 | 20 | > 如果你想了解更多关于 Waker 类型背后的原因,我可以推荐[Withoutboats articles series about them](https://boats.gitlab.io/blog/post/wakers-i/)。 21 | 22 | ### 理解唤醒器 23 | 24 | 在实现我们自己的`Future`时,我们遇到的最令人困惑的事情之一就是我们如何实现一个唤醒器。 创建一个 Waker 需要创建一个 vtable,这个vtable允许我们使用动态方式调用我们真实的Waker实现. 25 | 26 | > 如果你想知道更多关于Rust中的动态分发,我可以推荐 Adam Schwalm 写的一篇文章 [Exploring Dynamic Dispatch in Rust](https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/). 27 | 28 | 让我们更详细地解释一下。 29 | 30 | ### Rust中的胖指针 31 | 32 | 为了更好地理解我们如何在 Rust 中实现 Waker,我们需要退后一步并讨论一些基本原理。 让我们首先看看 Rust 中一些不同指针类型的大小。 33 | 34 | 运行以下代码: 35 | ```rust 36 | trait SomeTrait { } 37 | 38 | fn main() { 39 | println!("======== The size of different pointers in Rust: ========"); 40 | println!("&dyn Trait:-----{}", size_of::<&dyn SomeTrait>()); 41 | println!("&[&dyn Trait]:--{}", size_of::<&[&dyn SomeTrait]>()); 42 | println!("Box:-----{}", size_of::>()); 43 | println!("&i32:-----------{}", size_of::<&i32>()); 44 | println!("&[i32]:---------{}", size_of::<&[i32]>()); 45 | println!("Box:-------{}", size_of::>()); 46 | println!("&Box:------{}", size_of::<&Box>()); 47 | println!("[&dyn Trait;4]:-{}", size_of::<[&dyn SomeTrait; 4]>()); 48 | println!("[i32;4]:--------{}", size_of::<[i32; 4]>()); 49 | } 50 | ``` 51 | 从运行后的输出中可以看到,引用的大小是不同的。 许多是8字节(在64位系统中是指针大小) ,但有些是16字节。 52 | 53 | 16字节大小的指针被称为“胖指针” ,因为它们携带额外的信息。 54 | 55 | 56 | 例如 `&[i32]`: 57 | - 前8个字节是指向数组中第一个元素的实际指针(或 slice 引用的数组的一部分) 58 | - 第二个8字节是切片的长度 59 | 60 | 61 | 例如 `&dyn SomeTrait`: 62 | 63 | 这就是我们将要关注的胖指针的类型。`&dyn SomeTrait` 是一个trait的引用,或者 Rust称之为一个trait对象。 64 | 65 | 指向 trait 对象的指针布局如下: 66 | - 前8个字节指向trait 对象的data 67 | - 后八个字节指向trait对象的 vtable 68 | 69 | 这样做的好处是,我们可以引用一个对象,除了它实现了 trait 定义的方法之外,我们对这个对象一无所知。 为了达到这个目的,我们使用动态分发。 70 | 71 | 让我们用代码而不是文字来解释这一点,通过这些部分来实现我们自己的 trait 对象: 72 | 73 | ```rust 74 | // A reference to a trait object is a fat pointer: (data_ptr, vtable_ptr) 75 | trait Test { 76 | fn add(&self) -> i32; 77 | fn sub(&self) -> i32; 78 | fn mul(&self) -> i32; 79 | } 80 | 81 | // This will represent our home brewn fat pointer to a trait object 82 | #[repr(C)] 83 | struct FatPointer<'a> { 84 | /// A reference is a pointer to an instantiated `Data` instance 85 | data: &'a mut Data, 86 | /// Since we need to pass in literal values like length and alignment it's 87 | /// easiest for us to convert pointers to usize-integers instead of the other way around. 88 | vtable: *const usize, 89 | } 90 | 91 | // This is the data in our trait object. It's just two numbers we want to operate on. 92 | struct Data { 93 | a: i32, 94 | b: i32, 95 | } 96 | 97 | // ====== function definitions ====== 98 | fn add(s: &Data) -> i32 { 99 | s.a + s.b 100 | } 101 | fn sub(s: &Data) -> i32 { 102 | s.a - s.b 103 | } 104 | fn mul(s: &Data) -> i32 { 105 | s.a * s.b 106 | } 107 | 108 | fn main() { 109 | let mut data = Data {a: 3, b: 2}; 110 | // vtable is like special purpose array of pointer-length types with a fixed 111 | // format where the three first values has a special meaning like the 112 | // length of the array is encoded in the array itself as the second value. 113 | let vtable = vec![ 114 | 0, // pointer to `Drop` (which we're not implementing here) 115 | 6, // lenght of vtable 116 | 8, // alignment 117 | 118 | // we need to make sure we add these in the same order as defined in the Trait. 119 | add as usize, // function pointer - try changing the order of `add` 120 | sub as usize, // function pointer - and `sub` to see what happens 121 | mul as usize, // function pointer 122 | ]; 123 | 124 | let fat_pointer = FatPointer { data: &mut data, vtable: vtable.as_ptr()}; 125 | let test = unsafe { std::mem::transmute::(fat_pointer) }; 126 | 127 | // And voalá, it's now a trait object we can call methods on 128 | println!("Add: 3 + 2 = {}", test.add()); 129 | println!("Sub: 3 - 2 = {}", test.sub()); 130 | println!("Mul: 3 * 2 = {}", test.mul()); 131 | } 132 | ``` 133 | 134 | 稍后,当我们实现我们自己的 Waker 时,我们实际上会像这里一样建立一个 vtable。 我们创造它的方式略有不同,但是现在你知道了规则特征对象是如何工作的,你可能会认识到我们在做什么,这使得它不那么神秘。 135 | 136 | ### 奖励部分 137 | 138 | 您可能想知道为什么Waker是这样实现的,而不仅仅是作为一个普通的trait. 139 | 140 | 原因在于灵活性。 以这里的方式实现 Waker,可以很灵活地选择要使用的内存管理方案。 141 | 142 | “正常”的方法是使用 Arc 来使用引用计数来跟踪 Waker 对象何时可以被删除。 但是,这不是唯一的方法,您还可以使用纯粹的全局函数和状态,或者任何其他您希望的方法。 143 | 144 | 这在表中为运行时实现者留下了许多选项。 145 | 146 | -------------------------------------------------------------------------------- /src/3_generators_async_await.md: -------------------------------------------------------------------------------- 1 | ## 生成器和async/await 2 | 3 | ### 概述 4 | 5 | 1. 理解 async / await 语法在底层是如何工作的 6 | 2. 亲眼目睹(See first hand)我们为什么需要Pin 7 | 3. 理解是什么让 Rusts 异步模型的内存效率非常高 8 | 9 | 生成器的动机可以在 [RFC#2033](https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md)中找到。 它写得非常好,我建议您通读它(它谈论async/await的内容和谈论生成器的内容一样多)。 10 | 11 | ### 为什么要学习生成器 12 | 13 | generators/yield和 async/await 非常相似,一旦理解了其中一个,就应该能够理解另一个。 14 | 15 | 对我来说,使用Generators而不是 Futures 来提供可运行的和简短的示例要容易得多,这需要我们现在引入很多概念,稍后我们将介绍这些概念,以便展示示例。 16 | 17 | Async/await 的工作方式类似于生成器,但它不返回生成器,而是返回一个实现 Future trait 的特殊对象。 18 | 19 | 一个小小的好处是,在本章的最后,你将有一个很好的关于生成器和 async / await 的介绍。 20 | 21 | 基本上,在设计 Rust 如何处理并发时,主要讨论了三个选项: 22 | 1. Green Thread. 23 | 2. 使用组合符(Using combinators.) 24 | 3. Generator, 没有专门的栈 25 | 26 | 我们在背景信息中覆盖了绿色线程,所以我们不会在这里重复。 我们将集中在各种各样的无堆栈协同程序,这也就是Rust正在使用的. 27 | 28 | ### 组合子(Combinators) 29 | 30 | 在`Futures 0.1`中使用组合子.如果你曾经是用过Javascript中的`Promises`,那么你已经比较熟悉combinators了. 在Rust中,他们看起来如下: 31 | ```rust 32 | let future = Connection::connect(conn_str).and_then(|conn| { 33 | conn.query("somerequest").map(|row|{ 34 | SomeStruct::from(row) 35 | }).collect::>() 36 | }); 37 | 38 | let rows: Result, SomeLibraryError> = block_on(future); 39 | ``` 40 | 41 | 使用这个技巧主要有三个缺点: 42 | 1. 错误消息可能会冗长并且难懂 43 | 2. 不是最佳的内存使用(浪费内存) 44 | 3. Rust中不允许跨组合子借用. 45 | 46 | 其中第三点是这种方式的主要缺点. 47 | 48 | 不允许跨组合子借用,结果是非常不符合人体工程学的.为了完成某些任务,需要额外的内存分配或者复制,这很低效。 49 | 50 | 内存占用高的原因是,这基本上是一种基于回调的方法,其中每个闭包存储计算所需的所有数据。 这意味着,随着我们将它们链接起来,存储所需状态所需的内存会随着每一步的增加而增加。 51 | 52 | ### 无栈协程/生成器 53 | 这就是今天 Rust 使用的模型,它有几个显著的优点: 54 | 1. 使用 async/await 作为关键字,可以很容易地将普通的Rust代码转换为无堆栈的协程(甚至可以使用宏来完成) 55 | 2. 不需要上下文切换与保存恢复CPU状态 56 | 3. 不需要处理的动态栈分配 57 | 4. 内存效率高 58 | 5. 允许我们块暂停点(suspension)借用 **这是啥意思啊** 59 | 60 | 与Futures 0.1不一样,使用async/ await 我们可以这样做: 61 | 62 | ```rust 63 | async fn myfn() { 64 | let text = String::from("Hello world"); 65 | let borrowed = &text[0..5]; 66 | somefuture.await; 67 | println!("{}", borrowed); 68 | } 69 | ``` 70 | Rust中的异步使用生成器实现. 因此为了理解异步是如何工作的,我们首先需要理解生成器。 在Rust中,生成器被实现为状态机。 71 | 72 | 一个计算链的内存占用是由占用空间最大的那个步骤定义的。 73 | 74 | 这意味着在计算链中添加步骤可能根本不需要增加任何内存,这也是为什么Futures和 Async 在 Rust 中的开销很小的原因之一。 75 | 76 | 77 | ### 生成器是如何工作的 78 | 79 | 80 | 在今天的 Nightly Rust 中,你可以使用关键词 yield。 在闭包中使用这个关键字,将其转换为生成器。 在介绍Pin之前,闭包是这样的: 81 | 82 | ```rust 83 | #![feature(generators, generator_trait)] 84 | use std::ops::{Generator, GeneratorState}; 85 | 86 | fn main() { 87 | let a: i32 = 4; 88 | let mut gen = move || { 89 | println!("Hello"); 90 | yield a * 2; 91 | println!("world!"); 92 | }; 93 | 94 | if let GeneratorState::Yielded(n) = gen.resume() { 95 | println!("Got value {}", n); 96 | } 97 | 98 | if let GeneratorState::Complete(()) = gen.resume() { 99 | () 100 | }; 101 | } 102 | ``` 103 | 104 | 早些时候,在人们对 Pin 的设计达成共识之前,编译完代码看起来类似于这样: 105 | 106 | ```rust 107 | fn main() { 108 | let mut gen = GeneratorA::start(4); 109 | 110 | if let GeneratorState::Yielded(n) = gen.resume() { 111 | println!("Got value {}", n); 112 | } 113 | 114 | if let GeneratorState::Complete(()) = gen.resume() { 115 | () 116 | }; 117 | } 118 | 119 | // If you've ever wondered why the parameters are called Y and R the naming from 120 | // the original rfc most likely holds the answer 121 | enum GeneratorState { 122 | Yielded(Y), // originally called `Yield(Y)` 123 | Complete(R), // originally called `Return(R)` 124 | } 125 | 126 | trait Generator { 127 | type Yield; 128 | type Return; 129 | fn resume(&mut self) -> GeneratorState; 130 | } 131 | 132 | enum GeneratorA { 133 | Enter(i32), 134 | Yield1(i32), 135 | Exit, 136 | } 137 | 138 | impl GeneratorA { 139 | fn start(a1: i32) -> Self { 140 | GeneratorA::Enter(a1) 141 | } 142 | } 143 | 144 | impl Generator for GeneratorA { 145 | type Yield = i32; 146 | type Return = (); 147 | fn resume(&mut self) -> GeneratorState { 148 | // lets us get ownership over current state 149 | match std::mem::replace(self, GeneratorA::Exit) { 150 | GeneratorA::Enter(a1) => { 151 | 152 | /*----code before yield----*/ 153 | println!("Hello"); 154 | let a = a1 * 2; 155 | 156 | *self = GeneratorA::Yield1(a); 157 | GeneratorState::Yielded(a) 158 | } 159 | 160 | GeneratorA::Yield1(_) => { 161 | /*-----code after yield-----*/ 162 | println!("world!"); 163 | 164 | *self = GeneratorA::Exit; 165 | GeneratorState::Complete(()) 166 | } 167 | GeneratorA::Exit => panic!("Can't advance an exited generator!"), 168 | } 169 | } 170 | } 171 | 172 | ``` 173 | 174 | 关键词yield首先在[RFC#1823](https://github.com/rust-lang/rfcs/pull/1823)和 [RFC#1832](https://github.com/rust-lang/rfcs/pull/1832)中讨论。 175 | 176 | 既然您知道了现实中的 yield 关键字会将代码重写为状态机,那么您还将了解await 如何工作的,他们非常相似. 177 | 178 | 上述简单的状态机中有一些限制,当跨yield发生借用的时候会发生什么呢? 179 | 180 | 我们可以禁止这样做,但async/await 语法的主要设计目标之一就是允许这样做。 这些类型的借用是不可能使用`Futures 0.1`,所以我们不能让这个限制存在。 181 | 182 | 与其在理论上讨论它,不如让我们来看看一些代码。 183 | 184 | > 我们将使用目前 Rust 中使用的状态机的优化版本。 更深入的解释见 Tyler Mandry 的文章: [Rust 如何优化async/await](https://tmandry.gitlab.io/blog/posts/optimizing-await-1/) 185 | 186 | 187 | ```rust 188 | let mut generator = move || { 189 | let to_borrow = String::from("Hello"); 190 | let borrowed = &to_borrow; 191 | yield borrowed.len(); 192 | println!("{} world!", borrowed); 193 | }; 194 | ``` 195 | 196 | 我们将手工编写一些版本的状态机,这些状态机表示生成器定义的状态机。 197 | 198 | 在每个示例中,我们都是“手动”逐步完成每个步骤,因此它看起来非常陌生。 我们可以添加一些语法糖,比如为我们的生成器实现 Iterator trait,这样我们就可以这样做: 199 | 200 | ```rust 201 | while let Some(val) = generator.next() { 202 | println!("{}", val); 203 | } 204 | ``` 205 | 206 | 这是一个相当微不足道的改变,但是这一章已经变得很长了。我们继续前进的时候,请牢牢记住这点。 207 | 208 | 现在,我们的重写状态机在这个示例中看起来是什么样子的? 209 | ```rust 210 | 211 | #![allow(unused_variables)] 212 | fn main() { 213 | enum GeneratorState { 214 | Yielded(Y), 215 | Complete(R), 216 | } 217 | 218 | trait Generator { 219 | type Yield; 220 | type Return; 221 | fn resume(&mut self) -> GeneratorState; 222 | } 223 | 224 | enum GeneratorA { 225 | Enter, 226 | Yield1 { 227 | to_borrow: String, 228 | borrowed: &String, // uh, what lifetime should this have? 229 | }, 230 | Exit, 231 | } 232 | 233 | impl GeneratorA { 234 | fn start() -> Self { 235 | GeneratorA::Enter 236 | } 237 | } 238 | 239 | impl Generator for GeneratorA { 240 | type Yield = usize; 241 | type Return = (); 242 | fn resume(&mut self) -> GeneratorState { 243 | // lets us get ownership over current state 244 | match std::mem::replace(self, GeneratorA::Exit) { 245 | GeneratorA::Enter => { 246 | let to_borrow = String::from("Hello"); 247 | let borrowed = &to_borrow; // <--- NB! 248 | let res = borrowed.len(); 249 | 250 | *self = GeneratorA::Yield1 {to_borrow, borrowed}; 251 | GeneratorState::Yielded(res) 252 | } 253 | 254 | GeneratorA::Yield1 {to_borrow, borrowed} => { 255 | println!("Hello {}", borrowed); 256 | *self = GeneratorA::Exit; 257 | GeneratorState::Complete(()) 258 | } 259 | GeneratorA::Exit => panic!("Can't advance an exited generator!"), 260 | } 261 | } 262 | } 263 | } 264 | ``` 265 | 266 | 如果你试图编译这个,你会得到一个错误。 267 | 268 | 字符串的生命周期是什么。 这和Self的生命周期是不一样的。 它不是静态的。 事实证明,我们不可能用Rusts语法来描述这个生命周期,这意味着,为了使这个工作成功,我们必须让编译器知道,我们自己正确地控制了它。 269 | 270 | 这意味着必须借助unsafe。 271 | 272 | 让我们尝试编写一个使用unsafe的实现。 正如您将看到的,我们最终将使用一个自引用结构, 也就是将引用保存在自身中的结构体。 273 | 274 | 275 | 正如您所注意到的,这个编译器编译得很好! 276 | 277 | ```rust 278 | 279 | #![allow(unused_variables)] 280 | fn main() { 281 | enum GeneratorState { 282 | Yielded(Y), 283 | Complete(R), 284 | } 285 | 286 | trait Generator { 287 | type Yield; 288 | type Return; 289 | fn resume(&mut self) -> GeneratorState; 290 | } 291 | 292 | enum GeneratorA { 293 | Enter, 294 | Yield1 { 295 | to_borrow: String, 296 | borrowed: *const String, // NB! This is now a raw pointer! 297 | }, 298 | Exit, 299 | } 300 | 301 | impl GeneratorA { 302 | fn start() -> Self { 303 | GeneratorA::Enter 304 | } 305 | } 306 | impl Generator for GeneratorA { 307 | type Yield = usize; 308 | type Return = (); 309 | fn resume(&mut self) -> GeneratorState { 310 | match self { 311 | GeneratorA::Enter => { 312 | let to_borrow = String::from("Hello"); 313 | let borrowed = &to_borrow; 314 | let res = borrowed.len(); 315 | *self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()}; 316 | 317 | // NB! And we set the pointer to reference the to_borrow string here 318 | if let GeneratorA::Yield1 {to_borrow, borrowed} = self { 319 | *borrowed = to_borrow; 320 | } 321 | 322 | GeneratorState::Yielded(res) 323 | } 324 | 325 | GeneratorA::Yield1 {borrowed, ..} => { 326 | let borrowed: &String = unsafe {&**borrowed}; 327 | println!("{} world", borrowed); 328 | *self = GeneratorA::Exit; 329 | GeneratorState::Complete(()) 330 | } 331 | GeneratorA::Exit => panic!("Can't advance an exited generator!"), 332 | } 333 | } 334 | } 335 | } 336 | ``` 337 | 请记住,我们的例子是我们生成的生成器,它的原始文件像这样: 338 | ```rust 339 | let mut gen = move || { 340 | let to_borrow = String::from("Hello"); 341 | let borrowed = &to_borrow; 342 | yield borrowed.len(); 343 | println!("{} world!", borrowed); 344 | }; 345 | ``` 346 | 下面是我们如何运行这个状态机的示例,正如您所看到的,它完成了我们所期望的任务。 但这仍然存在一个巨大的问题: 347 | 348 | 349 | ```rust 350 | pub fn main() { 351 | let mut gen = GeneratorA::start(); 352 | let mut gen2 = GeneratorA::start(); 353 | 354 | if let GeneratorState::Yielded(n) = gen.resume() { 355 | println!("Got value {}", n); 356 | } 357 | 358 | if let GeneratorState::Yielded(n) = gen2.resume() { 359 | println!("Got value {}", n); 360 | } 361 | 362 | if let GeneratorState::Complete(()) = gen.resume() { 363 | () 364 | }; 365 | } 366 | enum GeneratorState { 367 | Yielded(Y), 368 | Complete(R), 369 | } 370 | 371 | trait Generator { 372 | type Yield; 373 | type Return; 374 | fn resume(&mut self) -> GeneratorState; 375 | } 376 | 377 | enum GeneratorA { 378 | Enter, 379 | Yield1 { 380 | to_borrow: String, 381 | borrowed: *const String, 382 | }, 383 | Exit, 384 | } 385 | 386 | impl GeneratorA { 387 | fn start() -> Self { 388 | GeneratorA::Enter 389 | } 390 | } 391 | impl Generator for GeneratorA { 392 | type Yield = usize; 393 | type Return = (); 394 | fn resume(&mut self) -> GeneratorState { 395 | match self { 396 | GeneratorA::Enter => { 397 | let to_borrow = String::from("Hello"); 398 | let borrowed = &to_borrow; 399 | let res = borrowed.len(); 400 | *self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()}; 401 | 402 | // We set the self-reference here 403 | if let GeneratorA::Yield1 {to_borrow, borrowed} = self { 404 | *borrowed = to_borrow; 405 | } 406 | 407 | GeneratorState::Yielded(res) 408 | } 409 | 410 | GeneratorA::Yield1 {borrowed, ..} => { 411 | let borrowed: &String = unsafe {&**borrowed}; 412 | println!("{} world", borrowed); 413 | *self = GeneratorA::Exit; 414 | GeneratorState::Complete(()) 415 | } 416 | GeneratorA::Exit => panic!("Can't advance an exited generator!"), 417 | } 418 | } 419 | } 420 | ``` 421 | 问题在于,如果在Safe Rust代码中,我们这样做: 422 | ```rust 423 | #![feature(never_type)] // Force nightly compiler to be used in playground 424 | // by betting on it's true that this type is named after it's stabilization date... 425 | pub fn main() { 426 | let mut gen = GeneratorA::start(); 427 | let mut gen2 = GeneratorA::start(); 428 | 429 | if let GeneratorState::Yielded(n) = gen.resume() { 430 | println!("Got value {}", n); 431 | } 432 | 433 | std::mem::swap(&mut gen, &mut gen2); // <--- Big problem! 434 | 435 | if let GeneratorState::Yielded(n) = gen2.resume() { 436 | println!("Got value {}", n); 437 | } 438 | 439 | // This would now start gen2 since we swapped them. 440 | if let GeneratorState::Complete(()) = gen.resume() { 441 | () 442 | }; 443 | } 444 | enum GeneratorState { 445 | Yielded(Y), 446 | Complete(R), 447 | } 448 | 449 | trait Generator { 450 | type Yield; 451 | type Return; 452 | fn resume(&mut self) -> GeneratorState; 453 | } 454 | 455 | enum GeneratorA { 456 | Enter, 457 | Yield1 { 458 | to_borrow: String, 459 | borrowed: *const String, 460 | }, 461 | Exit, 462 | } 463 | 464 | impl GeneratorA { 465 | fn start() -> Self { 466 | GeneratorA::Enter 467 | } 468 | } 469 | impl Generator for GeneratorA { 470 | type Yield = usize; 471 | type Return = (); 472 | fn resume(&mut self) -> GeneratorState { 473 | match self { 474 | GeneratorA::Enter => { 475 | let to_borrow = String::from("Hello"); 476 | let borrowed = &to_borrow; 477 | let res = borrowed.len(); 478 | *self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()}; 479 | 480 | // We set the self-reference here 481 | if let GeneratorA::Yield1 {to_borrow, borrowed} = self { 482 | *borrowed = to_borrow; 483 | } 484 | 485 | GeneratorState::Yielded(res) 486 | } 487 | 488 | GeneratorA::Yield1 {borrowed, ..} => { 489 | let borrowed: &String = unsafe {&**borrowed}; 490 | println!("{} world", borrowed); 491 | *self = GeneratorA::Exit; 492 | GeneratorState::Complete(()) 493 | } 494 | GeneratorA::Exit => panic!("Can't advance an exited generator!"), 495 | } 496 | } 497 | } 498 | ``` 499 | 500 | 运行代码并比较结果。你看到问题了吗? 501 | 502 | 等等? “Hello”怎么了? 为什么我们的代码出错了? 503 | 504 | 事实证明,虽然上面的例子编译得很好,但是我们在使用安全Rust时将这个API的使用者暴露在可能的内存未定义行为和其他内存错误中。 这是个大问题! 505 | 506 | 507 | 实际上,我已经强制上面的代码使用编译器的夜间版本。 如果您在playground上运行上面的示例,您将看到它在当前稳定状态(1.42.0)上运行时没有panic,但在当前夜间状态(1.44.0)上panic。 太可怕了! 508 | 509 | 我们将在下一章用一个稍微简单一点的例子来解释这里发生了什么,我们将使用 Pin 来修复我们的生成器,所以不用担心,您将看到到底出了什么问题,看看 Pin 如何能够帮助我们在一秒钟内安全地处理自引用类型。 510 | 511 | 在我们详细解释这个问题之前,让我们通过了解生成器和 async 关键字之间的关系来结束本章。 512 | 513 | ### 异步和生成器 514 | Futures 在Rust中被实现为状态机,就像生成器是状态机一样。 515 | 516 | 您可能已经注意到异步块中使用的语法和生成器中使用的语法的相似之处: 517 | 518 | ```rust 519 | let mut gen = move || { 520 | let to_borrow = String::from("Hello"); 521 | let borrowed = &to_borrow; 522 | yield borrowed.len(); 523 | println!("{} world!", borrowed); 524 | }; 525 | 526 | ``` 527 | 528 | 比较一下异步块的类似例子: 529 | 530 | ```rust 531 | let mut fut = async { 532 | let to_borrow = String::from("Hello"); 533 | let borrowed = &to_borrow; 534 | SomeResource::some_task().await; 535 | println!("{} world!", borrowed); 536 | }; 537 | ``` 538 | 539 | 不同之处在于,Futures 的状态与 Generator 的状态不同。 540 | 541 | 异步块将返回一个 Future 而不是 Generator,但是 Future 的工作方式和 Generator 的内部工作方式是相似的。 542 | 543 | 我们不调用`Generator::resume`,而是调用 `Future::poll`,并且不返回 generated 或 Complete,而是返回 Pending 或 Ready。 Future中的每一个await就像生成器中的一个yield。 544 | 545 | 你看到他们现在是怎么联系起来的了吗? 546 | 547 | 548 | 这就是为什么理解了生成器如何工作以及他需要面对的挑战,也就理解了Future如何工作以及它需要面对的挑战。 549 | 550 | 跨yield/await的借用就是这样. 551 | 552 | ### 奖励部分-正在使用的自引用生成器 553 | 554 | 感谢[ PR#45337 ](https://github.com/rust-lang/rust/pull/45337/files),你可以在nightly版本中使用static关键字运行上面的例子. 你可以试试: 555 | 556 | > 要注意的是,API可能会发生改变。 在我撰写本书时,生成器API有一个更改,添加了对“ resume”参数的支持,以便传递到生成器闭包中。 557 | > 可以关注[RFC#033](https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md)的相关[问题#4312](https://github.com/rust-lang/rust/issues/43122)的进展。 558 | 559 | ```rust 560 | #![feature(generators, generator_trait)] 561 | use std::ops::{Generator, GeneratorState}; 562 | 563 | 564 | pub fn main() { 565 | let gen1 = static || { 566 | let to_borrow = String::from("Hello"); 567 | let borrowed = &to_borrow; 568 | yield borrowed.len(); 569 | println!("{} world!", borrowed); 570 | }; 571 | 572 | let gen2 = static || { 573 | let to_borrow = String::from("Hello"); 574 | let borrowed = &to_borrow; 575 | yield borrowed.len(); 576 | println!("{} world!", borrowed); 577 | }; 578 | 579 | let mut pinned1 = Box::pin(gen1); 580 | let mut pinned2 = Box::pin(gen2); 581 | 582 | if let GeneratorState::Yielded(n) = pinned1.as_mut().resume(()) { 583 | println!("Gen1 got value {}", n); 584 | } 585 | 586 | if let GeneratorState::Yielded(n) = pinned2.as_mut().resume(()) { 587 | println!("Gen2 got value {}", n); 588 | }; 589 | 590 | let _ = pinned1.as_mut().resume(()); 591 | let _ = pinned2.as_mut().resume(()); 592 | } 593 | ``` 594 | -------------------------------------------------------------------------------- /src/4_pin.md: -------------------------------------------------------------------------------- 1 | ## Pin 2 | 3 | ### 概述 4 | > 译者注: Pin是在使用Future时一个非常重要的概念,我的理解是: 通过使用Pin,让用户无法安全的获取到`&mut T`,进而无法进行上述例子中的swap. 如果你觉得你的和这个struct没有自引用的问题,你可以自己实现UnPin. 5 | 6 | 1. 了解如何使用Pin以及当你自己实现`Future`的时候为什么需要Pin 7 | 2. 理解如何让自引用类型被安全的使用 8 | 3. 理解跨'await`借用是如何实现的 9 | 4. 制定一套实用的规则来帮助你使用Pin 10 | 11 | Pin是在[RFC#2349](https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md)中被提出的. 12 | 13 | 让我们直接了当的说吧,Pin是这一系列概念中很难一开始就搞明白的,但是一旦你理解了其心智模型,就会觉得非常容易理解. 14 | 15 | 16 | ### 定义 17 | 18 | Pin只与指针有关,在Rust中引用也是指针. 19 | 20 | Pin有`Pin`类型和`Unpin`标记组成(UnPin是Rust中为数不多的几个auto trait). Pin存在的目的就是为了让那些实现了`!UnPin`的类型遵守特定的规则. 21 | 22 | 23 | 是的,你是对的,这里是双重否定`!Unpin` 的意思是“not-un-pin”。 24 | 25 | 26 | > 这个命名方案是 Rusts 的安全特性之一,它故意测试您是否因为太累而无法安全地使用这个标记来实现类型。 如果你因为`UnPin`开始感到困惑,或者甚至生气,那么你就应该这样做! 是时候放下工作,以全新的心态重新开始明天的生活了,这是一个好兆头。 27 | 28 | 更严肃地说,我认为有必要提到,选择这些名字是有正当理由的。 命名并不容易,我曾经考虑过在这本书中重命名 `Unpin` 和`!UnPin` ,使他们更容易理解。 29 | 30 | 然而,一位经验丰富的Rust社区成员让我相信,当简单地给这些标记起不同的名字时,有太多的细微差别和边缘情况需要考虑,而这些很容易被忽略,我相信我们将不得不习惯它们并按原样使用它们。 31 | 32 | 如果你愿意,你可以从[内部讨论](https://internals.rust-lang.org/t/naming-pin-anchor-move/6864)中读到一些讨论。 33 | 34 | ### Pinning和自引用结构 35 | 让我们从上一章(生成器那一章)停止的地方开始,通过使用一些比状态机更容易推理的自引用结构,使我们在生成器中看到的使用自引用结构的问题变得简单得多: 36 | 37 | 38 | 现在我们的例子是这样的: 39 | 40 | ```rust 41 | use std::pin::Pin; 42 | 43 | #[derive(Debug)] 44 | struct Test { 45 | a: String, 46 | b: *const String, 47 | } 48 | 49 | impl Test { 50 | fn new(txt: &str) -> Self { 51 | let a = String::from(txt); 52 | Test { 53 | a, 54 | b: std::ptr::null(), 55 | } 56 | } 57 | 58 | fn init(&mut self) { 59 | let self_ref: *const String = &self.a; 60 | self.b = self_ref; 61 | } 62 | 63 | fn a(&self) -> &str { 64 | &self.a 65 | } 66 | 67 | fn b(&self) -> &String { 68 | unsafe {&*(self.b)} 69 | } 70 | } 71 | ``` 72 | 73 | 让我们来回顾一下这个例子,因为我们将在本章的其余部分使用它。 74 | 75 | 我们有一个自引用结构体`Test`。 Test需要创建一个init方法,这个方法很奇怪,但是为了尽可能简短,我们需要这个方法。 76 | 77 | Test 提供了两种方法来获取字段 a 和 b 值的引用。 因为 b 是 a 的参考,所以我们把它存储为一个指针,因为 Rust 的借用规则不允许我们定义这个生命周期。 78 | 79 | 现在,让我们用这个例子来详细解释我们遇到的问题: 80 | 81 | ```rust 82 | fn main() { 83 | let mut test1 = Test::new("test1"); 84 | test1.init(); 85 | let mut test2 = Test::new("test2"); 86 | test2.init(); 87 | 88 | println!("a: {}, b: {}", test1.a(), test1.b()); 89 | println!("a: {}, b: {}", test2.a(), test2.b()); 90 | 91 | } 92 | ``` 93 | 94 | 在main函数中,我们首先实例化Test的两个实例,然后输出test1和test2各字段的值,结果如我们所料: 95 | 96 | ``` 97 | a: test1, b: test1 98 | a: test2, b: test2 99 | ``` 100 | 让我们看看,如果我们将存储在 test1指向的内存位置的数据与存储在 test2指向的内存位置的数据进行交换,会发生什么情况,反之亦然。 101 | 102 | ```rust 103 | fn main() { 104 | let mut test1 = Test::new("test1"); 105 | test1.init(); 106 | let mut test2 = Test::new("test2"); 107 | test2.init(); 108 | 109 | println!("a: {}, b: {}", test1.a(), test1.b()); 110 | std::mem::swap(&mut test1, &mut test2); 111 | println!("a: {}, b: {}", test2.a(), test2.b()); 112 | 113 | } 114 | ``` 115 | 116 | 我们可能会认为会打印两边test1,比如: 117 | 118 | ```rust 119 | a: test1, b: test1 120 | a: test1, b: test1 121 | ``` 122 | 123 | 但是实际上我们得到的是: 124 | ``` 125 | a: test1, b: test1 126 | a: test1, b: test2 127 | ``` 128 | 129 | 指向 test2.b 的指针仍然指向test1内部的旧位置。 该结构不再是自引用的,它保存指向不同对象中的字段的指针。 这意味着我们不能再依赖test2.b的生存期与test2的生存期绑定在一起。 130 | 131 | 如果你仍然不相信,这至少可以说服你: 132 | 133 | ```rust 134 | fn main() { 135 | let mut test1 = Test::new("test1"); 136 | test1.init(); 137 | let mut test2 = Test::new("test2"); 138 | test2.init(); 139 | 140 | println!("a: {}, b: {}", test1.a(), test1.b()); 141 | std::mem::swap(&mut test1, &mut test2); 142 | test1.a = "I've totally changed now!".to_string(); 143 | println!("a: {}, b: {}", test2.a(), test2.b()); 144 | 145 | } 146 | ``` 147 | 这是不应该发生的。 目前还没有严重的错误,但是您可以想象,使用这些代码很容易创建严重的错误。 148 | 149 | 我创建了一个图表来帮助可视化正在发生的事情: 150 | 151 | ![](swap_problem.jpg) 152 | 图1: 交换前后 153 | 154 | 正如你看到的,这不是我们想要的结果. 这很容易导致段错误,也很容易导致其他意想不到的未知行为以及失败. 155 | 156 | ### 固定在栈上 157 | 158 | 现在,我们可以通过使用`Pin`来解决这个问题。 让我们来看看我们的例子是什么样的: 159 | 160 | ```rust 161 | use std::pin::Pin; 162 | use std::marker::PhantomPinned; 163 | 164 | #[derive(Debug)] 165 | struct Test { 166 | a: String, 167 | b: *const String, 168 | _marker: PhantomPinned, 169 | } 170 | 171 | 172 | impl Test { 173 | fn new(txt: &str) -> Self { 174 | let a = String::from(txt); 175 | Test { 176 | a, 177 | b: std::ptr::null(), 178 | // This makes our type `!Unpin` 179 | _marker: PhantomPinned, 180 | } 181 | } 182 | fn init<'a>(self: Pin<&'a mut Self>) { 183 | let self_ptr: *const String = &self.a; 184 | let this = unsafe { self.get_unchecked_mut() }; 185 | this.b = self_ptr; 186 | } 187 | 188 | fn a<'a>(self: Pin<&'a Self>) -> &'a str { 189 | &self.get_ref().a 190 | } 191 | 192 | fn b<'a>(self: Pin<&'a Self>) -> &'a String { 193 | unsafe { &*(self.b) } 194 | } 195 | } 196 | ``` 197 | 198 | 现在,我们在这里所做的就是固定到一个栈地址。如果我们的类型实现了`!UnPin`,那么它将总是`unsafe`。 199 | 200 | 我们在这里使用相同的技巧,包括需要 init。 如果我们想要解决这个问题并让用户避免`unsafe`,我们需要将数据钉在堆上,我们马上就会展示这一点。 201 | 202 | 让我们看看如果我们现在运行我们的例子会发生什么: 203 | 204 | ```rust 205 | pub fn main() { 206 | // test1 is safe to move before we initialize it 207 | let mut test1 = Test::new("test1"); 208 | // Notice how we shadow `test1` to prevent it from beeing accessed again 209 | let mut test1 = unsafe { Pin::new_unchecked(&mut test1) }; 210 | Test::init(test1.as_mut()); 211 | 212 | let mut test2 = Test::new("test2"); 213 | let mut test2 = unsafe { Pin::new_unchecked(&mut test2) }; 214 | Test::init(test2.as_mut()); 215 | 216 | println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref())); 217 | println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref())); 218 | } 219 | ``` 220 | 221 | 现在,如果我们尝试使用上次使我们陷入麻烦的问题,您将得到一个编译错误。 222 | ```rust 223 | pub fn main() { 224 | let mut test1 = Test::new("test1"); 225 | let mut test1 = unsafe { Pin::new_unchecked(&mut test1) }; 226 | Test::init(test1.as_mut()); 227 | 228 | let mut test2 = Test::new("test2"); 229 | let mut test2 = unsafe { Pin::new_unchecked(&mut test2) }; 230 | Test::init(test2.as_mut()); 231 | 232 | println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref())); 233 | std::mem::swap(test1.as_mut(), test2.as_mut()); 234 | println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref())); 235 | } 236 | ``` 237 | 238 | 正如您从运行代码所得到的错误中看到的那样,类型系统阻止我们交换固定指针。 239 | 240 | > 需要注意的是,栈pinning总是依赖于我们所在的当前栈帧,因此我们不能在一个栈帧中创建一个自引用对象并返回它,因为任何指向“self”的指针都是无效的。 241 | > 如果你把一个值固定在一个栈上,这也会让你承担很多责任。 一个很容易犯的错误是,忘记对原始变量进行阴影处理,因为这样可以在初始化后drop固定的指针并访问原来的值: 242 | ```rust 243 | fn main() { 244 | let mut test1 = Test::new("test1"); 245 | let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) }; 246 | Test::init(test1_pin.as_mut()); 247 | drop(test1_pin); 248 | 249 | let mut test2 = Test::new("test2"); 250 | mem::swap(&mut test1, &mut test2); 251 | println!("Not self referential anymore: {:?}", test1.b); 252 | } 253 | ``` 254 | 255 | ### 固定在堆上 256 | 257 | 258 | 为了完整性,让我们删除一些不安全的内容,通过以堆分配为代价来消除`init`方法。 固定到堆是安全的,这样用户不需要实现任何不安全的代码: 259 | 260 | ```rust 261 | use std::pin::Pin; 262 | use std::marker::PhantomPinned; 263 | 264 | #[derive(Debug)] 265 | struct Test { 266 | a: String, 267 | b: *const String, 268 | _marker: PhantomPinned, 269 | } 270 | 271 | impl Test { 272 | fn new(txt: &str) -> Pin> { 273 | let a = String::from(txt); 274 | let t = Test { 275 | a, 276 | b: std::ptr::null(), 277 | _marker: PhantomPinned, 278 | }; 279 | let mut boxed = Box::pin(t); 280 | let self_ptr: *const String = &boxed.as_ref().a; 281 | unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr }; 282 | 283 | boxed 284 | } 285 | 286 | fn a<'a>(self: Pin<&'a Self>) -> &'a str { 287 | &self.get_ref().a 288 | } 289 | 290 | fn b<'a>(self: Pin<&'a Self>) -> &'a String { 291 | unsafe { &*(self.b) } 292 | } 293 | } 294 | 295 | pub fn main() { 296 | let mut test1 = Test::new("test1"); 297 | let mut test2 = Test::new("test2"); 298 | 299 | println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b()); 300 | println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b()); 301 | } 302 | ``` 303 | 304 | 事实上就算是`!Unpin`有意义,固定一个堆分配的值也是安全的。 一旦在堆上分配了数据,它就会有一个稳定的地址。 305 | 306 | 作为 API 的用户,我们不需要特别注意并确保自引用指针保持有效。 307 | 308 | 也有一些方法能够对固定栈上提供一些安全保证,但是现在我们使用[pin_project](https://docs.rs/pin-project/)这个包来实现这一点。 309 | 310 | ### Pinning的一些实用规则 311 | 312 | 1. 针对`T:UnPin`(这是默认值),`Pin<'a,T>`完全定价与`&'a mut T`. 换句话说: `UnPin`意味着这个类型即使在固定时也可以移动,所以Pin对这个类型没有影响。 313 | 2. 针对`T:!UnPin`,从`Pin< T>`获取到`&mut T`,则必须使用unsafe. 换句话说,`!Unpin`能够阻止API的使用者移动T,除非他写出unsafe的代码. 314 | 3. Pinning对于内存分配没有什么特别的作用,比如将其放入某个“只读”内存或任何奇特的内存中。 它只使用类型系统来防止对该值进行某些操作。 315 | 4. 大多数标准库类型实现 Unpin。 这同样适用于你在 Rust 中遇到的大多数“正常”类型。 `Future`和`Generators`是两个例外。 316 | 5. Pin的主要用途就是自引用类型,Rust语言的所有这些调整就是为了允许这个. 这个API中仍然有一些问题需要探讨. 317 | 6. `!UnPin`这些类型的实现很有可能是不安全的. 在这种类型被钉住后移动它可能会导致程序崩溃。 在撰写本书时,创建和读取自引用结构的字段仍然需要不安全的方法(唯一的方法是创建一个包含指向自身的原始指针的结构)。 318 | 7. 当使用nightly版本时,你可以在一个使用特性标记在一个类型上添加`!UnPin`. 当使用stable版本时,可以将std: : marker: : PhantomPinned 添加到类型上。 319 | 8. 你既可以固定一个栈上的对象也可以固定一个堆上的对象. 320 | 9. 将一个`!UnPin`的指向栈上的指针固定需要unsafe. 321 | 10. 将一个`!UnPin`的指向堆上的指针固定,不需要unsafe,可以直接使用`Box::Pin`. 322 | 323 | > 不安全的代码并不意味着它真的“unsafe” ,它只是减轻了通常从编译器得到的保证。 一个不安全的实现可能是完全安全的,但是您没有编译器保证的安全网。 324 | 325 | ### 映射/结构体的固定 326 | 327 | 简而言之,投影是一个编程语言术语。 Mystruct.field1是一个投影。 结构体的固定是在每一个字段上使用Pin。 这里有一些注意事项,您通常不会看到,因此我参考相关文档。 328 | 329 | ### Pin和Drop 330 | 331 | Pin保证从值被固定到被删除的那一刻起一直存在。 而在Drop实现中,您需要一个可变的 self 引用,这意味着在针对固定类型实现 Drop 时必须格外小心。 332 | 333 | ### 把它们放在一起 334 | 335 | 当我们实现自己的`Futures`的时候,这正是我们要做的,我们很快就完成了。 336 | 337 | ### 奖励部分 338 | 339 | **修复我们实现的自引用生成器以及学习更多的关于Pin的知识.** 340 | 341 | 但是现在,让我们使用 Pin 来防止这个问题。 我一直在评论,以便更容易地发现和理解我们需要做出的改变。 342 | ```rust 343 | #![feature(optin_builtin_traits, negative_impls)] // needed to implement `!Unpin` 344 | use std::pin::Pin; 345 | 346 | pub fn main() { 347 | let gen1 = GeneratorA::start(); 348 | let gen2 = GeneratorA::start(); 349 | // Before we pin the pointers, this is safe to do 350 | // std::mem::swap(&mut gen, &mut gen2); 351 | 352 | // constructing a `Pin::new()` on a type which does not implement `Unpin` is 353 | // unsafe. A value pinned to heap can be constructed while staying in safe 354 | // Rust so we can use that to avoid unsafe. You can also use crates like 355 | // `pin_utils` to pin to the stack safely, just remember that they use 356 | // unsafe under the hood so it's like using an already-reviewed unsafe 357 | // implementation. 358 | 359 | let mut pinned1 = Box::pin(gen1); 360 | let mut pinned2 = Box::pin(gen2); 361 | 362 | // Uncomment these if you think it's safe to pin the values to the stack instead 363 | // (it is in this case). Remember to comment out the two previous lines first. 364 | //let mut pinned1 = unsafe { Pin::new_unchecked(&mut gen1) }; 365 | //let mut pinned2 = unsafe { Pin::new_unchecked(&mut gen2) }; 366 | 367 | if let GeneratorState::Yielded(n) = pinned1.as_mut().resume() { 368 | println!("Gen1 got value {}", n); 369 | } 370 | 371 | if let GeneratorState::Yielded(n) = pinned2.as_mut().resume() { 372 | println!("Gen2 got value {}", n); 373 | }; 374 | 375 | // This won't work: 376 | // std::mem::swap(&mut gen, &mut gen2); 377 | // This will work but will just swap the pointers so nothing bad happens here: 378 | // std::mem::swap(&mut pinned1, &mut pinned2); 379 | 380 | let _ = pinned1.as_mut().resume(); 381 | let _ = pinned2.as_mut().resume(); 382 | } 383 | 384 | enum GeneratorState { 385 | Yielded(Y), 386 | Complete(R), 387 | } 388 | 389 | trait Generator { 390 | type Yield; 391 | type Return; 392 | fn resume(self: Pin<&mut Self>) -> GeneratorState; 393 | } 394 | 395 | enum GeneratorA { 396 | Enter, 397 | Yield1 { 398 | to_borrow: String, 399 | borrowed: *const String, 400 | }, 401 | Exit, 402 | } 403 | 404 | impl GeneratorA { 405 | fn start() -> Self { 406 | GeneratorA::Enter 407 | } 408 | } 409 | 410 | // This tells us that the underlying pointer is not safe to move after pinning. 411 | // In this case, only we as implementors "feel" this, however, if someone is 412 | // relying on our Pinned pointer this will prevent them from moving it. You need 413 | // to enable the feature flag ` #![feature(optin_builtin_traits)]` and use the 414 | // nightly compiler to implement `!Unpin`. Normally, you would use 415 | // `std::marker::PhantomPinned` to indicate that the struct is `!Unpin`. 416 | impl !Unpin for GeneratorA { } 417 | 418 | impl Generator for GeneratorA { 419 | type Yield = usize; 420 | type Return = (); 421 | fn resume(self: Pin<&mut Self>) -> GeneratorState { 422 | // lets us get ownership over current state 423 | let this = unsafe { self.get_unchecked_mut() }; 424 | match this { 425 | GeneratorA::Enter => { 426 | let to_borrow = String::from("Hello"); 427 | let borrowed = &to_borrow; 428 | let res = borrowed.len(); 429 | *this = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()}; 430 | 431 | // Trick to actually get a self reference. We can't reference 432 | // the `String` earlier since these references will point to the 433 | // location in this stack frame which will not be valid anymore 434 | // when this function returns. 435 | if let GeneratorA::Yield1 {to_borrow, borrowed} = this { 436 | *borrowed = to_borrow; 437 | } 438 | 439 | GeneratorState::Yielded(res) 440 | } 441 | 442 | GeneratorA::Yield1 {borrowed, ..} => { 443 | let borrowed: &String = unsafe {&**borrowed}; 444 | println!("{} world", borrowed); 445 | *this = GeneratorA::Exit; 446 | GeneratorState::Complete(()) 447 | } 448 | GeneratorA::Exit => panic!("Can't advance an exited generator!"), 449 | } 450 | } 451 | } 452 | ``` 453 | 454 | 现在,正如你所看到的,这个 API 的使用者必须: 455 | 1. 将值装箱,从而在堆上分配它 456 | 2. 使用`unafe`然后把值固定到栈上。 用户知道如果他们事后移动了这个值,那么他们在就违反了当他们使用unsafe时候做出的承诺,也就是一直持有. 457 | 458 | 希望在这之后,你会知道当你在一个异步函数中使用`yield`或者`await`关键词时会发生什么,以及如果我们想要安全地跨yield/await借用时。,为什么我们需要`Pin` 459 | -------------------------------------------------------------------------------- /src/8_finished_example.md: -------------------------------------------------------------------------------- 1 | ## 完整的例子 2 | 3 | ```rust 4 | fn main() { 5 | let start = Instant::now(); 6 | let reactor = Reactor::new(); 7 | 8 | let fut1 = async { 9 | let val = Task::new(reactor.clone(), 1, 1).await; 10 | println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32()); 11 | }; 12 | 13 | let fut2 = async { 14 | let val = Task::new(reactor.clone(), 2, 2).await; 15 | println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32()); 16 | }; 17 | 18 | let mainfut = async { 19 | fut1.await; 20 | fut2.await; 21 | }; 22 | 23 | block_on(mainfut); 24 | reactor.lock().map(|mut r| r.close()).unwrap(); 25 | } 26 | 27 | use std::{ 28 | future::Future, sync::{ mpsc::{channel, Sender}, Arc, Mutex, Condvar}, 29 | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem, pin::Pin, 30 | thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap 31 | }; 32 | // ============================= EXECUTOR ==================================== 33 | #[derive(Default)] 34 | struct Parker(Mutex, Condvar); 35 | 36 | impl Parker { 37 | fn park(&self) { 38 | let mut resumable = self.0.lock().unwrap(); 39 | while !*resumable { 40 | resumable = self.1.wait(resumable).unwrap(); 41 | } 42 | *resumable = false; 43 | } 44 | 45 | fn unpark(&self) { 46 | *self.0.lock().unwrap() = true; 47 | self.1.notify_one(); 48 | } 49 | } 50 | 51 | fn block_on(mut future: F) -> F::Output { 52 | let parker = Arc::new(Parker::default()); 53 | let mywaker = Arc::new(MyWaker { parker: parker.clone() }); 54 | let waker = mywaker_into_waker(Arc::into_raw(mywaker)); 55 | let mut cx = Context::from_waker(&waker); 56 | 57 | // SAFETY: we shadow `future` so it can't be accessed again. 58 | let mut future = unsafe { Pin::new_unchecked(&mut future) }; 59 | loop { 60 | match Future::poll(future.as_mut(), &mut cx) { 61 | Poll::Ready(val) => break val, 62 | Poll::Pending => parker.park(), 63 | }; 64 | } 65 | } 66 | // ====================== FUTURE IMPLEMENTATION ============================== 67 | #[derive(Clone)] 68 | struct MyWaker { 69 | parker: Arc, 70 | } 71 | 72 | #[derive(Clone)] 73 | pub struct Task { 74 | id: usize, 75 | reactor: Arc>>, 76 | data: u64, 77 | } 78 | 79 | fn mywaker_wake(s: &MyWaker) { 80 | let waker_arc = unsafe { Arc::from_raw(s) }; 81 | waker_arc.parker.unpark(); 82 | } 83 | 84 | fn mywaker_clone(s: &MyWaker) -> RawWaker { 85 | let arc = unsafe { Arc::from_raw(s) }; 86 | std::mem::forget(arc.clone()); // increase ref count 87 | RawWaker::new(Arc::into_raw(arc) as *const (), &VTABLE) 88 | } 89 | 90 | const VTABLE: RawWakerVTable = unsafe { 91 | RawWakerVTable::new( 92 | |s| mywaker_clone(&*(s as *const MyWaker)), // clone 93 | |s| mywaker_wake(&*(s as *const MyWaker)), // wake 94 | |s| mywaker_wake(*(s as *const &MyWaker)), // wake by ref 95 | |s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount 96 | ) 97 | }; 98 | 99 | fn mywaker_into_waker(s: *const MyWaker) -> Waker { 100 | let raw_waker = RawWaker::new(s as *const (), &VTABLE); 101 | unsafe { Waker::from_raw(raw_waker) } 102 | } 103 | 104 | impl Task { 105 | fn new(reactor: Arc>>, data: u64, id: usize) -> Self { 106 | Task { id, reactor, data } 107 | } 108 | } 109 | 110 | impl Future for Task { 111 | type Output = usize; 112 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 113 | let mut r = self.reactor.lock().unwrap(); 114 | if r.is_ready(self.id) { 115 | *r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished; 116 | Poll::Ready(self.id) 117 | } else if r.tasks.contains_key(&self.id) { 118 | r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone())); 119 | Poll::Pending 120 | } else { 121 | r.register(self.data, cx.waker().clone(), self.id); 122 | Poll::Pending 123 | } 124 | } 125 | } 126 | // =============================== REACTOR =================================== 127 | enum TaskState { 128 | Ready, 129 | NotReady(Waker), 130 | Finished, 131 | } 132 | struct Reactor { 133 | dispatcher: Sender, 134 | handle: Option>, 135 | tasks: HashMap, 136 | } 137 | 138 | #[derive(Debug)] 139 | enum Event { 140 | Close, 141 | Timeout(u64, usize), 142 | } 143 | 144 | impl Reactor { 145 | fn new() -> Arc>> { 146 | let (tx, rx) = channel::(); 147 | let reactor = Arc::new(Mutex::new(Box::new(Reactor { 148 | dispatcher: tx, 149 | handle: None, 150 | tasks: HashMap::new(), 151 | }))); 152 | 153 | let reactor_clone = Arc::downgrade(&reactor); 154 | let handle = thread::spawn(move || { 155 | let mut handles = vec![]; 156 | for event in rx { 157 | let reactor = reactor_clone.clone(); 158 | match event { 159 | Event::Close => break, 160 | Event::Timeout(duration, id) => { 161 | let event_handle = thread::spawn(move || { 162 | thread::sleep(Duration::from_secs(duration)); 163 | let reactor = reactor.upgrade().unwrap(); 164 | reactor.lock().map(|mut r| r.wake(id)).unwrap(); 165 | }); 166 | handles.push(event_handle); 167 | } 168 | } 169 | } 170 | handles.into_iter().for_each(|handle| handle.join().unwrap()); 171 | }); 172 | reactor.lock().map(|mut r| r.handle = Some(handle)).unwrap(); 173 | reactor 174 | } 175 | 176 | fn wake(&mut self, id: usize) { 177 | let state = self.tasks.get_mut(&id).unwrap(); 178 | match mem::replace(state, TaskState::Ready) { 179 | TaskState::NotReady(waker) => waker.wake(), 180 | TaskState::Finished => panic!("Called 'wake' twice on task: {}", id), 181 | _ => unreachable!() 182 | } 183 | } 184 | 185 | fn register(&mut self, duration: u64, waker: Waker, id: usize) { 186 | if self.tasks.insert(id, TaskState::NotReady(waker)).is_some() { 187 | panic!("Tried to insert a task with id: '{}', twice!", id); 188 | } 189 | self.dispatcher.send(Event::Timeout(duration, id)).unwrap(); 190 | } 191 | 192 | fn close(&mut self) { 193 | self.dispatcher.send(Event::Close).unwrap(); 194 | } 195 | 196 | fn is_ready(&self, id: usize) -> bool { 197 | self.tasks.get(&id).map(|state| match state { 198 | TaskState::Ready => true, 199 | _ => false, 200 | }).unwrap_or(false) 201 | } 202 | } 203 | 204 | impl Drop for Reactor { 205 | fn drop(&mut self) { 206 | self.handle.take().map(|h| h.join().unwrap()).unwrap(); 207 | } 208 | } 209 | 210 | ``` 211 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [引言](./introduction.md) 4 | 5 | - [背景资料](./0_background_information.md) 6 | - [Rust中的Futures](./1_futures_in_rust.md) 7 | - [唤醒器和上下文](./2_waker_context.md) 8 | - [生成器和async/await](./3_generators_async_await.md) 9 | - [Pin](./4_pin.md) 10 | - [实现Futures--主要例子](./6_future_example.md) 11 | - [完整的例子](./8_finished_example.md) 12 | 13 | [结论和练习](./conclusion.md) 14 | -------------------------------------------------------------------------------- /src/assets/reactorexecutor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/src/assets/reactorexecutor.png -------------------------------------------------------------------------------- /src/assets/swap_problem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkbai/200-Rust-Futures/f8117948ce5314c599381fa8f2104777e7a2e5be/src/assets/swap_problem.jpg -------------------------------------------------------------------------------- /src/conclusion.md: -------------------------------------------------------------------------------- 1 | ## 结论和练习 2 | 3 | 我们的实现采取了一些明显的捷径,可以使用一些改进。 实际上,深入研究代码并自己尝试一些事情是一个很好的学习方式。 如果你想探索更多,这里有一些很好的练习 4 | 5 | ### 编码park 6 | 7 | 使用 Thread: : park 和 Thread: : unpark 的大问题是用户可以从自己的代码访问这些相同的方法。 尝试使用另一种方法来挂起线程并根据我们的命令再次唤醒它。 一些提示: 8 | 9 | - [std::sync::CondVar](https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html) 10 | - [crossbeam::sync::Parker](https://github.com/crossbeam-rs/crossbeam) 11 | 12 | 13 | ### 编码传递reactor 14 | 15 | 避免包装整个Reactor in a mutex and pass it around 在互斥对象中传递 16 | 17 | 首先,保护整个reactor并传递它是过分的。 我们只对同步它包含的部分信息感兴趣。 尝试将其重构出来,只同步访问真正需要的内容。 18 | 19 | 我建议您看看[async_std驱动程序](https://github.com/async-rs/async-std/blob/master/src/net/driver/mod.rs)是如何实现的,以及[tokio调度程序](https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/basic_scheduler.rs)是如何实现的,以获得一些灵感。 20 | 21 | - 你想使用Arc来传递这些信息的引用? 22 | - 你是否想创建一个全局的reactor,这样他可以随时随地被访问? 23 | 24 | 25 | ### 创建一个更好的执行器 26 | 27 | 现在我们只支持一个一个Future运行. 大多数运行时都有一个spawn函数,让你能够启动一个future,然后await它. 这样你就可以同时运行多个Future. 28 | 29 | 正如我在本书开头所建议的那样,访问[stjepan 的博客系列](https://stjepang.github.io/2020/01/31/build-your-own-executor.html),了解如何实现你自己的执行者,是我将要开始的地方,并从那里开始。 30 | 31 | 32 | ### 进一步阅读 33 | 34 | - [The official Asyc book](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html) 35 | 36 | - [ The async_std book](https://book.async.rs/) 37 | 38 | - [Aron Turon: Designing futures for Rust](https://aturon.github.io/blog/2016/09/07/futures-design/) 39 | 40 | - [Steve Klabnik's presentation: Rust's journey to Async/Await](https://www.infoq.com/presentations/rust-2019/) 41 | 42 | - [The Tokio Blog](https://tokio.rs/blog/2019-10-scheduler/) 43 | 44 | - [Stjepan's blog with a series where he implements an Executor](https://stjepang.github.io/) 45 | 46 | - [Jon Gjengset's video on The Why, What and How of Pinning in Rust](https://youtu.be/DkMwYxfSYNQ) 有中英文字幕的[B站链接](https://www.bilibili.com/video/BV1tK411L7s5/) 47 | 48 | - [Withoutboats blog series about async/await](https://boats.gitlab.io/blog/post/2018-01-25-async-i-self-referential-structs/) 49 | 50 | -------------------------------------------------------------------------------- /src/introduction.md: -------------------------------------------------------------------------------- 1 | ## 引言 2 | 本书[github地址](https://github.com/nkbai/books-futures-explained) 欢迎star,以及提出任何问题 3 | 4 | [原书地址](https://cfsamson.github.io/books-futures-explained/) 5 | 6 | 这本书的目的是用一个例子驱动的方法来解释Rust中的Futures,探索为什么他们被设计成这样,以及他们如何工作。 我们还将介绍在编程中处理并发性时的一些替代方案。 7 | 8 | 理解这本书中描述的细节, 不需要使用Rust中的 futures或async/await。 这是为那些好奇的人准备的,他们想知道这一切是如何运作的。 9 | 10 | 11 | ### 这本书涵盖的内容 12 | 13 | 这本书将试图解释你可能想知道的一切,包括不同类型的执行器(executor)和运行时(runtime)的主题。 我们将在本书中实现一个非常简单的运行时,介绍一些概念,但这已经足够开始了。 14 | 15 | 16 | [Stjepan Glavina](https://github.com/stjepang) 发表了一系列关于异步运行时和执行器的优秀文章,如果谣言属实,那么在不久的将来他会发表更多的文章。 17 | 18 | 你应该做的是先读这本书,然后继续阅读 [stejpang 的文章](https://stjepang.github.io/),了解更多关于运行时以及它们是如何工作的,特别是: 19 | 1. [构建自的block_on](https://stjepang.github.io/2020/01/25/build-your-own-block-on.html) 20 | 2. [构建自己的executor](https://stjepang.github.io/2020/01/31/build-your-own-executor.html) 21 | 22 | 我将自己限制在一个200行的主示例(因此才有了这个标题)中,以限制范围并介绍一个可以轻松进一步研究的示例。 23 | 24 | 然而,有很多东西需要消化,这并不像我所说的那么简单,但是我们会一步一步来,所以喝杯茶,放松一下。 25 | 26 | > 这本书是在开放的,并欢迎贡献。 你可以在[这里找到这本书](https://github.com/cfsamson/books-futures-explained)。 最后的例子,你可以[克隆或复制](https://github.com/cfsamson/examples-futures)可以在这里找到。 任何建议或改进可以归档为一个公关或在问题追踪的书。 27 | > 一如既往,我们欢迎各种各样的反馈。 28 | 29 | 30 | ### 阅读练习和进一步阅读 31 | 32 | 在[最后一章](#结论和练习))中,我冒昧地提出了一些小练习,如果你想进一步探索的话。 33 | 34 | 这本书也是我在 Rust 中写的关于并发编程的第四本书。 如果你喜欢它,你可能也想看看其他的: 35 | 36 | - [Green Threads Explained in 200 lines of rust](https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/) 37 | - [The Node Experiment - Exploring Async Basics with Rust 节点实验——用Rust探索异步基础](https://cfsamson.github.io/book-exploring-async-basics/) 38 | - [Epoll, Kqueue and IOCP Explained with Rust 用 Rust 解释 Epoll,Kqueue 和 IOCP](https://cfsamsonbooks.gitbook.io/epoll-kqueue-iocp-explained/) 39 | 40 | ### 感谢 41 | 42 | 我想借此机会感谢 mio,tokio,async std,Futures,libc,crossbeam 背后的人们,他们支撑着这个异步生态系统,却很少在我眼中得到足够的赞扬。 43 | 44 | 特别感谢 jonhoo,他对我这本书的初稿给予了一些有价值的反馈。 他还没有读完最终的成品,但是我们应该向他表示感谢。 45 | 46 | -------------------------------------------------------------------------------- /theme/highlight.css: -------------------------------------------------------------------------------- 1 | /* Base16 Atelier Dune Light - Theme */ 2 | /* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ 3 | /* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ 4 | 5 | /* Atelier-Dune Comment */ 6 | .hljs-comment { 7 | color: rgb(160, 160, 160);; 8 | font-style: italic; 9 | } 10 | .hljs-quote { 11 | color: #AAA; 12 | } 13 | 14 | /* Atelier-Dune Red */ 15 | .hljs-variable, 16 | .hljs-template-variable, 17 | .hljs-attribute, 18 | .hljs-tag, 19 | .hljs-name, 20 | .hljs-regexp, 21 | .hljs-link, 22 | .hljs-name, 23 | .hljs-selector-id, 24 | .hljs-selector-class { 25 | color: #d73737; 26 | } 27 | 28 | /* Atelier-Dune Orange */ 29 | .hljs-number, 30 | .hljs-meta, 31 | .hljs-built_in, 32 | .hljs-builtin-name, 33 | .hljs-literal, 34 | .hljs-type, 35 | .hljs-params { 36 | color: #b65611; 37 | } 38 | 39 | /* Atelier-Dune Green */ 40 | .hljs-string, 41 | .hljs-symbol, 42 | .hljs-bullet { 43 | color: #60ac39; 44 | } 45 | 46 | /* Atelier-Dune Blue */ 47 | .hljs-title, 48 | .hljs-section { 49 | color: #6684e1; 50 | } 51 | 52 | /* Atelier-Dune Purple */ 53 | .hljs-keyword, 54 | .hljs-selector-tag { 55 | color: #b854d4; 56 | } 57 | 58 | .hljs { 59 | display: block; 60 | overflow-x: auto; 61 | background: #f1f1f1; 62 | color: #6e6b5e; 63 | padding: 0.5em; 64 | } 65 | 66 | .hljs-emphasis { 67 | font-style: italic; 68 | } 69 | 70 | .hljs-strong { 71 | font-weight: bold; 72 | } 73 | 74 | .hljs-addition { 75 | color: #22863a; 76 | background-color: #f0fff4; 77 | } 78 | 79 | .hljs-deletion { 80 | color: #b31d28; 81 | background-color: #ffeef0; 82 | } 83 | --------------------------------------------------------------------------------