<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.7">Jekyll</generator><link href="timryan.org/feed.xml" rel="self" type="application/atom+xml" /><link href="timryan.org/" rel="alternate" type="text/html" /><updated>2025-07-26T23:25:09-04:00</updated><id>timryan.org/feed.xml</id><title type="html">Non-Recurring Engineering</title><subtitle>I write a lot of code and get really dramatic about it</subtitle><author><name>Tim Ryan</name></author><entry><title type="html">Exporting Serde types to TypeScript</title><link href="timryan.org/2019/01/22/exporting-serde-types-to-typescript.html" rel="alternate" type="text/html" title="Exporting Serde types to TypeScript" /><published>2019-01-22T11:00:00-05:00</published><updated>2019-01-22T11:00:00-05:00</updated><id>timryan.org/2019/01/22/exporting-serde-types-to-typescript</id><content type="html" xml:base="timryan.org/2019/01/22/exporting-serde-types-to-typescript.html"><![CDATA[<p>I built my first web application with Rust and WebAssembly back in 2017. At the
time, support for compiling Rust with the <code class="highlighter-rouge">wasm32-unknown-unknown</code>  target had
just landed, letting you run Rust code in the browser with few modifications.
The downside was that loading and interacting with WebAssembly might
require you to explicitly allocate and track memory. You
might even need to manually decode UTF-8 strings in JavaScript:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nx">Module</span> <span class="o">=</span> <span class="p">{};</span>
<span class="nx">fetchAndInstantiate</span><span class="p">(</span><span class="dl">"</span><span class="s2">./factorial.wasm</span><span class="dl">"</span><span class="p">,</span> <span class="p">{})</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">mod</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">Module</span><span class="p">.</span><span class="nx">fact_str</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">outptr</span> <span class="o">=</span> <span class="nx">mod</span><span class="p">.</span><span class="nx">exports</span><span class="p">.</span><span class="nx">fact_str</span><span class="p">(</span><span class="nx">n</span><span class="p">);</span>
    <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">copyCStr</span><span class="p">(</span><span class="nx">Module</span><span class="p">,</span> <span class="nx">outptr</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
  <span class="p">};</span>
<span class="p">})</span>

<span class="c1">// This method reaches into the WebAssembly memory map and copies a</span>
<span class="c1">// null-terminated string starting at *ptr. This method also consumes</span>
<span class="c1">// the old data, so we tell WebAssembly to deallocate this memory.</span>
<span class="kd">function</span> <span class="nx">copyCStr</span><span class="p">(</span><span class="nx">module</span><span class="p">,</span> <span class="nx">ptr</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">buffer_as_u8</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">collectCString</span><span class="p">(</span><span class="nx">module</span><span class="p">,</span> <span class="nx">ptr</span><span class="p">))</span>
  <span class="kd">const</span> <span class="nx">utf8Decoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TextDecoder</span><span class="p">(</span><span class="dl">"</span><span class="s2">UTF-8</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">buffer_as_utf8</span> <span class="o">=</span> <span class="nx">utf8Decoder</span><span class="p">.</span><span class="nx">decode</span><span class="p">(</span><span class="nx">buffer_as_u8</span><span class="p">);</span>
  <span class="nx">module</span><span class="p">.</span><span class="nx">dealloc_str</span><span class="p">(</span><span class="nx">orig_ptr</span><span class="p">);</span>
  <span class="p">...</span>
</code></pre></div></div>

<p>(Adapted from <a href="https://www.hellorust.com">Hello Rust</a>, a guide for
writing Rust and WebAssembly bindings by hand.)</p>

<p>This code looked fragile and prone to copy + paste errors. If any of these
manual memory allocations or conversions happened at the wrong time, it could
even cause Rust code to panic! After I got a demo working, instead of
building out a complex API, I decided to abstract as much
of this possible and behind a simple message-passing
interface to exchange
JSON-encoded strings between WebAssembly and JavaScript.</p>

<p>One upside of this design is that it makes it easier to run and test your
WebAssembly code outside of a web context: rather than boot up a web browser, you can have it receive messages from
a virtual frontend. It also becomes straightforward to delegate WebAssembly code to a
Web Worker and communicate with the main thread using the <code class="highlighter-rouge">postMessage</code> interface.
The tradeoff is that we have to assume all messages sent by
JavaScript are well-formed. Whenever a new message variant is added or changed, the
frontend must manually be kept in sync.</p>

<h2 id="what-wasm-bindgen-exports-to-typescript">What wasm-bindgen exports to TypeScript</h2>

<p>It’s now 2019, and the amazing work done by the Rust WebAssembly WG on
<a href="https://github.com/rustwasm/wasm-bindgen">wasm-bindgen</a> makes it easy to expose
functions from Rust, import JavaScript methods, and even manipulate rich data
types across the runtime boundary.</p>

<p>Using wasm-bindgen, Rust structs, methods,
free functions, and even basic enums can be imported to JavaScript. The command
line <code class="highlighter-rouge">wasm-bindgen</code> tool can also generate TypeScript definitions (when you
pass the
<code class="highlighter-rouge">--typescript</code> argument). This lets you type-check both your Rust code and your
frontend components at the same time, catching type errors during compilation rather than
at runtime. This is especially valuable given how hard WebAssembly runtime
errors are to debug!</p>

<p>The caveat is that wasm-bindgen only supports a subset of Rust types: those with a corresponding semantic representation in TypeScript.
It supports exporting  <code class="highlighter-rouge">struct</code> fields and <code class="highlighter-rouge">impl</code>  methods as part of a single
<code class="highlighter-rouge">class</code> definition, for example, and exporting simple C-like <code class="highlighter-rouge">enum</code>s as
TypeScript <code class="highlighter-rouge">enum</code>s. If we wanted to export a Rust struct with a single field:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[wasm_bindgen]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">SimpleMessage</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">value</span><span class="p">:</span> <span class="nb">f64</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For this type, wasm-bindgen would generate the following TypeScript definition:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">SimpleMessage</span> <span class="p">{</span>
  <span class="nx">free</span><span class="p">():</span> <span class="k">void</span><span class="p">;</span>
  <span class="nl">value</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Note that our one-field struct is exported as a <code class="highlighter-rouge">class</code> that is allocated
on WebAssembly’s heap (hence the <code class="highlighter-rouge">free()</code> method). Rather than initialize a
variable with an anonymous JavaScript object, we instead have have to
instantiate a member of the <code class="highlighter-rouge">SimpleMessage</code> class with <code class="highlighter-rouge">new</code> and assign its
properties directly.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// expected SimpleMessage, found literal</span>
<span class="kd">let</span> <span class="nx">message</span><span class="p">:</span> <span class="nx">SimpleMessage</span> <span class="o">=</span> <span class="p">{</span> <span class="na">value</span><span class="p">:</span> <span class="mi">5</span> <span class="p">};</span> 

<span class="c1">// this works though</span>
<span class="kd">let</span> <span class="nx">message</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SimpleMessage</span><span class="p">();</span>
<span class="nx">message</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
</code></pre></div></div>

<p>You also can’t add any fields to the struct definition in Rust which do not
implement <code class="highlighter-rouge">Copy</code>, which includes <code class="highlighter-rouge">String</code>.  The types of objects we can
expose to TypeScript are powerful, but limited in complexity.</p>

<p>The reason wasm-bindgen has these restrictions is because its design is to
expose an <strong>interface</strong> between the two systems, whereas we want to share a
<strong>data structure</strong>. A Typescript definition that looks exactly like our Rust
type when serialized into JSON would enable compile-time type-checking.  With
that, we could add new messages to Rust and know we’ve either handled them
explicitly on the frontend, or trigger a compilation error pointing out the missing
type.</p>

<h2 id="derivetypescriptdefinition">#[derive(TypeScriptDefinition)]</h2>

<p>Let’s say we want to generate a TypeScript definition for any type which can derive the <code class="highlighter-rouge">Serialize</code> trait. The types that can implement this trait include:</p>

<ul>
  <li><code class="highlighter-rouge">struct</code> types, which can be a unit type (<code class="highlighter-rouge">Struct()</code>), newtype <code class="highlighter-rouge">Struct(u64)</code>, tuple type <code class="highlighter-rouge">Struct(u64, String)</code>, or struct type <code class="highlighter-rouge">Struct { value: u64, name: String }</code></li>
  <li>C-style <code class="highlighter-rouge">enum</code> types, which contain only unit discriminants with associated values</li>
  <li><code class="highlighter-rouge">enum</code> types whose variants contain other values, aka sum types. This includes unit, newtype, tuple, or struct variants</li>
</ul>

<p>My first attempt at exporting custom TypeScript definitions was to hack on the
<code class="highlighter-rouge">wasm-bindgen</code> crate itself and modify how it generates <a href="https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html"><code class="highlighter-rouge">.d.ts</code>
files</a>.
It was hard to distinguish what types should be exported as JSON instead of
fully interactive interfaces. And a Rust type that can implement Serialize isn’t
necessarily supported as a type by <code class="highlighter-rouge">wasm-bindgen</code>; for example, support for
things like enums with complex variants (sum types) had to be written from scratch.</p>

<p>My next idea was to write a stand-alone crate that understood how <code class="highlighter-rouge">serde</code>
serialized data and have it print out a JSON-compatible TypeScript definition.
Instead of using constructs like  <code class="highlighter-rouge">class</code> to model a Rust type, this crate would
export a type alias for an object literal that matched its JSON serialization.
Implementing code in an external crate made it easier to support serde-specific attributes like
<code class="highlighter-rouge">#[serde(rename="...")]</code>, which changes the structure of the type (like renaming
fields) but only when serialized.</p>

