518 lines
24 KiB
HTML
518 lines
24 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>Pin - Futures Explained in 200 Lines of Rust</title>
|
|
|
|
|
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|
<meta name="description" content="This book aims to explain Futures in Rust using an example driven approach.">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff" />
|
|
|
|
<link rel="shortcut icon" href="favicon.png">
|
|
<link rel="stylesheet" href="css/variables.css">
|
|
<link rel="stylesheet" href="css/general.css">
|
|
<link rel="stylesheet" href="css/chrome.css">
|
|
<link rel="stylesheet" href="css/print.css" media="print">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="highlight.css">
|
|
<link rel="stylesheet" href="tomorrow-night.css">
|
|
<link rel="stylesheet" href="ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
|
|
|
|
|
</head>
|
|
<body class="light">
|
|
<!-- Provide site root to javascript -->
|
|
<script type="text/javascript">
|
|
var path_to_root = "";
|
|
var default_theme = "light";
|
|
</script>
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script type="text/javascript">
|
|
try {
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
}
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
}
|
|
} catch (e) { }
|
|
</script>
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
<script type="text/javascript">
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
document.body.className = theme;
|
|
document.querySelector('html').className = theme + ' js';
|
|
</script>
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script type="text/javascript">
|
|
var html = document.querySelector('html');
|
|
var sidebar = 'hidden';
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
}
|
|
html.classList.remove('sidebar-visible');
|
|
html.classList.add("sidebar-" + sidebar);
|
|
</script>
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<div class="sidebar-scrollbox">
|
|
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="1_background_information.html"><strong aria-hidden="true">1.</strong> Some background information</a></li><li><a href="2_trait_objects.html"><strong aria-hidden="true">2.</strong> Trait objects and fat pointers</a></li><li><a href="3_generators_pin.html"><strong aria-hidden="true">3.</strong> Generators</a></li><li><a href="4_pin.html" class="active"><strong aria-hidden="true">4.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">5.</strong> Futures - our main example</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">6.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
|
</div>
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
|
</nav>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
<div class="page">
|
|
|
|
<div id="menu-bar" class="menu-bar">
|
|
<div id="menu-bar-sticky-container">
|
|
<div class="left-buttons">
|
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
<i class="fa fa-bars"></i>
|
|
</button>
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
</ul>
|
|
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<h1 class="menu-title">Futures Explained in 200 Lines of Rust</h1>
|
|
|
|
<div class="right-buttons">
|
|
<a href="print.html" title="Print this book" aria-label="Print this book">
|
|
<i id="print-button" class="fa fa-print"></i>
|
|
</a>
|
|
|
|
<a href="https://github.com/cfsamson/books-futures-explained" title="Git repository" aria-label="Git repository">
|
|
<i id="git-repository-button" class="fa fa-github"></i>
|
|
</a>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
<script type="text/javascript">
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
});
|
|
</script>
|
|
|
|
<div id="content" class="content">
|
|
<main>
|
|
<h1><a class="header" href="#pin" id="pin">Pin</a></h1>
|
|
<blockquote>
|
|
<p><strong>Relevant for</strong></p>
|
|
<ol>
|
|
<li>Understanding <code>Generators</code> and <code>Futures</code></li>
|
|
<li>Knowing how to use <code>Pin</code> is required when implementing your own <code>Future</code></li>
|
|
<li>Understanding how to make self-referential types safe to use in Rust</li>
|
|
<li>Learning how borrowing across <code>await</code> points is accomplished</li>
|
|
</ol>
|
|
<p><code>Pin</code> was suggested in <a href="https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md">RFC#2349</a></p>
|
|
</blockquote>
|
|
<p>We already got a brief introduction of <code>Pin</code> in the previous chapters, so we'll
|
|
start off without any further introduction.</p>
|
|
<p>Let's jump strait to some definitions and then create 10 rules to remember when
|
|
we work with <code>Pin</code>.</p>
|
|
<h2><a class="header" href="#definitions" id="definitions">Definitions</a></h2>
|
|
<p>Pin consists of the <code>Pin</code> type and the <code>Unpin</code> marker. Pin's purpose in life is
|
|
to govern the rules that need to apply for types which implement <code>!Unpin</code>.</p>
|
|
<p>Pin is only relevant for pointers. A reference to an object is a pointer.</p>
|
|
<p>Yep, you're right, that's double negation right there. <code>!Unpin</code> means
|
|
"not-un-pin".</p>
|
|
<p><em>This naming scheme is Rust deliberately testing if you're too tired to safely implement a type with this marker. If you're starting to get confused by
|
|
<code>!Unpin</code> it's a good sign that it's time to lay down the work and start over
|
|
tomorrow with a fresh mind.</em></p>
|
|
<blockquote>
|
|
<p>On a more serious note, I feel obliged to mention that there are valid reasons for the names
|
|
that were chosen. If you want to you can read a bit of the discussion from the
|
|
<a href="https://internals.rust-lang.org/t/naming-pin-anchor-move/6864/12">internals thread</a>. One of the best takeaways from there in my eyes
|
|
is this quote from <code>tmandry</code>:</p>
|
|
<p><em>Think of taking a thumbtack out of a cork board so you can tweak how a flyer looks. For Unpin types, this unpinning is directly supported by the type; you can do this implicitly. You can even swap out the object with another before you put the pin back. For other types, you must be much more careful.</em></p>
|
|
</blockquote>
|
|
<p>For the next paragraph we'll rename these markers to:</p>
|
|
<blockquote>
|
|
<p><code>!Unpin</code> = <code>MustStay</code> and <code>Unpin</code> = <code>CanMove</code></p>
|
|
</blockquote>
|
|
<p>It just makes it much easier to talk about them.</p>
|
|
<h2><a class="header" href="#rules-to-remember" id="rules-to-remember">Rules to remember</a></h2>
|
|
<ol>
|
|
<li>
|
|
<p>If <code>T: CanMove</code> (which is the default), then <code>Pin<'a, T></code> is entirely equivalent to <code>&'a mut T</code>. in other words: <code>CanMove</code> means it's OK for this type to be moved even when pinned, so <code>Pin</code> will have no effect on such a type.</p>
|
|
</li>
|
|
<li>
|
|
<p>Getting a <code>&mut T</code> to a pinned pointer requires unsafe if <code>T: MustStay</code>. In other words: requiring a pinned pointer to a type which is <code>MustStay</code> prevents the <em>user</em> of that API from moving that value unless it choses to write <code>unsafe</code> code.</p>
|
|
</li>
|
|
<li>
|
|
<p>Pinning does nothing special with memory allocation like putting it into some "read only" memory or anything fancy. It only tells the compiler that some operations on this value should be forbidden.</p>
|
|
</li>
|
|
<li>
|
|
<p>Most standard library types implement <code>CanMove</code>. The same goes for most
|
|
"normal" types you encounter in Rust. <code>Futures</code> and <code>Generators</code> are two
|
|
exceptions.</p>
|
|
</li>
|
|
<li>
|
|
<p>The main use case for <code>Pin</code> is to allow self referential types, the whole
|
|
justification for stabilizing them was to allow that. There are still corner
|
|
cases in the API which are being explored.</p>
|
|
</li>
|
|
<li>
|
|
<p>The implementation behind objects that are <code>MustStay</code> is most likely unsafe.
|
|
Moving such a type can cause the universe to crash. As of the time of writing
|
|
this book, creating and reading fields of a self referential struct still requires <code>unsafe</code>.</p>
|
|
</li>
|
|
<li>
|
|
<p>You can add a <code>MustStay</code> bound on a type on nightly with a feature flag, or
|
|
by adding <code>std::marker::PhantomPinned</code> to your type on stable.</p>
|
|
</li>
|
|
<li>
|
|
<p>You can either pin a value to memory on the stack or on the heap.</p>
|
|
</li>
|
|
<li>
|
|
<p>Pinning a <code>MustStay</code> pointer to the stack requires <code>unsafe</code></p>
|
|
</li>
|
|
<li>
|
|
<p>Pinning a <code>MustStay</code> pointer to the heap does not require <code>unsafe</code>. There is a shortcut for doing this using <code>Box::pin</code>.</p>
|
|
</li>
|
|
</ol>
|
|
<blockquote>
|
|
<p>Unsafe code does not mean it's literally "unsafe", it only relieves the
|
|
guarantees you normally get from the compiler. An <code>unsafe</code> implementation can
|
|
be perfectly safe to do, but you have no safety net.</p>
|
|
</blockquote>
|
|
<p>Let's take a look at an example:</p>
|
|
<pre><pre class="playpen"><code class="language-rust editable">use std::pin::Pin;
|
|
|
|
fn main() {
|
|
let mut test1 = Test::new("test1");
|
|
test1.init();
|
|
let mut test2 = Test::new("test2");
|
|
test2.init();
|
|
|
|
println!("a: {}, b: {}", test1.a(), test1.b());
|
|
std::mem::swap(&mut test1, &mut test2); // try commenting out this line
|
|
println!("a: {}, b: {}", test2.a(), test2.b());
|
|
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Test {
|
|
a: String,
|
|
b: *const String,
|
|
}
|
|
|
|
impl Test {
|
|
fn new(txt: &str) -> Self {
|
|
let a = String::from(txt);
|
|
Test {
|
|
a,
|
|
b: std::ptr::null(),
|
|
}
|
|
}
|
|
|
|
fn init(&mut self) {
|
|
let self_ref: *const String = &self.a;
|
|
self.b = self_ref;
|
|
}
|
|
|
|
fn a(&self) -> &str {
|
|
&self.a
|
|
}
|
|
|
|
fn b(&self) -> &String {
|
|
unsafe {&*(self.b)}
|
|
}
|
|
}
|
|
</code></pre></pre>
|
|
<p>Let's walk through this example since we'll be using it the rest of this chapter.</p>
|
|
<p>We have a self-referential struct <code>Test</code>. <code>Test</code> needs an <code>init</code> method to be
|
|
created which is strange but we'll need that to keep this example as short as
|
|
possible.</p>
|
|
<p><code>Test</code> provides two methods to get a reference to the value of the fields
|
|
<code>a</code> and <code>b</code>. Since <code>b</code> is a reference to <code>a</code> we store it as a pointer since
|
|
the borrowing rules of Rust doesn't allow us to define this lifetime.</p>
|
|
<p>In our main method we first instantiate two instances of <code>Test</code> and print out
|
|
the value of the fields on <code>test1</code>. We get:</p>
|
|
<pre><code class="language-rust ignore">a: test1, b: test1
|
|
</code></pre>
|
|
<p>Next we swap the data stored at the memory location which <code>test1</code> is pointing to
|
|
with the data stored at the memory location <code>test2</code> is pointing to and vice a versa.</p>
|
|
<p>We should expect that printing the fields of <code>test2</code> should display the same as
|
|
<code>test1</code> (since the object we printed before the swap has moved there now).</p>
|
|
<pre><code class="language-rust ignore">a: test1, b: test2
|
|
</code></pre>
|
|
<p>The pointer to <code>b</code> still points to the old location. That location is now
|
|
occupied with the string "test2". This can be a bit hard to visualize so I made
|
|
a figure that i hope can help.</p>
|
|
<p><strong>Fig 1: Before and after swap</strong>
|
|
<img src="./assets/swap_problem.jpg" alt="swap_problem" /></p>
|
|
<p>As you can see this results in unwanted behavior. It's easy to get this to
|
|
segfault, show UB and fail in other spectacular ways as well.</p>
|
|
<p>If we change the example to using <code>Pin</code> instead:</p>
|
|
<pre><pre class="playpen"><code class="language-rust editable">use std::pin::Pin;
|
|
use std::marker::PhantomPinned;
|
|
|
|
#[derive(Debug)]
|
|
struct Test {
|
|
a: String,
|
|
b: *const String,
|
|
_marker: PhantomPinned,
|
|
}
|
|
|
|
|
|
impl Test {
|
|
fn new(txt: &str) -> Self {
|
|
let a = String::from(txt);
|
|
Test {
|
|
a,
|
|
b: std::ptr::null(),
|
|
// This makes our type `!Unpin`
|
|
_marker: PhantomPinned,
|
|
}
|
|
}
|
|
fn init(&mut self) {
|
|
let self_ptr: *const String = &self.a;
|
|
self.b = self_ptr;
|
|
}
|
|
|
|
fn a<'a>(self: Pin<&'a Self>) -> &'a str {
|
|
&self.get_ref().a
|
|
}
|
|
|
|
fn b<'a>(self: Pin<&'a Self>) -> &'a String {
|
|
unsafe { &*(self.b) }
|
|
}
|
|
}
|
|
|
|
pub fn main() {
|
|
let mut test1 = Test::new("test1");
|
|
test1.init();
|
|
let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
|
|
let mut test2 = Test::new("test2");
|
|
test2.init();
|
|
let mut test2_pin = unsafe { Pin::new_unchecked(&mut test2) };
|
|
|
|
println!(
|
|
"a: {}, b: {}",
|
|
Test::a(test1_pin.as_ref()),
|
|
Test::b(test1_pin.as_ref())
|
|
);
|
|
|
|
// Try to uncomment this and see what happens
|
|
// std::mem::swap(test1_pin.as_mut(), test2_pin.as_mut());
|
|
println!(
|
|
"a: {}, b: {}",
|
|
Test::a(test2_pin.as_ref()),
|
|
Test::b(test2_pin.as_ref())
|
|
);
|
|
}
|
|
|
|
</code></pre></pre>
|
|
<p>Now, what we've done here is pinning a stack address. That will always be
|
|
<code>unsafe</code> if our type implements <code>!Unpin</code> (aka <code>MustStay</code>).</p>
|
|
<p>We use some tricks here, including requiring an <code>init</code>. If we want to fix that
|
|
and let users avoid <code>unsafe</code> we need to pin our data on the heap instead.</p>
|
|
<blockquote>
|
|
<p>Stack pinning will always depend on the current stack frame we're in, so we
|
|
can't create a self referential object in one stack frame and return it since
|
|
any pointers we take to "self" is invalidated.</p>
|
|
</blockquote>
|
|
<p>The next example solves some of our friction at the cost of a heap allocation.</p>
|
|
<pre><pre class="playpen"><code class="language-rust editbable">use std::pin::Pin;
|
|
use std::marker::PhantomPinned;
|
|
|
|
#[derive(Debug)]
|
|
struct Test {
|
|
a: String,
|
|
b: *const String,
|
|
_marker: PhantomPinned,
|
|
}
|
|
|
|
impl Test {
|
|
fn new(txt: &str) -> Pin<Box<Self>> {
|
|
let a = String::from(txt);
|
|
let t = Test {
|
|
a,
|
|
b: std::ptr::null(),
|
|
_marker: PhantomPinned,
|
|
};
|
|
let mut boxed = Box::pin(t);
|
|
let self_ptr: *const String = &boxed.as_ref().a;
|
|
unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };
|
|
|
|
boxed
|
|
}
|
|
|
|
fn a<'a>(self: Pin<&'a Self>) -> &'a str {
|
|
&self.get_ref().a
|
|
}
|
|
|
|
fn b<'a>(self: Pin<&'a Self>) -> &'a String {
|
|
unsafe { &*(self.b) }
|
|
}
|
|
}
|
|
|
|
pub fn main() {
|
|
let mut test1 = Test::new("test1");
|
|
let mut test2 = Test::new("test2");
|
|
|
|
println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());
|
|
|
|
// Try to uncomment this and see what happens
|
|
// std::mem::swap(&mut test1, &mut test2);
|
|
println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());
|
|
}
|
|
</code></pre></pre>
|
|
<p>The fact that boxing (heap allocating) a value that implements <code>!Unpin</code> is safe
|
|
makes sense. Once the data is allocated on the heap it will have a stable address.</p>
|
|
<p>There is no need for us as users of the API to take special care and ensure
|
|
that the self-referential pointer stays valid.</p>
|
|
<p>There are ways to safely give some guarantees on stack pinning as well, but right
|
|
now you need to use a crate like <a href="https://docs.rs/pin-project/">pin_project</a> to do that.</p>
|
|
<h3><a class="header" href="#projectionstructural-pinning" id="projectionstructural-pinning">Projection/structural pinning</a></h3>
|
|
<p>In short, projection is a programming language term. <code>mystruct.field1</code> is a
|
|
projection. Structural pinning is using <code>Pin</code> on fields. This has several
|
|
caveats and is not something you'll normally see so I refer to the documentation
|
|
for that.</p>
|
|
<h3><a class="header" href="#pin-and-drop" id="pin-and-drop">Pin and Drop</a></h3>
|
|
<p>The <code>Pin</code> guarantee exists from the moment the value is pinned until it's dropped.
|
|
In the <code>Drop</code> implementation you take a mutable reference to <code>self</code>, which means
|
|
extra care must be taken when implementing <code>Drop</code> for pinned types.</p>
|
|
<h2><a class="header" href="#putting-it-all-together" id="putting-it-all-together">Putting it all together</a></h2>
|
|
<p>This is exactly what we'll do when we implement our own <code>Futures</code> stay tuned,
|
|
we're soon finished.</p>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
<a rel="prev" href="3_generators_pin.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
|
|
|
|
<a rel="next" href="6_future_example.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
|
|
<a href="3_generators_pin.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
|
|
|
|
<a href="6_future_example.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Google Analytics Tag -->
|
|
<script type="text/javascript">
|
|
var localAddrs = ["localhost", "127.0.0.1", ""];
|
|
|
|
// make sure we don't activate google analytics if the developer is
|
|
// inspecting the book locally...
|
|
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
|
|
|
ga('create', 'UA-157536992-1', 'auto');
|
|
ga('send', 'pageview');
|
|
}
|
|
</script>
|
|
|
|
|
|
|
|
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
|
|
|
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
|
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
|
|
|
|
|
</body>
|
|
</html>
|