A couple of years ago I picked up the excellent Programming Rust book.
Reading how the Rust compiler enforces memory safety and avoids data-races reminded me of the AHA! moment, when I learned how Node.js makes concurrency accessible to JavaScript developers, without the synchronization headaches of multi-threaded servers.
But there's more. Rust programs have a very minimal runtime - no garbage collector or class loader. This makes Rust ideal for constrained environments like embedded systems or edge compute platforms - so watch this space.
This article covers the experience of buiding my first Rust crate.
The shortscale-rs library tries to replicate shortscale, a small JavaScript module with just one function which converts numbers to English words.
The Rust ecosystem has produced an absolutely awesome array of tools and documentation.
To get started:
Those steps also take care of 'cargo', the Rust build tool.
https://www.rust-lang.org/learn/get-started
I followed the recommendations of Jason Williams to install Rust Analyzer for VS Code instead of the default Rust extension. You'll also need CodeLLDB for debugging.
I particularly like the ability to run doctests directly in the VS Code terminal.
In JavaScript building strings is straightforward. Simply use +
to concatenate any string to any other string. Empty strings being falsy helps to write very compact logic.
The example below from shortscale.js behaves like the built-in Array.join, except that it avoids repeating separators by ignoring empty strings.
// concatenate array of strings, separated by sep, ignoring '' values
function concat(strings, sep) {
return strings.reduce((s1, s2) => s1 + (s1 && s2 ? sep : '') + s2, '')
}
Here's my first attempt to do something similar in Rust.
type Strvec = Vec<&'static str>;
// concatenate 2 Strvec's, separated with "and" if both have length
fn concat_and(v1: Strvec, v2: Strvec) -> Strvec {
match (v1.len(), v2.len()) {
(_, 0) => v1,
(0, _) => v2,
(_, _) => [v1, vec!["and"], v2].concat(),
}
}
'Why Strvec?', you might ask. In Rust, the primitive string type, used for string literals, is a str. My first thought was that shortscale-rs should manipulate collections of str's. So, instead of using String concatenation, I put str's into Vec's.
Notice the elegant match syntax - one of my favorite Rust language features. The compiler ensures that the 'arms' of the match cover all possible inputs. The result is both readable and concise. The '_' is shorthand for any value.
Performance does not matter,
until it absolutely does.
@matteocollina
The measured performance was, well, an eye-opener! ~4459ns per shortscale_vec_concat call in Rust, compared to ~1342ns for the equivalent in Node.js.
shortscale 251 ns/iter (+/- 18)
shortscale_string_writer_no_alloc 191 ns/iter (+/- 11)
shortscale_str_push 247 ns/iter (+/- 22)
shortscale_vec_push 363 ns/iter (+/- 26)
shortscale_display_no_alloc 498 ns/iter (+/- 21)
shortscale_vec_concat 4459 ns/iter (+/- 344)
shortscale_string_join 5549 ns/iter (+/- 378)
shortscale 1342 ns/iter
Clearly the v8 JavaScript engine in Node.js is working very hard to make string manipulation efficient.
The next thing I tried was to replace the Vec collections with simple Strings, creating and returning those from each function in the Rust program. This is shortscale_string_join. You should see from the benchmark, that its performance was even worse. Clearly I was doing something wrong.
Fast forward to the current implementation, which mutates a pre-allocated String rather than calling functions which create and return new Strings.
The result is significantly faster than JavaScript.
I still have a lot to learn, but this exercise was a great way to start building an intuition for Rust development and the performance of Rust primitives.
😎
To leave a comment
please visit dev.to/jldec
{ "path": "/blog/forays-from-node-to-rust", "attrs": { "title": "Forays from Node to Rust", "splash": { "image": "/images/fog.jpg" }, "date": "2021-01-10", "layout": "BlogPostLayout", "excerpt": "This article covers the experience of buiding my first Rust crate." }, "md": "# Forays from Node to Rust\n\nA couple of years ago I picked up the excellent [Programming Rust](https://www.oreilly.com/library/view/programming-rust/9781491927274/) book.\n\nReading how the Rust compiler enforces memory safety and avoids data-races reminded me of the AHA! moment, when I learned how [Node.js](https://nodejs.org/en/about/) makes concurrency accessible to JavaScript developers, without the synchronization headaches of multi-threaded servers.\n\nBut there's more. Rust programs have a very minimal runtime - no garbage collector or class loader. This makes Rust ideal for constrained environments like embedded systems or edge compute platforms - so watch [this](https://github.com/oxidecomputer) [space](https://github.com/bytecodealliance).\n\n## First impressions\n\nThis article covers the experience of buiding my first Rust crate.\n\nThe [shortscale-rs](https://github.com/jldec/shortscale-rs) library tries to replicate [shortscale](https://github.com/jldec/shortscale), a small JavaScript module with just one function which converts numbers to English words.\n\nThe [Rust ecosystem](https://www.rust-lang.org) has produced an absolutely awesome array of tools and documentation.\n\nTo get started:\n- Install Rust [using rustup](https://www.rust-lang.org/tools/install).\n- Run 'rustup update' whenever there is a new [Rust release](https://github.com/rust-lang/rust/releases).\n\nThose steps also take care of 'cargo', the Rust build tool.\n\n![Image showing cargo commands](/images/cargo.png) https://www.rust-lang.org/learn/get-started\n\n## VS Code\n\nI followed the [recommendations](https://jason-williams.co.uk/debugging-rust-in-vscode) of Jason Williams to install [Rust Analyzer](https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer) for VS Code instead of the default Rust extension. You'll also need [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) for debugging.\n\n![VS Code showing Rust program](/images/vs-code-rust.png) \nI particularly like the ability to run doctests directly in the VS Code terminal.\n\n## Rust String and str\n\nIn **JavaScript** building strings is straightforward. Simply use `+` to concatenate any string to any other string. Empty strings being [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) helps to write very compact logic.\n\nThe example below from [shortscale.js](https://github.com/jldec/shortscale/blob/main/shortscale.js#L96) behaves like the built-in [Array.join](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join), except that it avoids repeating separators by ignoring empty strings.\n\n```js\n// concatenate array of strings, separated by sep, ignoring '' values\nfunction concat(strings, sep) {\n return strings.reduce((s1, s2) => s1 + (s1 && s2 ? sep : '') + s2, '')\n}\n```\n\nHere's my [first attempt](https://github.com/jldec/shortscale-rs/blob/main/src/extra.rs#L374) to do something similar in **Rust**.\n\n```rust\ntype Strvec = Vec<&'static str>;\n\n// concatenate 2 Strvec's, separated with \"and\" if both have length\nfn concat_and(v1: Strvec, v2: Strvec) -> Strvec {\n match (v1.len(), v2.len()) {\n (_, 0) => v1,\n (0, _) => v2,\n (_, _) => [v1, vec![\"and\"], v2].concat(),\n }\n}\n```\n\n'Why Strvec?', you might ask. In Rust, the primitive string type, used for string literals, is a [str](https://doc.rust-lang.org/nightly/std/primitive.str.html). My first thought was that shortscale-rs should manipulate collections of str's. So, instead of using [String](https://doc.rust-lang.org/nightly/std/string/struct.String.html) concatenation, I put str's into [Vec](https://doc.rust-lang.org/nightly/std/vec/struct.Vec.html)'s.\n\nNotice the elegant [match](https://doc.rust-lang.org/rust-by-example/flow_control/match.html) syntax - one of my favorite Rust language features. The compiler ensures that the 'arms' of the match cover all possible inputs. The result is both readable and concise. The '_' is shorthand for any value.\n\n> Performance does not matter, \n> until it absolutely does. \n> [@matteocollina](https://twitter.com/matteocollina/status/1260887018617352192?s=20)\n\n## Benchmarks\n\nThe measured [performance](https://github.com/jldec/shortscale-rs#extra) was, well, an eye-opener! ~4459ns per [shortscale_vec_concat](https://docs.rs/shortscale/1.3.2/src/shortscale/extra.rs.html#314-336) call in Rust, compared to ~1342ns for the equivalent in Node.js.\n\n[cargo bench](https://github.com/jldec/shortscale-rs/blob/main/benches/bench-shortscale.rs)\n```\nshortscale 251 ns/iter (+/- 18)\nshortscale_string_writer_no_alloc 191 ns/iter (+/- 11)\nshortscale_str_push 247 ns/iter (+/- 22)\nshortscale_vec_push 363 ns/iter (+/- 26)\nshortscale_display_no_alloc 498 ns/iter (+/- 21)\nshortscale_vec_concat 4459 ns/iter (+/- 344)\nshortscale_string_join 5549 ns/iter (+/- 378)\n```\n\n[npm run bench](https://github.com/jldec/shortscale/blob/main/test/bench.js)\n```\nshortscale 1342 ns/iter\n```\n\nClearly the v8 JavaScript engine in Node.js is working very hard to make string manipulation efficient.\n\n## Learn & Iterate\n\nThe next thing I tried was to replace the Vec collections with simple Strings, creating and returning those from each function in the Rust program. This is [shortscale_string_join](https://docs.rs/shortscale/1.3.2/src/shortscale/extra.rs.html#389-406). You should see from the benchmark, that its performance was _even worse_. Clearly I was doing something wrong.\n\nFast forward to the [current implementation](https://docs.rs/shortscale/1.3.2/src/shortscale/shortscale.rs.html#46-61), which mutates a pre-allocated String rather than calling functions which create and return new Strings.\n\n> The result is significantly faster than JavaScript.\n\nI still have a lot to learn, but this exercise was a great way to start building an intuition for Rust development and the performance of Rust primitives.\n\n> 😎\n\n_To leave a comment \nplease visit [dev.to/jldec](https://dev.to/jldec/forays-from-node-to-rust-3fk1)_\n\n", "html": "<h1>Forays from Node to Rust</h1>\n<p>A couple of years ago I picked up the excellent <a href=\"https://www.oreilly.com/library/view/programming-rust/9781491927274/\">Programming Rust</a> book.</p>\n<p>Reading how the Rust compiler enforces memory safety and avoids data-races reminded me of the AHA! moment, when I learned how <a href=\"https://nodejs.org/en/about/\">Node.js</a> makes concurrency accessible to JavaScript developers, without the synchronization headaches of multi-threaded servers.</p>\n<p>But there's more. Rust programs have a very minimal runtime - no garbage collector or class loader. This makes Rust ideal for constrained environments like embedded systems or edge compute platforms - so watch <a href=\"https://github.com/oxidecomputer\">this</a> <a href=\"https://github.com/bytecodealliance\">space</a>.</p>\n<h2>First impressions</h2>\n<p>This article covers the experience of buiding my first Rust crate.</p>\n<p>The <a href=\"https://github.com/jldec/shortscale-rs\">shortscale-rs</a> library tries to replicate <a href=\"https://github.com/jldec/shortscale\">shortscale</a>, a small JavaScript module with just one function which converts numbers to English words.</p>\n<p>The <a href=\"https://www.rust-lang.org\">Rust ecosystem</a> has produced an absolutely awesome array of tools and documentation.</p>\n<p>To get started:</p>\n<ul>\n<li>Install Rust <a href=\"https://www.rust-lang.org/tools/install\">using rustup</a>.</li>\n<li>Run 'rustup update' whenever there is a new <a href=\"https://github.com/rust-lang/rust/releases\">Rust release</a>.</li>\n</ul>\n<p>Those steps also take care of 'cargo', the Rust build tool.</p>\n<p><img src=\"/images/cargo.png\" alt=\"Image showing cargo commands\"> <a href=\"https://www.rust-lang.org/learn/get-started\">https://www.rust-lang.org/learn/get-started</a></p>\n<h2>VS Code</h2>\n<p>I followed the <a href=\"https://jason-williams.co.uk/debugging-rust-in-vscode\">recommendations</a> of Jason Williams to install <a href=\"https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer\">Rust Analyzer</a> for VS Code instead of the default Rust extension. You'll also need <a href=\"https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb\">CodeLLDB</a> for debugging.</p>\n<p><img src=\"/images/vs-code-rust.png\" alt=\"VS Code showing Rust program\"><br>\nI particularly like the ability to run doctests directly in the VS Code terminal.</p>\n<h2>Rust String and str</h2>\n<p>In <strong>JavaScript</strong> building strings is straightforward. Simply use <code>+</code> to concatenate any string to any other string. Empty strings being <a href=\"https://developer.mozilla.org/en-US/docs/Glossary/Falsy\">falsy</a> helps to write very compact logic.</p>\n<p>The example below from <a href=\"https://github.com/jldec/shortscale/blob/main/shortscale.js#L96\">shortscale.js</a> behaves like the built-in <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join\">Array.join</a>, except that it avoids repeating separators by ignoring empty strings.</p>\n<pre><code class=\"language-js\">// concatenate array of strings, separated by sep, ignoring '' values\nfunction concat(strings, sep) {\n return strings.reduce((s1, s2) => s1 + (s1 && s2 ? sep : '') + s2, '')\n}\n</code></pre>\n<p>Here's my <a href=\"https://github.com/jldec/shortscale-rs/blob/main/src/extra.rs#L374\">first attempt</a> to do something similar in <strong>Rust</strong>.</p>\n<pre><code class=\"language-rust\">type Strvec = Vec<&'static str>;\n\n// concatenate 2 Strvec's, separated with "and" if both have length\nfn concat_and(v1: Strvec, v2: Strvec) -> Strvec {\n match (v1.len(), v2.len()) {\n (_, 0) => v1,\n (0, _) => v2,\n (_, _) => [v1, vec!["and"], v2].concat(),\n }\n}\n</code></pre>\n<p>'Why Strvec?', you might ask. In Rust, the primitive string type, used for string literals, is a <a href=\"https://doc.rust-lang.org/nightly/std/primitive.str.html\">str</a>. My first thought was that shortscale-rs should manipulate collections of str's. So, instead of using <a href=\"https://doc.rust-lang.org/nightly/std/string/struct.String.html\">String</a> concatenation, I put str's into <a href=\"https://doc.rust-lang.org/nightly/std/vec/struct.Vec.html\">Vec</a>'s.</p>\n<p>Notice the elegant <a href=\"https://doc.rust-lang.org/rust-by-example/flow_control/match.html\">match</a> syntax - one of my favorite Rust language features. The compiler ensures that the 'arms' of the match cover all possible inputs. The result is both readable and concise. The '_' is shorthand for any value.</p>\n<blockquote>\n<p>Performance does not matter,<br>\nuntil it absolutely does.<br>\n<a href=\"https://twitter.com/matteocollina/status/1260887018617352192?s=20\">@matteocollina</a></p>\n</blockquote>\n<h2>Benchmarks</h2>\n<p>The measured <a href=\"https://github.com/jldec/shortscale-rs#extra\">performance</a> was, well, an eye-opener! ~4459ns per <a href=\"https://docs.rs/shortscale/1.3.2/src/shortscale/extra.rs.html#314-336\">shortscale_vec_concat</a> call in Rust, compared to ~1342ns for the equivalent in Node.js.</p>\n<p><a href=\"https://github.com/jldec/shortscale-rs/blob/main/benches/bench-shortscale.rs\">cargo bench</a></p>\n<pre><code>shortscale 251 ns/iter (+/- 18)\nshortscale_string_writer_no_alloc 191 ns/iter (+/- 11)\nshortscale_str_push 247 ns/iter (+/- 22)\nshortscale_vec_push 363 ns/iter (+/- 26)\nshortscale_display_no_alloc 498 ns/iter (+/- 21)\nshortscale_vec_concat 4459 ns/iter (+/- 344)\nshortscale_string_join 5549 ns/iter (+/- 378)\n</code></pre>\n<p><a href=\"https://github.com/jldec/shortscale/blob/main/test/bench.js\">npm run bench</a></p>\n<pre><code>shortscale 1342 ns/iter\n</code></pre>\n<p>Clearly the v8 JavaScript engine in Node.js is working very hard to make string manipulation efficient.</p>\n<h2>Learn & Iterate</h2>\n<p>The next thing I tried was to replace the Vec collections with simple Strings, creating and returning those from each function in the Rust program. This is <a href=\"https://docs.rs/shortscale/1.3.2/src/shortscale/extra.rs.html#389-406\">shortscale_string_join</a>. You should see from the benchmark, that its performance was <em>even worse</em>. Clearly I was doing something wrong.</p>\n<p>Fast forward to the <a href=\"https://docs.rs/shortscale/1.3.2/src/shortscale/shortscale.rs.html#46-61\">current implementation</a>, which mutates a pre-allocated String rather than calling functions which create and return new Strings.</p>\n<blockquote>\n<p>The result is significantly faster than JavaScript.</p>\n</blockquote>\n<p>I still have a lot to learn, but this exercise was a great way to start building an intuition for Rust development and the performance of Rust primitives.</p>\n<blockquote>\n<p>😎</p>\n</blockquote>\n<p><em>To leave a comment<br>\nplease visit <a href=\"https://dev.to/jldec/forays-from-node-to-rust-3fk1\">dev.to/jldec</a></em></p>\n" }