<p>All this needed was a way to add strings directly to wasm-bindgen’s
TypeScript pass. Based on Rust’s <code class="highlighter-rouge">wasm_custom_section</code> attribute, I <a href="https://github.com/rustwasm/wasm-bindgen/pull/1048">submitted a
feature to the
wasm-bindgen crate</a>
to let you inject strings directly into its generated TypeScript
definition file. You can now use the
<a href="https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-rust-exports/typescript_custom_section.html"><code class="highlighter-rouge">typescript_custom_section</code></a>
attribute to inject custom types when running wasm-bindgen:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// This static string will be injected into the TypeScript definition file. </span>
<span class="nd">#[wasm_bindgen(typescript_custom_section)]</span>
<span class="k">const</span> <span class="n">TS_APPEND_CONTENT</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span> <span class="o">=</span> <span class="s">r#"

export type Coords = { "latitude": number, "longitude": number }; 

"#</span><span class="p">;</span>
</code></pre></div></div>

<p>Note that these <code class="highlighter-rouge">&amp;’static str</code> values can be <em>dynamically generated</em> by a macro,
for example a <code class="highlighter-rouge">#[derive()]</code> macro that adds a custom type definition.</p>

<p>Creating such a macro that could parse a <code class="highlighter-rouge">Serialize</code>-able type and generate a type definition was surprisingly
easy. Assuming someone had already written a serde-aware crate that printed
a self-describing schema, I came across the
<a href="https://github.com/srijs/rust-serde-schema"><code class="highlighter-rouge">rust-serde-schema</code></a> crate (MIT +
Apache-2 licensed) which recursively walks the structure of a Rust type and
builds up a context object. To generate code, it’s even easier to recursively
build up a list of tokens instead. These tokens can then be flattened into a
string and inserted by our macro with the
<code class="highlighter-rouge">#[wasm_bindgen(typescript_custom_section)]</code> annotation.</p>

<p>Putting these ideas together: You can try my experimental
<strong><a href="https://github.com/tcr/wasm-typescript-definition">wasm-typescript-definition</a></strong>
crate, which provides a <code class="highlighter-rouge">TypeScriptDefintion</code> macro that exports serde-compatible
TypeScript definitions when running wasm-bindgen.</p>

<p>A type only needs to be annotated with the
<code class="highlighter-rouge">#[derive(TypeScriptDefinition)]</code> attribute to work, though it makes sense to
implement <code class="highlighter-rouge">Serialize</code> and <code class="highlighter-rouge">Deserialize</code> as well so it works with serde_json.
A nice demonstration of this macro is with sum types. We can now convert Rust
types that are otherwise inexpressible in JavaScript into data types that
TypeScript understands. If we wanted to define an enum whose variants are
different messages our frontend can receive:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[derive(TypeScriptDefinition,</span> <span class="nd">Serialize,</span> <span class="nd">Deserialize)]</span>
<span class="nd">#[serde(tag</span> <span class="nd">=</span> <span class="s">"tag"</span><span class="nd">,</span> <span class="nd">content</span> <span class="nd">=</span> <span class="s">"fields"</span><span class="nd">)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">FrontendMessage</span> <span class="p">{</span>
  <span class="n">Init</span> <span class="p">{</span> <span class="n">id</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="p">},</span>
  <span class="n">ButtonState</span> <span class="p">{</span> <span class="n">selected</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">time</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="p">}</span>
  <span class="n">Render</span> <span class="p">{</span> <span class="n">html</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="n">time</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="p">},</span>
<span class="p">}</span>
</code></pre></div></div>

<p>When we run wasm-bindgen, it generates a type definition that is inlined into the TypeScript definition file:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">type</span> <span class="nx">FrontendMessage</span> <span class="o">=</span>
  <span class="o">|</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">tag</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Init</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">fields</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">id</span><span class="dl">"</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">}</span> <span class="p">},</span>
  <span class="o">|</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">tag</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ButtonState</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">fields</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">selected</span><span class="dl">"</span><span class="p">:</span> <span class="nb">Array</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span><span class="p">,</span> <span class="dl">"</span><span class="s2">time</span><span class="dl">"</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="p">}</span> <span class="p">},</span>
  <span class="o">|</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">tag</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Render</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">fields</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">html</span><span class="dl">"</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="dl">"</span><span class="s2">time</span><span class="dl">"</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="p">}</span> <span class="p">},</span>
  <span class="p">;</span>
</code></pre></div></div>

<p>The pipe operator <code class="highlighter-rouge">|</code> in a type definition means that it can be one of a list of different variants, much like enum variants in Rust.</p>

<p>Now when we create a new message in TypeScript like:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">msg</span><span class="p">:</span> <span class="nx">FrontendMessage</span> <span class="o">=</span>
  <span class="p">{</span><span class="dl">"</span><span class="s2">tag</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ButtonState</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">fields</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span><span class="dl">"</span><span class="s2">selected</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">bold</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">list</span><span class="dl">"</span><span class="p">],</span> <span class="na">time</span><span class="p">:</span> <span class="mi">0</span> <span class="p">}};</span>
