442 lines
24 KiB
HTML
442 lines
24 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js light">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>Futures in Rust - Futures Explained in 200 Lines of Rust</title>
|
|
|
|
|
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff" />
|
|
|
|
<link rel="shortcut icon" href="favicon.png">
|
|
<link rel="stylesheet" href="css/variables.css">
|
|
<link rel="stylesheet" href="css/general.css">
|
|
<link rel="stylesheet" href="css/chrome.css">
|
|
<link rel="stylesheet" href="css/print.css" media="print">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="highlight.css">
|
|
<link rel="stylesheet" href="tomorrow-night.css">
|
|
<link rel="stylesheet" href="ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
|
|
|
|
|
</head>
|
|
<body>
|
|
<!-- Provide site root to javascript -->
|
|
<script type="text/javascript">
|
|
var path_to_root = "";
|
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
|
</script>
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script type="text/javascript">
|
|
try {
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
}
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
}
|
|
} catch (e) { }
|
|
</script>
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
<script type="text/javascript">
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
var html = document.querySelector('html');
|
|
html.classList.remove('no-js')
|
|
html.classList.remove('light')
|
|
html.classList.add(theme);
|
|
html.classList.add('js');
|
|
</script>
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script type="text/javascript">
|
|
var html = document.querySelector('html');
|
|
var sidebar = 'hidden';
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
}
|
|
html.classList.remove('sidebar-visible');
|
|
html.classList.add("sidebar-" + sidebar);
|
|
</script>
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
|
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html" class="active"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
|
</div>
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
|
</nav>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
<div class="page">
|
|
|
|
<div id="menu-bar" class="menu-bar">
|
|
<div id="menu-bar-sticky-container">
|
|
<div class="left-buttons">
|
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
<i class="fa fa-bars"></i>
|
|
</button>
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
</ul>
|
|
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
|
|
|
<div class="right-buttons">
|
|
<a href="print.html" title="Print this book" aria-label="Print this book">
|
|
<i id="print-button" class="fa fa-print"></i>
|
|
</a>
|
|
|
|
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
|
<i id="git-repository-button" class="fa fa-github"></i>
|
|
</a>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
<script type="text/javascript">
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
});
|
|
</script>
|
|
|
|
<div id="content" class="content">
|
|
<main>
|
|
<h1><a class="header" href="#futures-in-rust" id="futures-in-rust">Futures in Rust</a></h1>
|
|
<blockquote>
|
|
<p><strong>Overview:</strong></p>
|
|
<ul>
|
|
<li>Get a high level introduction to concurrency in Rust</li>
|
|
<li>Know what Rust provides and not when working with async code</li>
|
|
<li>Get to know why we need a runtime-library in Rust</li>
|
|
<li>Understand the difference between "leaf-future" and a "non-leaf-future"</li>
|
|
<li>Get insight on how to handle CPU intensive tasks</li>
|
|
</ul>
|
|
</blockquote>
|
|
<h2><a class="header" href="#futures" id="futures">Futures</a></h2>
|
|
<p>So what is a future?</p>
|
|
<p>A future is a representation of some operation which will complete in the
|
|
future.</p>
|
|
<p>Async in Rust uses a <code>Poll</code> based approach, in which an asynchronous task will
|
|
have three phases.</p>
|
|
<ol>
|
|
<li><strong>The Poll phase.</strong> A Future is polled which result in the task progressing until
|
|
a point where it can no longer make progress. We often refer to the part of the
|
|
runtime which polls a Future as an executor.</li>
|
|
<li><strong>The Wait phase.</strong> An event source, most often referred to as a reactor,
|
|
registers that a Future is waiting for an event to happen and makes sure that it
|
|
will wake the Future when that event is ready.</li>
|
|
<li><strong>The Wake phase.</strong> The event happens and the Future is woken up. It's now up
|
|
to the executor which polled the Future in step 1 to schedule the future to be
|
|
polled again and make further progress until it completes or reaches a new point
|
|
where it can't make further progress and the cycle repeats.</li>
|
|
</ol>
|
|
<p>Now, when we talk about futures I find it useful to make a distinction between
|
|
<strong>non-leaf</strong> futures and <strong>leaf</strong> futures early on because in practice they're
|
|
pretty different from one another.</p>
|
|
<h3><a class="header" href="#leaf-futures" id="leaf-futures">Leaf futures</a></h3>
|
|
<p>Runtimes create <em>leaf futures</em> which represents a resource like a socket.</p>
|
|
<pre><code class="language-rust ignore noplaypen">// stream is a **leaf-future**
|
|
let mut stream = tokio::net::TcpStream::connect("127.0.0.1:3000");
|
|
</code></pre>
|
|
<p>Operations on these resources, like a <code>Read</code> on a socket, will be non-blocking
|
|
and return a future which we call a leaf future since it's the future which
|
|
we're actually waiting on.</p>
|
|
<p>It's unlikely that you'll implement a leaf future yourself unless you're writing
|
|
a runtime, but we'll go through how they're constructed in this book as well.</p>
|
|
<p>It's also unlikely that you'll pass a leaf-future to a runtime and run it to
|
|
completion alone as you'll understand by reading the next paragraph.</p>
|
|
<h3><a class="header" href="#non-leaf-futures" id="non-leaf-futures">Non-leaf-futures</a></h3>
|
|
<p>Non-leaf-futures is the kind of futures we as <em>users</em> of a runtime write
|
|
ourselves using the <code>async</code> keyword to create a <strong>task</strong> which can be run on the
|
|
executor.</p>
|
|
<p>The bulk of an async program will consist of non-leaf-futures, which are a kind
|
|
of pause-able computation. This is an important distinction since these futures represents a <em>set of operations</em>. Often, such a task will <code>await</code> a leaf future
|
|
as one of many operations to complete the task.</p>
|
|
<pre><code class="language-rust ignore noplaypen edition2018">// Non-leaf-future
|
|
let non_leaf = async {
|
|
let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap();// <- yield
|
|
println!("connected!");
|
|
let result = stream.write(b"hello world\n").await; // <- yield
|
|
println!("message sent!");
|
|
...
|
|
};
|
|
</code></pre>
|
|
<p>The key to these tasks is that they're able to yield control to the runtime's
|
|
scheduler and then resume execution again where it left off at a later point.</p>
|
|
<p>In contrast to leaf futures, these kind of futures does not themselves represent
|
|
an I/O resource. When we poll these futures we either run some code or we yield
|
|
to the scheduler while waiting for some resource to signal us that it's ready so
|
|
we can resume where we left off.</p>
|
|
<h2><a class="header" href="#runtimes" id="runtimes">Runtimes</a></h2>
|
|
<p>Languages like C#, JavaScript, Java, GO and many others comes with a runtime
|
|
for handling concurrency. So if you come from one of those languages this will
|
|
seem a bit strange to you.</p>
|
|
<p>Rust is different from these languages in the sense that Rust doesn't come with
|
|
a runtime for handling concurrency, so you need to use a library which provide
|
|
this for you.</p>
|
|
<p>Quite a bit of complexity attributed to Futures is actually complexity rooted
|
|
in runtimes. Creating an efficient runtime is hard.</p>
|
|
<p>Learning how to use one correctly requires quite a bit of effort as well, but
|
|
you'll see that there are several similarities between these kind of runtimes so
|
|
learning one makes learning the next much easier.</p>
|
|
<p>The difference between Rust and other languages is that you have to make an
|
|
active choice when it comes to picking a runtime. Most often, in other languages
|
|
you'll just use the one provided for you.</p>
|
|
<p><strong>An async runtime can be divided into two parts:</strong></p>
|
|
<ol>
|
|
<li>The Executor</li>
|
|
<li>The Reactor</li>
|
|
</ol>
|
|
<p>When Rusts Futures were designed there was a desire to separate the job of
|
|
notifying a <code>Future</code> that it can do more work, and actually doing the work
|
|
on the <code>Future</code>.</p>
|
|
<p>You can think of the former as the reactor's job, and the latter as the
|
|
executors job. These two parts of a runtime interact with each other using the <code>Waker</code> type.</p>
|
|
<p>The two most popular runtimes for Futures as of writing this is:</p>
|
|
<ul>
|
|
<li><a href="https://github.com/async-rs/async-std">async-std</a></li>
|
|
<li><a href="https://github.com/tokio-rs/tokio">Tokio</a></li>
|
|
</ul>
|
|
<h3><a class="header" href="#what-rusts-standard-library-takes-care-of" id="what-rusts-standard-library-takes-care-of">What Rust's standard library takes care of</a></h3>
|
|
<ol>
|
|
<li>A common interface representing an operation which will be completed in the
|
|
future through the <code>Future</code> trait.</li>
|
|
<li>An ergonomic way of creating tasks which can be suspended and resumed through
|
|
the <code>async</code> and <code>await</code> keywords.</li>
|
|
<li>A defined interface wake up a suspended task through the <code>Waker</code> type.</li>
|
|
</ol>
|
|
<p>That's really what Rusts standard library does. As you see there is no definition
|
|
of non-blocking I/O, how these tasks are created or how they're run.</p>
|
|
<h2><a class="header" href="#io-vs-cpu-intensive-tasks" id="io-vs-cpu-intensive-tasks">I/O vs CPU intensive tasks</a></h2>
|
|
<p>As you know now, what you normally write are called non-leaf futures. Let's
|
|
take a look at this async block using pseudo-rust as example:</p>
|
|
<pre><code class="language-rust ignore">let non_leaf = async {
|
|
let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap(); // <-- yield
|
|
|
|
// request a large dataset
|
|
let result = stream.write(get_dataset_request).await.unwrap(); // <-- yield
|
|
|
|
// wait for the dataset
|
|
let mut response = vec![];
|
|
stream.read(&mut response).await.unwrap(); // <-- yield
|
|
|
|
// do some CPU-intensive analysis on the dataset
|
|
let report = analyzer::analyze_data(response).unwrap();
|
|
|
|
// send the results back
|
|
stream.write(report).await.unwrap(); // <-- yield
|
|
};
|
|
</code></pre>
|
|
<p>Now, as you'll see when we go through how Futures work, the code we write between
|
|
the yield points are run on the same thread as our executor.</p>
|
|
<p>That means that while our <code>analyzer</code> is working on the dataset, the executor
|
|
is busy doing calculations instead of handling new requests.</p>
|
|
<p>Fortunately there are a few ways to handle this, and it's not difficult, but it's
|
|
something you must be aware of:</p>
|
|
<ol>
|
|
<li>
|
|
<p>We could create a new leaf future which sends our task to another thread and
|
|
resolves when the task is finished. We could <code>await</code> this leaf-future like any
|
|
other future.</p>
|
|
</li>
|
|
<li>
|
|
<p>The runtime could have some kind of supervisor that monitors how much time
|
|
different tasks take, and move the executor itself to a different thread so it can
|
|
continue to run even though our <code>analyzer</code> task is blocking the original executor thread.</p>
|
|
</li>
|
|
<li>
|
|
<p>You can create a reactor yourself which is compatible with the runtime which
|
|
does the analysis any way you see fit, and returns a Future which can be awaited.</p>
|
|
</li>
|
|
</ol>
|
|
<p>Now, #1 is the usual way of handling this, but some executors implement #2 as well.
|
|
The problem with #2 is that if you switch runtime you need to make sure that it
|
|
supports this kind of supervision as well or else you will end up blocking the
|
|
executor.</p>
|
|
<p>#3 is more of theoretical importance, normally you'd be happy by sending the task
|
|
to the thread-pool most runtimes provide.</p>
|
|
<p>Most executors have a way to accomplish #1 using methods like <code>spawn_blocking</code>.</p>
|
|
<p>These methods send the task to a thread-pool created by the runtime where you
|
|
can either perform CPU-intensive tasks or "blocking" tasks which is not supported
|
|
by the runtime.</p>
|
|
<p>Now, armed with this knowledge you are already on a good way for understanding
|
|
Futures, but we're not gonna stop yet, there is lots of details to cover.</p>
|
|
<p>Take a break or a cup of coffe and get ready as we go for a deep dive in the next chapters.</p>
|
|
<h2><a class="header" href="#bonus-section" id="bonus-section">Bonus section</a></h2>
|
|
<p>If you find the concepts of concurrency and async programming confusing in
|
|
general, I know where you're coming from and I have written some resources to
|
|
try to give a high level overview that will make it easier to learn Rusts
|
|
Futures afterwards:</p>
|
|
<ul>
|
|
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/1_concurrent_vs_parallel.html">Async Basics - The difference between concurrency and parallelism</a></li>
|
|
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/2_async_history.html">Async Basics - Async history</a></li>
|
|
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/5_strategies_for_handling_io.html">Async Basics - Strategies for handling I/O</a></li>
|
|
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/6_epoll_kqueue_iocp.html">Async Basics - Epoll, Kqueue and IOCP</a></li>
|
|
</ul>
|
|
<p>Learning these concepts by studying futures is making it much harder than
|
|
it needs to be, so go on and read these chapters if you feel a bit unsure.</p>
|
|
<p>I'll be right here when you're back.</p>
|
|
<p>However, if you feel that you have the basics covered, then let's get moving!</p>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
<a rel="prev" href="0_background_information.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
|
|
|
|
<a rel="next" href="2_waker_context.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
|
|
<a href="0_background_information.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
|
|
|
|
<a href="2_waker_context.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
|
|
<!-- Livereload script (if served using the cli tool) -->
|
|
<script type="text/javascript">
|
|
var socket = new WebSocket("ws://localhost:3001");
|
|
socket.onmessage = function (event) {
|
|
if (event.data === "reload") {
|
|
socket.close();
|
|
location.reload(true); // force reload from server (not from cache)
|
|
}
|
|
};
|
|
|
|
window.onbeforeunload = function() {
|
|
socket.close();
|
|
}
|
|
</script>
|
|
|
|
|
|
|
|
<!-- Google Analytics Tag -->
|
|
<script type="text/javascript">
|
|
var localAddrs = ["localhost", "127.0.0.1", ""];
|
|
|
|
// make sure we don't activate google analytics if the developer is
|
|
// inspecting the book locally...
|
|
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
|
|
|
ga('create', 'UA-157536992-1', 'auto');
|
|
ga('send', 'pageview');
|
|
}
|
|
</script>
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
window.playpen_line_numbers = true;
|
|
</script>
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
window.playpen_copyable = true;
|
|
</script>
|
|
|
|
|
|
|
|
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
|
|
|
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
|
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
|
|
|
|
|
</body>
|
|
</html>
|