</code></pre></div></div>

<p>We can rely on compile
time type-checking to ensure the message is well-formed. That guarantees it can
be serialized into a string with <code class="highlighter-rouge">JSON.stringify</code> and parsed without errors
using <code class="highlighter-rouge">serde_json::from_str</code> in Rust. It is now possible to change and
introduce new messages to interface between the two runtimes without the risk of
introducting runtime errors.</p>

<p>A primary benefit of <code class="highlighter-rouge">wasm-bindgen</code> is to push Rust’s safety guarantees into
frontend code. I think there’s a compelling case for a “serde_typescript” crate
that can generate TypeScript definitions for serializable data types, and has
complete support for all of serde’s custom serialization attributes. For now,
you can check out my <a href="https://github.com/tcr/wasm-typescript-definition">Github repo for
wasm-typescript-definition</a>.
Hope you find it useful!</p>

<h2 id="bonus-exhaustive-switch-statements-in-typescript">Bonus: Exhaustive switch statements in TypeScript</h2>

<p>Strong typing on the frontend gets us many of the same guarantees we have in
Rust and makes it possible to keep frontend and WebAssembly code in sync. But we
lack one important aspect of Rust’s pattern matching capabilities: its
requirement that all <code class="highlighter-rouge">match</code> arms must be “exhaustive”  (that is, there are no
unhandled cases).</p>

<p>If we matched against the  <code class="highlighter-rouge">FrontendMessage</code> enum type, but we only handled the
<code class="highlighter-rouge">Init</code> and <code class="highlighter-rouge">ButtonState</code> variants, our program would fail at compile time
indicating that we are missing a match arm for the third variant <code class="highlighter-rouge">Render</code>. In
TypeScript, we have to work to get the same guarantee. Assuming we choose
serde’s <a href="https://serde.rs/enum-representations.html">“internally tagged”
representation</a> for our enum, we can
branch in TypeScript based on which variant it is by doing e.g.:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">switch</span> <span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="nx">tag</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">case</span> <span class="dl">"</span><span class="s2">Init</span><span class="dl">"</span><span class="p">:</span> <span class="p">...</span> 
  <span class="k">case</span> <span class="dl">"</span><span class="s2">ButtonState</span><span class="dl">"</span><span class="p">:</span> <span class="p">...</span>
  <span class="k">case</span> <span class="dl">"</span><span class="s2">Render</span><span class="dl">"</span><span class="p">:</span> <span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At build time, the compiler can guarantee that each of the
values we use for “tag” matches the name of an actual variant, which prevents
you from misspelling or omitting a message type. But the compiler doesn’t check to
see that your <code class="highlighter-rouge">case</code> statements are <em>exhaustive</em>—that every case is
handled—unless you explicitly guarantee that the “default” code path will never be taken.</p>

<p>For this arcane purpose, TypeScript provides the <code class="highlighter-rouge">never</code> keyword, and in its
Advanced Types documentation suggests <a href="https://serde.rs/enum-representations.html">how you can make a switch statement
exhaustively check all variants</a> of
a “discriminated union” (essentially the same as our “internally tagged” enum
representation):</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">assertNever</span><span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="nx">never</span><span class="p">):</span> <span class="nx">never</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Unexpected object: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">x</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">sendFrontendMessage</span><span class="p">(</span><span class="nx">msg</span><span class="p">:</span> <span class="nx">FrontendMessage</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">switch</span> <span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">case</span> <span class="dl">"</span><span class="s2">Init</span><span class="dl">"</span><span class="p">:</span> <span class="k">return</span> <span class="nx">App</span><span class="p">.</span><span class="nx">initialize</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span>
        <span class="k">case</span> <span class="dl">"</span><span class="s2">ButtonState</span><span class="dl">"</span><span class="p">:</span> <span class="k">return</span> <span class="nx">App</span><span class="p">.</span><span class="nx">updateButtons</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">selected</span><span class="p">,</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">time</span><span class="p">);</span>
        <span class="k">case</span> <span class="dl">"</span><span class="s2">Render</span><span class="dl">"</span><span class="p">:</span> <span class="k">return</span> <span class="nx">App</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">html</span><span class="p">,</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">time</span><span class="p">);</span>

        <span class="c1">// If we missed an enum variant, TypeScript would now error out here.</span>
        <span class="nl">default</span><span class="p">:</span> <span class="k">return</span> <span class="nx">assertNever</span><span class="p">(</span><span class="nx">msg</span><span class="p">);</span> 
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The assertNever function accepts an argument with a <code class="highlighter-rouge">never</code> type, meaning it
should never be called given a valid FrontendMessage type. As long as this
<code class="highlighter-rouge">default: return assertNever(msg);</code> case appears in every switch statement
operating on a “discriminated union”, TypeScript can provide compile-time type
checking that you’ve handled all possible variants. <a href="https://t.co/3E2CkVzgCQ">See this TypeScript
playground</a> to try out never types for yourself.</p>]]></content><author><name>Tim Ryan</name></author><summary type="html"><![CDATA[I built my first web application with Rust and WebAssembly back in 2017. At the time, support for compiling Rust with the wasm32-unknown-unknown target had just landed, letting you run Rust code in the browser with few modifications. The downside was that loading and interacting with WebAssembly might require you to explicitly allocate and track memory. You might even need to manually decode UTF-8 strings in JavaScript:]]></summary></entry><entry><title type="html">A “rustup target” Example: Using a Mac to cross-compile Linux binaries</title><link href="timryan.org/2018/07/27/cross-compiling-linux-binaries-from-macos.html" rel="alternate" type="text/html" title="A “rustup target” Example: Using a Mac to cross-compile Linux binaries" /><published>2018-07-27T09:22:00-04:00</published><updated>2018-07-27T09:22:00-04:00</updated><id>timryan.org/2018/07/27/cross-compiling-linux-binaries-from-macos</id><content type="html" xml:base="timryan.org/2018/07/27/cross-compiling-linux-binaries-from-macos.html"><![CDATA[<p>The cycle of development we’re most familiar with is: write code, compile your code, then run this code on the same machine you were writing it on. On most desktop OSes, you pick up a compiler by downloading one from your package manager. Xcode and Visual Studio are toolchains (actually IDEs) that leverage being platform-specific, each including tools tailored around the platform your code will run on and heavily showcasing the parent OS’s design language.</p>

<p>Yet you can also write code that runs on platforms you <em>aren’t</em> simultaneously coding on. Every modern computer architecture supports a C compiler you can download and run on your PC, usually a binary + utils for a new gcc or llvm backend. In practice, using these tools means setting several non-obvious environment variables like <code class="highlighter-rouge">CC</code> and searching the internet for magic command line arguments (it takes a lot of work to convince a Makefile not to default to running <code class="highlighter-rouge">gcc</code>). Installing a compiler for another machine is easy, but getting a usable result takes trial and error.</p>

<p>If you’ve picked up Rust and are learning systems programming, you might ask: Does Rust, a language whose design addresses C’s inadequacies for developing secure software, also address its shortcomings in generating code on other platforms?</p>

<p>Let’s start with the <a href="https://forge.rust-lang.org/platform-support.html">tiered platform support system</a> Rust maintains to track which platforms it supports and how full that support is (from “it actually might not build at all” to “we test it each release”). On its own, this is a useful reference of the target identifiers of popular consumer OSes, embedded platforms, and some experimental ones (like Redox!). The majority of these different platforms don’t actually support running <code class="highlighter-rouge">rustc</code>  or <code class="highlighter-rouge">cargo</code> from the command line though.</p>

<p>Rust makes up for this by advertising a strong cross-compilation story. Quoting from <a href="https://blog.rust-lang.org/2016/05/13/rustup.html">the announcement post</a> for <code class="highlighter-rouge">rustup target</code>:</p>

<blockquote>
  <p>In practice, there are a lot of ducks you have to get in a row to make [cross-compilation] work: the appropriate Rust standard library, a cross-compiling C toolchain including linker, headers and binaries for C libraries, and so on. This typically involves pouring over various blog posts and package installers to get everything “just so”. And the exact set of tools can be different for every pair of host and target platforms.</p>

  <p>The Rust community has been hard at work toward the goal of “push-button cross-compilation”. We want to provide a complete setup for a given host/target pair with the run of a single command.</p>
</blockquote>

<p>This is an excellent goal given the infrastructure and design challenges ahead. And I wanted to learn more about this part of the article:</p>

<blockquote>
  <p>Cross-compilation is an imposing term for a common kind of desire:</p>
  <ul>
    <li>…</li>
    <li><strong>You want to write, test and build code on your Mac, but deploy it to your Linux server.</strong></li>
    <li>…</li>
  </ul>
</blockquote>

<p>This is exactly the scenario I’m in! But… understandably, the article doesn’t actually include an example of how to do this, because cross-compiling for another OS requires making several assumptions about the target platform that maybe not apply to everyone.  Here is a recap of how I made it work for my project.</p>

<h1 id="deploying-via-git-and-building-from-git">Deploying via git and building from git</h1>
<p>In my case, the need to build binaries for Linux came up while working on my project <a href="http://github.com/tcr/edit-text">edit-text, a collaborative test editor for the web written in Rust</a>. I regularly test changes out <a href="http://sandbox.edit.io/">in a sandbox environment</a> since you can’t rely on testing code locally to catch behavior that might only appear in production. Yet the issue I kept running into was how <em>long</em> it was taking deploy to my $10 DigitalOcean server. I spent a long time rereading the same compiler logs before it actually dawned on me—I was compiling on my web server and not my laptop. And that’s really slow.</p>

<p>If you have a githook that takes new source code pushed via <code class="highlighter-rouge">git</code> and loads it into a Docker container, deploying via <code class="highlighter-rouge">git</code> just sends up your source directory and points at a <code class="highlighter-rouge">rustc</code> compiler. On each new deploy, your server has to rebuild from your Dockerfile from scratch, and unless you configure it to support caching this throws away the benefit of quickly iterating on your code. If you want the benefit of faster builds by having cargo incrementally cache compiled files between builds, you’ll find it’s complex to get right in your Dockerfile configuration but entirely natural to manage this in your local development environment.</p>

<p>The approach I have the most experience with is to take the compilation environment and just run it locally on my machine. With Docker, we have easy way to run Linux environments (even on Mac) and to pin it to the same development environment I have on my server. Since Docker on my machine will be running Linux in a hypervisor, local performance should beat what I can do on my server even with the overhead of not being the host OS.</p>

<p>Did you know Rust has a first-party story for cross-compiling for Linux using Docker? The <a href="https://hub.docker.com/r/rustlang/rust/">Rust cross-compilation image</a> <code class="highlighter-rouge">rustlang/rust:nightly</code> can help you generate binaries for the Linux target compiled with nightly Rust and can be invoked on demand from the command line using <code class="highlighter-rouge">docker run</code> . I developed this set of command line arguments to get cross-compilation with caching working:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="se">\</span>
  <span class="nt">-v</span> <span class="nv">$DIR_GIT</span>:/usr/local/cargo/git <span class="se">\ </span><span class="c"># cache git downloads</span>
  <span class="nt">-v</span> <span class="nv">$DIR_REGISTRY</span>:/usr/local/cargo/registry <span class="se">\ </span><span class="c"># cache cargo registry</span>
  <span class="nt">-v</span> <span class="nv">$DIR_RUSTUP</span>:/usr/local/rustup/toolchains <span class="se">\ </span><span class="c"># cache rustup</span>
  <span class="nt">-v</span> <span class="nv">$DIR_SELF</span>:/app <span class="se">\</span>
  <span class="nt">-w</span> /app/edit-server <span class="se">\</span>
  <span class="nt">-t</span> edit-build-server <span class="se">\</span>
  cargo build <span class="nt">--release</span> <span class="nt">--target</span><span class="o">=</span>x86_64-unknown-linux-gnu <span class="nt">--bin</span> edit-server <span class="nt">--features</span> <span class="s1">'standalone'</span>
</code></pre></div></div>

<p>The binaries this produced, amazingly, worked when I copied them to and ran them on my Linux server. Compiling locally was marginally faster. But there were drawbacks to this approach for cross-compilation:</p>

<ul>
  <li>I had to manage and run Docker locally, which on macOS requires a Docker daemon be running on macOS.</li>
  <li>I had to cache each cargo and rust directory individually, which, managed separate from <code class="highlighter-rouge">rustup</code> on my machine, seemingly never got garbage collected. I accrued huge directories of cached files just for compiling for Linux.</li>
  <li>Intermediate artifacts from successive builds seemed less likely to be cached between builds, meaning building took longer than they should on my machine.</li>
</ul>

<p>I like that Docker gives a reproducible environment to build in—building Debian binaries on the Debian kernel makes things easy—but Rust’s cross-compilation support might allow me to manage all the compilation artifacts that make modern Rust compile times tolerable.</p>

<h2 id="rustup-target-add">“rustup target add”</h2>
<p>So far I’ve only mentioned the Rust compiler’s support for cross-compiling. There are a actually handful of components you need to make cross-compiling work:</p>

<ol>
  <li>Compiler that support your target</li>
  <li>Library headers to link your program against (if any)</li>
  <li>Shared library files to link against (if any)</li>
  <li>A “linker” for the target platform</li>
</ol>

<p>Let’s start with compiler support. Passing <code class="highlighter-rouge">--target</code> when running <code class="highlighter-rouge">cargo build|run</code> changes the assembly your CPU outputs and bundled in object files supported by that OS. But we have to install the new toolchain adding that capability to Cargo. This is  done with the command <code class="highlighter-rouge">rustup target add</code>.Installing support for another “target” is something you do once on your machine, and from then on cargo can support it via the <code class="highlighter-rouge">--target</code> argument.</p>

<p>To install a Linux target on my Mac, I ran:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rustup target add x86_64-unknown-linux-musl
</code></pre></div></div>

<p>This installs the new compilation target based on its <a href="https://wiki.osdev.org/Target_Triplet">target triplet</a>, which means:</p>

<ul>
  <li>We’re compiling for the x84-64 processor set (AMD64)</li>
  <li>from an unknown (generic) vendor, targeting the Linux OS</li>
  <li>compiled with the <a href="https://www.musl-libc.org/">musl</a> toolchain.</li>
</ul>

<p>A note on musl: the alternative to musl is GNU, as in GNU libc—which almost every Linux environment has installed anyway. So why choose musl? musl is designed to be statically linked into a binary rather than dynamically linked; this means I could compile a single binary I could deploy to any server without any requirements as to what libraries were installed on that OS. This might even mean we can skip steps 2) and 3) above. And I could back to GNU if it didn’t work out.</p>

<p>Finally, we need a program that links our compiled objects together. On macOS, you can use <a href="brew.sh">brew</a> to download a community-provided binary for Linux + musl cross-compilation. Just run this to install the toolchain including the command “x86_64-linux-musl-gcc”:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>FiloSottile/musl-cross/musl-cross
</code></pre></div></div>

<p>At this point we need to tell Rust about the linker. The official way to do this is to add a new file named <code class="highlighter-rouge">.cargo/config</code> in the root of your project and set its content to something similar:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[target.x86_64-unknown-linux-musl]</span>
<span class="py">linker</span> <span class="p">=</span> <span class="s">"x86_64-linux-musl-gcc"</span>
</code></pre></div></div>

<p>This should instruct Rust, whenever the target is set to <code class="highlighter-rouge">--target=x86_64-unknown-linux-musl</code>,  to use the executable “x86_64-linux-musl-gcc” to link the compiled objects. But it seems to be the case that if you have any C code compiled by a Rust build script, <a href="https://github.com/alexcrichton/cc-rs/issues/82">you also have to set environment variables like TARGET_CC</a> to get it working. So when my code started throwing linking errors, I just ran the following in my shell:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">TARGET_CC</span><span class="o">=</span>x86_64-linux-musl-gcc
</code></pre></div></div>

<p>Thankfully, this made the compilation steps with linker errors work consistently.</p>

<h2 id="libraries-and-linking">Libraries and Linking</h2>
<p>musl doesn’t support shared libraries, that is, libraries that are independently installed and versioned on your system via a package manager. Shared libraries are ones your program links to these at runtime (once the program starts), rather than literally embedding them in their binary at compile time (static libraries).</p>

<p>Sometimes you can work around the constraint of requiring static libraries without leaving the <code class="highlighter-rouge">cargo</code> ecosystem: some radical crates support a “bundled” feature, as in <a href="https://github.com/jgallagher/rusqlite">libsqlite3-sys</a>, which compiles a static library during your build step and links it into your project. For example, the SQLite drivers I was relying on had no problem being compiled with musl if I enabled the “bundled” feature; I didn’t have to <code class="highlighter-rouge">apt-get install libsqlite3</code> on the remote platform, nor did I have to find headers that matched it. An app with only this requirement would be a solid usecase for deploying binaries compiled with <code class="highlighter-rouge">musl</code>.</p>

<p>If your project depends on openssl, though, you’ll see this type of error midway during cargo build:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error: failed to run custom build <span class="nb">command </span><span class="k">for</span> <span class="sb">`</span>openssl-sys v0.9.33<span class="sb">`</span>
process didn<span class="s1">'t exit successfully: `/Users/timryan/edit-text/target/debug/build/openssl-sys-89bb414c25e8c29b/build-script-main` (exit code: 101)
--- stdout
[...]
--- stderr
thread '</span>main<span class="s1">' panicked at '</span>

Could not find directory of OpenSSL installation, and this <span class="sb">`</span><span class="nt">-sys</span><span class="sb">`</span> crate cannot
proceed without this knowledge. If OpenSSL is installed and this crate had
trouble finding it,  you can <span class="nb">set </span>the <span class="sb">`</span>OPENSSL_DIR<span class="sb">`</span> environment variable <span class="k">for </span>the
compilation process.

Make sure you also have the development packages of openssl installed.
For example, <span class="sb">`</span>libssl-dev<span class="sb">`</span> on Ubuntu or <span class="sb">`</span>openssl-devel<span class="sb">`</span> on Fedora.
</code></pre></div></div>

<p>No need to check the exit code, this clearly isn’t building correctly. The error says “Could not find directory of OpenSSL installation”. This doesn’t mean I didn’t have OpenSSL installed on my computer (I did); it means it can’t find its headers and source code to compile. Compiling a static binary is with musl is now much more complicated if I need to download and compile an arbitrary OpenSSL dependency.</p>

<p>Why I need OpenSSL as a dependency to begin with: one of the libraries I depend on in edit-text’s software stack,<code class="highlighter-rouge">reqwest</code>, relies on <code class="highlighter-rouge">native-tls</code> to support encryption for download data over HTTPS. <code class="highlighter-rouge">reqwest</code> is a programmatic HTTP client for Rust, and it uses  <code class="highlighter-rouge">native-tls</code> to link against the OS’s native SSL implementation and expose an agnostic interface to it in order to support HTTPS. I can imagine in the future a <code class="highlighter-rouge">reqwest</code> feature that substitutes a <code class="highlighter-rouge">rust-tls</code> backend instead of <code class="highlighter-rouge">native-tls</code>, allowing me to compile all my crypto could without needing to touch <code class="highlighter-rouge">gcc</code>. But for now, since I don’t want the heavy lift of compilng OpenSSL myself, dynamic linking looks like the only way forward.</p>

<h2 id="debian-packaging">Debian Packaging</h2>
<p>New plan: compile against the GNU toolchain and use dynamic linking. If we don’t want to cross-compile libraries ourselves, then we have to find a source of pre-compiled libraries and headers (which are sometimes distinct things). Since we’re moving away from musl, we’ll even need to bring our own copy of <code class="highlighter-rouge">libc</code>!</p>

<p>Luckily, we just have to recreate the same environment as a Linux compiler. This turns out to be straightforward. When I’m compiling code in Linux and need to, say, link against OpenSSL, I can run the following:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>libssl-dev
</code></pre></div></div>

<p>Now I can compile any binary which relies on OpenSSL headers, because they were installed to my system. Where are these files? One way to find them is to run <code class="highlighter-rouge">dpkg-query -L libssl-dec</code> to list which files were installed by my package manager. In this case, most of our header files are inserted into <code class="highlighter-rouge">/usr/include</code> and the Linux libraries in <code class="highlighter-rouge">/usr/lib</code>. If we have the .deb file itself, we can actually confirm this by dumping its archive contents:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ar p libssl-dev_1.1.0f-3+deb9u2_amd64.deb data.tar.xz | <span class="nb">tar </span>tvf -
drwxr-xr-x  0 root   root        0 Mar 29 06:51 ./
drwxr-xr-x  0 root   root        0 Mar 29 06:51 ./usr/
drwxr-xr-x  0 root   root        0 Mar 29 06:51 ./usr/include/
drwxr-xr-x  0 root   root        0 Mar 29 06:51 ./usr/include/openssl/
<span class="nt">-rw-r--r--</span>  0 root   root     3349 Mar 29 06:51 ./usr/include/openssl/aes.h
...
</code></pre></div></div>

<p>Where <code class="highlighter-rouge">aes.h</code> is the header we might require linking against.</p>

<p>We can essentially reuse these packages on other platforms. Package managers extract files to specific locations on your machine. If can extract these same archives locally, we tell the compiler to look in these folders for headers instead of OS folders.</p>

<p>Let’s describe which archives we want: First, my choice of a broadly-accessible Linux distribution that has good tooling is Debian, of which Ubuntu is a fork and which has a straightforward packaging system found with apt and its <code class="highlighter-rouge">.deb</code> package format. Second, we need to pick a sufficiently old enough version of Debian that would support ABI-compatible libraries. I chose /jessie/, the version of Debian immediately prior its current stable release, /stretch/.</p>

<p>We can’t just fetch a <code class="highlighter-rouge">.deb</code> archive via ‘apt-get install’ on a Mac though. Downloading library headers directly means navigating a <a href="https://packages.debian.org/stretch/amd64/openssl/download">hyperlink survey of computer architectures and CDNs</a>. I poked around for a while to see if there was an obvious way to compute the URL of any Debian package, but it looks like to retrieve the package URL you basically need to reimplement all of <em>aptitude</em> (the package manager used by Debian). Because there were no <code class="highlighter-rouge">brew</code> formulae for <em>libapt</em>, and no standalone Rust bindings either, I assumed any solution would be more complicated than just referencing the direct URL. As such, the build script fetches each package URL in sequence and extracts them into a local folder:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">URL</span><span class="o">=</span>http://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl-dev_1.1.0f-3+deb9u2_amd64.deb
curl <span class="nt">-O</span> <span class="nv">$URL</span>
ar p <span class="si">$(</span><span class="nb">basename</span> <span class="nv">$URL</span><span class="si">)</span> data.tar.xz | <span class="nb">tar </span>xvf -

<span class="nb">export </span><span class="nv">URL</span><span class="o">=</span>http://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.1_1.1.0f-3+deb9u2_amd64.deb
curl <span class="nt">-O</span> <span class="nv">$URL</span>
ar p <span class="si">$(</span><span class="nb">basename</span> <span class="nv">$URL</span><span class="si">)</span> data.tar.xz | <span class="nb">tar </span>xvf -

<span class="nb">export </span><span class="nv">URL</span><span class="o">=</span>http://ftp.us.debian.org/debian/pool/main/g/glibc/libc6_2.24-11+deb9u3_amd64.deb
curl <span class="nt">-O</span> <span class="nv">$URL</span>
ar p <span class="si">$(</span><span class="nb">basename</span> <span class="nv">$URL</span><span class="si">)</span> data.tar.xz | <span class="nb">tar </span>xvf -
</code></pre></div></div>

<p>You can <a href="https://github.com/tcr/edit-text/blob/ac3faf2de79fd2cb04c1599944a4eadaa99e0db9/x.rs#L498-L508">see the dependencies</a> my build script relies on. Note that we only install the packages we need to build with: <code class="highlighter-rouge">backtrace-rs</code> requires the libc library headers, and <code class="highlighter-rouge">openssl-sys</code> on Rust requires not only the headers in <code class="highlighter-rouge">libssl-dev</code> but also the shared library in <code class="highlighter-rouge">libssl1.1</code>. Other than that, these are all the packages I required when cross-compiling.</p>

<h2 id="building-linux-binaries-on-macos">Building Linux binaries on macOS</h2>
<p>We again need to install a linker, this time one that targets GNU/Linux. Again this is made easy with brew thanks to another community contribution:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew tap SergioBenitez/osxct
</code></pre></div></div>

<p>Now the executable “x86_64-unknown-linux-gnu-gcc” is available on our PATH.</p>

<p>We next make a series of environment variable updates:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Linker for the target platform</span>
<span class="c"># (cc can also be updated using .cargo/config)</span>
<span class="nb">export </span><span class="nv">TARGET_CC</span><span class="o">=</span><span class="s2">"x86_64-unknown-linux-gnu-gcc"</span>

<span class="c"># Library headers to link against</span>
<span class="nb">export </span><span class="nv">TARGET_CFLAGS</span><span class="o">=</span><span class="s2">"-I </span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/usr/include/x86_64-linux-gnu -isystem </span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/usr/include"</span>
<span class="c"># Libraries (shared objects) to link against</span>
<span class="nb">export </span><span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/usr/lib/x86_64-linux-gnu;</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/lib/x86_64-linux-gnu"</span>

<span class="c"># openssl-sys specific build flags</span>
<span class="nb">export </span><span class="nv">OPENSSL_DIR</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/usr/"</span>
<span class="nb">export </span><span class="nv">OPENSSL_LIB_DIR</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/usr/lib/x86_64-linux-gnu/"</span>
</code></pre></div></div>

<p>This specifies the linker headers and shared library locations, and some OpenSSL-specific flags required by openssl-sys. Take note of <code class="highlighter-rouge">-isystem</code>, which changes where <code class="highlighter-rouge">gcc</code> looks for system headers. Because we are using only Debian packages, the OpenSSL-specific build flags refer to the same folders as our other system libraries.</p>

<p>Now we can run the Cargo build command to cross-compile for Linux:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo build <span class="nt">--target</span><span class="o">=</span>x86_64-unknown-linux-gnu <span class="nt">--features</span> <span class="s1">'standalone'</span>
</code></pre></div></div>

<p>The “standalone” feature is part of the project, and configures everything that can be built without relying on system libraries (like SQLite).</p>

<p>Now in my project’s <code class="highlighter-rouge">./target/debug/x86_64-unknown-linux-gnu/</code> folder, I can run <code class="highlighter-rouge">file</code> on the edit-server binary:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>file target/x86_64-unknown-linux-gnu/release/edit-server
target/x86_64-unknown-linux-gnu/release/edit-server: ELF 64-bit <span class="se">\</span>
  LSB shared object, x86-64, version 1 <span class="o">(</span>SYSV<span class="o">)</span>, dynamically linked, <span class="se">\</span>
  interpreter /lib64/ld-linux-x86-64.so.2, <span class="k">for </span>GNU/Linux 3.2.0, <span class="se">\</span>
  with debug_info, not stripped
</code></pre></div></div>

<p>It says it’s a shared object (in this case, an executable) and mentions we compiled it for GNU/Linux. Next, I created a example Dockerfile based on Debian that, when the binary is placed in the same directory, just launches it:</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> nginx</span>

<span class="k">RUN </span>apt-get update<span class="p">;</span> apt-get <span class="nb">install </span>sqlite3 <span class="nt">-y</span>

<span class="k">ADD</span><span class="s"> . /app</span>
<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">EXPOSE</span><span class="s"> 80</span>

<span class="k">CMD</span><span class="s"> RUST_BACKTRACE=1 ./edit-server</span>
</code></pre></div></div>

<p>I tried it out with<code class="highlighter-rouge">docker run</code> on my machine, and saw the server successfully boot up:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker run <span class="nt">-it</span> <span class="si">$(</span>docker build <span class="nt">-q</span> .<span class="si">)</span>
<span class="o">[</span> ok <span class="o">]</span> Restarting nginx: nginx.
client proxy: <span class="nb">false
</span>Listening on http://0.0.0.0:8000/
Graphql served on http://0.0.0.0:8003
sync_socket_server is listening <span class="k">for </span>ws connections on 0.0.0.0:8001
</code></pre></div></div>

<p>This is Debian, running locally on my machine, successfully running the binary we compiled on my Mac. Since this is the same Dockerfile we send to the server, this means the server will be able to deploy it too!</p>

<p>There is one more step here: the binary now has to be sent to the server along with each deploy. Checking a large binary into Git just so Dokku could receive it via <code class="highlighter-rouge">git push</code> performs very poorly, and really is not what Git is built for. What worked for me: I switched to creating an archive of my Dockerfile’s directory and piping into <code class="highlighter-rouge">ssh</code>  running <code class="highlighter-rouge">dokku tar:in -</code> on the remote server. This Dokku command loads a tarball from stdin (in this case) and deploys it, making it possible to push new code to the server without needing to check in anything to git each time.</p>

<h2 id="rust-advantages-in-webdev">Rust advantages in webdev</h2>
<p>And the result: it is now much faster to update code running on a server. Compilation speed improved immensely between compiling it remotely, where each compile felt as slow as a full rebuild—and compiling it locally, where cargo’s incremental cache makes builds feel as fast as targeting your default OS. It’s fast enough I can deploy new code to a remote test server when it’s too annoying to set up a local server. Yet Rust’s cross-compilation story can’t eliminate by itself the clumsy ritual of setting arbitrary environment variables in order to get compilation to succeed.</p>

<p>If <code class="highlighter-rouge">rustup target</code> is a blueprint for the future, I imagine an ecosystem of cross-compilation tools will inevitably spring up that makes bundling for other OSes straightforward and configurable. Even though Rust isn’t an interpreted language, if deploying code no longer means compiling code on your server, and local recompilation is fast, it makes deployment in Rust feel much more like modern web development. Cross-compilation support is an undersold factor in Rust’s webdev story.</p>]]></content><author><name>Tim Ryan</name></author><summary type="html"><![CDATA[The cycle of development we’re most familiar with is: write code, compile your code, then run this code on the same machine you were writing it on. On most desktop OSes, you pick up a compiler by downloading one from your package manager. Xcode and Visual Studio are toolchains (actually IDEs) that leverage being platform-specific, each including tools tailored around the platform your code will run on and heavily showcasing the parent OS’s design language.]]></summary></entry><entry><title type="html">Moving from the shell to Rust with commandspec</title><link href="timryan.org/2018/07/02/moving-from-the-shell-to-rust-with-commandspec.html" rel="alternate" type="text/html" title="Moving from the shell to Rust with commandspec" /><published>2018-07-02T11:50:00-04:00</published><updated>2018-07-02T11:50:00-04:00</updated><id>timryan.org/2018/07/02/moving-from-the-shell-to-rust-with-commandspec</id><content type="html" xml:base="timryan.org/2018/07/02/moving-from-the-shell-to-rust-with-commandspec.html"><![CDATA[<p>Almost every project I’ve worked on has grown a shell script named “build.sh”, and not much later a “test.sh” and “run.sh”. At this point, you have to make a decision as a developer whether your goal is to accidentally reinvent <code class="highlighter-rouge">make</code> or if your codebase’s needs are better met by an executable to manage your workflow.</p>

<p>My hobby projects are mostly written in Rust, and there was a brilliant post earlier this year about how to <a href="https://matklad.github.io/2018/01/03/make-your-own-make.html">write your own cargo tools</a> using cargo’s command aliases. This works well for individual tools (like your own <code class="highlighter-rouge">cargo todo</code>), and has the benefit of needing no additional setup to work out of the box. The downside of this approach, besides discoverability, is that you don’t have a common entry point for your commands. In my case, most of my tooling shares a lot of common abstractions like debug/release mode switches or common environment variables. Rather than manage a constellation of separate binaries, here’s how I structured a unified build tool written for a moderate-sized Rust project.</p>

<p>To build a single entry point for these, I picked up <a href="https://github.com/DanielKeep/cargo-script">cargo-script</a> and created the executable “x.rs”, invoking <a href="https://github.com/rust-lang/rust/blob/master/x.py">Rust’s own build system “x.py”</a>. The goal is to have a build process as straightforward as running “./x.rs build” from the root of the project.</p>

<h1 id="design">Design</h1>

<p>The project this tool is for is <a href="https://github.com/tcr/edit-text">edit-text</a>, a collaborative text editor written in Rust which runs in the browser via WebAssembly. (<a href="https://github.com/tcr/edit-text/blob/master/x.rs">Here is its ./x.rs script.</a>) Because the editor is collaborative, it needs a server to coordinate editing. Because code can be shared between the server and client, common code is broken out into its own crate. The client is able to be compiled for the web or run as a standalone command-line binary, so we need to support multiple compilation targets; the frontend is an npm module bundled with webpack; also, WebAssembly requires an additional <code class="highlighter-rouge">wasm-bindgen</code> step when compiling. Long story short, it has accumulated a lot of build tools spread across many sub-projects.</p>

<p>Instead of having a command line interface that focuses on the <em>action</em> being taken, like how <code class="highlighter-rouge">cargo build --bin &lt;target&gt;</code> requires “build” be first, we can make it centered on the component itself: <code class="highlighter-rouge">./x.rs server-build</code>, <code class="highlighter-rouge">./x.rs server-watch</code>, and <code class="highlighter-rouge">./x.rs server</code> will build the server, rebuild while polling its source code for changes, and lastly run the binary. This makes <em>server</em> the target of our commands, and makes it especially easy to reverse-i-search (Ctrl+R) in the terminal for previous server commands. When needed, <code class="highlighter-rouge">./x.rs</code> recursively can shell out to itself, as in the case with <code class="highlighter-rouge">./x.rs server-test</code> which builds and spawns <code class="highlighter-rouge">./x.rs server</code> before running a WebDriver process in the foreground.</p>

<p>The build script is designed to have few dependencies as possible. It depends on the stellar <a href="https://github.com/killercup/quicli">quicli</a> and <a href="https://clap.rs/">clap</a> utilities for writing clean command line interfaces, and the <a href="https://github.com/rust-lang-nursery/failure">failure</a> crate for ergonomic error handling. This ensures we compile fast the first time <code class="highlighter-rouge">./x.rs</code> is run and when it’s next modified. (Sidenote: I’ll probably take this a step further and drop <code class="highlighter-rouge">quicli</code>, since <code class="highlighter-rouge">structopt</code> is less suited than <code class="highlighter-rouge">clap</code>’s builder API for adding new one-off subcommands.)</p>

<p>The build script doesn’t have any knowledge of code running contained in its subcrates, so it remains small and quick to bootstrap but also so build errors from subcrates don’t propagate up to the build tool. Complex functionality can be broken out into crates themselves. For example, <code class="highlighter-rouge">./x.rs logs</code> is a command that shells out to the command <code class="highlighter-rouge">cargo run --bin edit-server-logs -- {args}</code>. In this way the logic for the log parser can be part of the project that understands and writes those logs, and we can also independently test and use this functionality outside the build tool. Sending <code class="highlighter-rouge">edit-server</code> and <code class="highlighter-rouge">edit-server-logs</code> binaries to the server is more useful the vendoring the entire build script.</p>

<p>The last dependency is self-written and provides an abstraction for talking to the shell.</p>

<h1 id="scripting-with-commandspec">Scripting with <code class="highlighter-rouge">commandspec</code></h1>

<p>The challenge of moving from a scripting to a programming language is that shell scripting is very expressive. Using the child process API is trying to express the same thing with way more characters. Because the command line is versatile but permissive, the best strategy to adapting it to modern programming is to limit it to clean subset of features, like how XML is a less permissive HTML. But also, we need to increase the expressive power of the shell to make it easy to work with Rust-native data structures; so really we want what JSX to HTML, a clean, restrictive syntax the allows interpolation.</p>

<p>So I wrote <a href="https://github.com/tcr/commandspec/">commandspec</a>, a macro_rules! macro to inline shell syntax with your code without requiring it to run in an unsafe shell subprocess. This makes porting from the shell familiar, incremental, and robust. Say we start with this <code class="highlighter-rouge">build.sh</code> script:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git submodule update <span class="nt">--init</span> <span class="nt">--recursive</span>

<span class="nb">cd </span>frontend
npm <span class="nb">install
</span>npm run build
</code></pre></div></div>

<p>When we need more flexibility language, we can embed this script directly into a Rust program:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">sh_execute!</span><span class="p">(</span><span class="s">r"
    git submodule update --init --recursive

    cd frontend
    npm install
    npm run build
"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p>This just runs code directly in a shell process, so we can still make it safer by invoking these programs directly. What our build script is doing is running three commands and making one environment change (changing directories). If we consider each <code class="highlighter-rouge">{directory, environment, command}</code> group separately, we can rewrite each of these to use the <code class="highlighter-rouge">execute!</code> macro, a safe wrapper over familiar shell syntax:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">execute!</span><span class="p">(</span><span class="s">r"
    git submodule update --init --recursive
"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

<span class="nd">execute!</span><span class="p">(</span><span class="s">r"
    cd frontend
    npm install
"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

<span class="nd">execute!</span><span class="p">(</span><span class="s">r"
    cd frontend
    npm run build
"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p>Each step breaks the script into its component parts. You can add one <code class="highlighter-rouge">cd</code> instruction, multiple <code class="highlighter-rouge">export name=value</code> environment variables, and then ultimately your shell command. But the DSL parses this std::process::Command API underneath, which is cross-platform and doesn’t require a shell process at all:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// This executes the native rustc command in cmd.exe or in sh</span>
<span class="c1">// it prints the version the shell and returns Ok(())</span>
<span class="nd">execute!</span><span class="p">(</span><span class="s">r"
    rustc -V
"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p>From here, Rust-native abstractions can be added (like using the std::fs APIs instead of <code class="highlighter-rouge">mv</code> or <code class="highlighter-rouge">cp</code> commands) or be left alone, in cases where you would just be replacing it with the equivalent std::process::Command constructor.</p>

<p>A benefit of this pattern is that our commands can be copied almost verbatim into a new shell session and tested in isolation. The syntax is also similar enough for non POSIX shells (Powershell and cmd.exe, namely) that you can trivially rewrite it to run in those terminals.</p>

<p>This <code class="highlighter-rouge">execute!</code> method wraps the <code class="highlighter-rouge">command!</code> macro, which is identical except for returning a <code class="highlighter-rouge">std::process::Command</code> object directly. It then calls <code class="highlighter-rouge">.execute()</code>, a new trait method on <code class="highlighter-rouge">Command</code> that returns <code class="highlighter-rouge">Result&lt;(), CommandError&gt;</code>, the error being an object that wraps over non-0 error codes and filesystem or interrupt errors. Thus we can use <code class="highlighter-rouge">execute!(...)?</code> to return immediately for any command that is not successful (exit code of <code class="highlighter-rouge">0</code>), or we can decompose the error as an enum, or we can use the <code class="highlighter-rouge">.unwrap_err().error_code()</code> to get the error code directly.</p>

<p>It’s straightforward to combine the <code class="highlighter-rouge">command!</code> macro with the full std::process::Command API (or even something more complex, like <a href="https://docs.rs/os_pipe">os_pipe</a>).</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">cmd</span> <span class="o">=</span> <span class="nd">command!</span><span class="p">(</span><span class="s">r"
    tar -xvf -
"</span><span class="p">)</span><span class="o">?</span>
    <span class="nf">.stdin</span><span class="p">(</span><span class="nn">Stdio</span><span class="p">::</span><span class="nf">piped</span><span class="p">())</span>
    <span class="nf">.spawn</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

<span class="n">cmd</span><span class="py">.stdin</span><span class="nf">.write</span><span class="p">(</span><span class="cm">/* my precious, piped data */</span><span class="p">);</span>
</code></pre></div></div>

<p><code class="highlighter-rouge">commandspec</code> works on stable Rust. This is not a hard requirement for me (edit-text absolutely requires nightly for now) but it’s a good requirement for writing maintainable code generally. It accomplishes this by internally leveraging <code class="highlighter-rouge">format!</code>’s macro syntax.  As such, you can use a familiar <code class="highlighter-rouge">{}</code> syntax and get compile-type typechecking to verify your code will work. Unlike <code class="highlighter-rouge">format!</code>, <code class="highlighter-rouge">commandspec</code> pre-escapes all strings passed as arguments to the format function to be escaped for the shell:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">execute!</span><span class="p">(</span>
    <span class="s">r"
        export RUST_BACKTRACE=1
        cd my-server
        cargo run {release} -- {custom_args}
    "</span><span class="p">,</span>
    <span class="c1">// Pass options that will be omitted if None</span>
    <span class="n">release</span> <span class="o">=</span> <span class="k">if</span> <span class="n">not_debug</span> <span class="p">{</span> <span class="nf">Some</span><span class="p">(</span><span class="s">"--release"</span><span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nb">None</span> <span class="p">},</span>
    <span class="c1">// Pass vectors to expand to multiple arguments</span>
    <span class="n">custom_args</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="s">"./file1"</span><span class="p">,</span> <span class="s">"./file2"</span><span class="p">],</span>
<span class="p">)</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p>All arguments are embedded as strings. This breaks formatting features aside from positional (<code class="highlighter-rouge">{}</code>) or indexed embedding (<code class="highlighter-rouge">{0}</code>, <code class="highlighter-rouge">{release}</code>), unfortunately. Since the command line treats strings and other types indiscriminately, individually <code class="highlighter-rouge">format!</code>-ing arguments is an easy workaround.</p>

<p>Even with the release of Macros 2.0 I think this a good balance for embedding shell scripts. If these removed quotes from the macro as in <code class="highlighter-rouge">execute!(cargo run --release)?</code>, we’d have to distinguish <code class="highlighter-rouge">--release</code> from the raw tokens <code class="highlighter-rouge">-</code> <code class="highlighter-rouge">-</code> <code class="highlighter-rouge">release</code>, which is non-trivial.</p>

<h1 id="takeaways">Takeaways</h1>

<p>My approach to build scripts here evolved over about six months for one project, so I assume it’ll change in the future. However, my takeaways are that building should be 1) easy to refactor and 2) easy to interface with. In order to encourage better abstractions than shell scripting, it should be easy if not pleasant to work with executables with a programmatic API. Using <code class="highlighter-rouge">make</code> as a script runner <em>and</em> compilation tool is fundamentally a worser interface than splitting your build system into a project-centric interface, and then well-tested, domain-tailored build tools.</p>

<p>This post is also a shoutout to my favorite Rust tool, <a href="https://github.com/DanielKeep/cargo-script">cargo-script</a>. It’s brilliant for all sorts of one-off scripts, but I found it also holds up well to the task of managing entire workflows. When I started using <code class="highlighter-rouge">cargo-script</code>, it had invalidation issues that caused my script to be re-compiled each time it was run. This no longer seems to be the case, and running command line scripts (that are small enough to compile) feels similar to the workflow of running an interpreted language. If you haven’t legitimately considered Rust for scripting, <code class="highlighter-rouge">quicli</code> + <code class="highlighter-rouge">cargo-script</code> present a very flattering case.</p>]]></content><author><name>Tim Ryan</name></author><summary type="html"><![CDATA[Almost every project I’ve worked on has grown a shell script named “build.sh”, and not much later a “test.sh” and “run.sh”. At this point, you have to make a decision as a developer whether your goal is to accidentally reinvent make or if your codebase’s needs are better met by an executable to manage your workflow.]]></summary></entry></feed>