This content originally appeared on DEV Community and was authored by Sean Watters
Link to my original Medium post here.
Object Oriented State Management With WebAssembly and Rust
State management in any application is always a super fun problem to solve. When it comes to integrating WebAssembly with existing applications or starting from scratch with a new project, this problem gets even more interesting, but it’s not as complicated as it may seem.
Before we jump in, I do want to make it known that this may not yet be the most performant solution for general state management (“yet” is important there). Interop between JavaScript and WebAssembly still suffers some performance limitations when it comes to serialization and de-serialization of complex data structures, but it is improving and there are proposals that are on their way that could have a significant, positive impact on performance.
Jumping In
For this example, we’re going to build a super basic counter application — you’ll be able to increment and decrement the count with “+” and “-” buttons. This will cover very surface level information and basic implementation, but won’t go deep into state management patterns like “flux with Rust,” or how to build your signup form; those are posts for another time, and I do plan on covering those topics in the next little while if folks find this walk-through helpful.
If you’re feeling like, “just show me the code!” you can view it here.
High Level
Using the diagram above, we can think about our application as being three distinct parts:
- The View — our HTML document that a user would interact with
- The Link — our JavaScript code that bridges the gap between our View and our State layer
- The State — our Rust code that worries about application state and provides an interface for our JavaScript to read and write from
The View layer is relatively simple — a couple of buttons and a <div />
to render our counter state. The JavaScript necessary for hooking up our view to our application state is mostly generated by a Rust library called wasm_bindgen, but we will still need to utilize the generated bindings in our custom JavaScript.
The state layer is the most interesting and complex of the three, but if implemented properly, can actually provide a very clean interface through which we interact with our application state.
Initial Setup
First we’re going to create a basic vanilla JavaScript, WebAssembly & Rust project. You’ll need to make sure that you have rust
installed via rustup
— instructions here. You’ll also need to make sure that wasm-pack
is installed — link here.
We’ll generate the project — if you have difficulty with this step, you may need to use sudo
.
npm init rust-webpack counter-app
Then we’re going to build and run the project — again, may need to use sudo
.
npm run build && npm run start
You should see a blank page at localhost:8080
with Hello world!
logged in the console. If you take a look in the src/lib.rs
file, the default project is using the web_sys
and wasm_bindgen
Crates (Rust libraries) to generate this message.
The Code
So now that we’ve got our project up and running, we need to actually write some code. If you’re not yet familiar with Rust, I highly recommend reading through The Book.
We’re going to use some Object Oriented Programming paradigms to start out. OOP in Rust, is an interesting animal, and isn’t necessarily the most idiomatic approach, but may be an easier transition for folks coming from OOP backgrounds. We’ll cover a more functional style in a separate post.
In our Object Oriented approach, we are going to use only Rust for state management, and won’t be using web_sys
to generate our HTML via JavaScript bindings.
Rust
First let’s create a new file called counter_state.rs
in our src
directory:
There’s a bit going on here —
First we’re creating a public Rust struct
, then we are implementing that struct
using the impl
keyword.
note: all structs with JavaScript bindings generated by wasm_bindgen must use the pub keyword.
The key indicator here that we’re using OOP-style Rust, is that in our struct
implementation, we are adding a public new()
method which will return an instance of the previously defined CounterState
struct
.
In addition to the new()
method, we have also exposed three other public methods: increment_counter()
, decrement_counter()
, and get_counter()
. The counter
property on the CounterState
struct
is private and isn’t exposed to the consumer.
Important: we will also need to add this counter_state
module to our imports in the src/lib.rs
file. Add the line: mod counter_state;
to the top of your file below the other imports.
HTML
The next step will be to update our static/index.html
file to include the <button />
elements, as well as the element where we’ll display the counter state:
JavaScript
Before we can create the JavaScript glue to connect the HTML document to the Rust state we will first need to update our package.json
file to provide access to our WebAssembly module by adding "wasm": "file:pkg"
to our dependencies — you will also need to run npm i
again.
Finally, we can add the JavaScript that will access our stateful WebAssembly module. It will go in the js/counter.js
file:
We will also need to update our js/index.js
file to import the counter.js
file, instead of the pkg/index.js
file:
In the counter.js
file, we’re importing the CounterState
JavaScript class that wasm_bindgen
has generated as a binding for our Rust struct
. The generated code looks like this:
Because we now have access to this class
we also have access to the public methods on the Rust struct
— what we’re doing on line 3 of the counter.js
file is creating an instance of the struct
in WebAssembly, and assigning it to a JavaScript variable using the new()
method we created in our counter_state.rs
file.
From here, we’re setting the initial text content of the #counter
HTML element using the get_counter()
method. We’re also adding event listeners to the <button />
elements in our HTML document, that will increment and decrement our counter’s state.
The increment_counter()
and decrement_counter()
methods both return the post-modification state of the private counter property, so we don’t need to use get_counter()
a second time.
To validate that we’ve successfully implemented the counter we run:
npm i && npm run build && npm run start
Checkout localhost:8080
and you should see something that looks like this:
Conclusion
Object Oriented state management with Rust and WebAssembly is not only very possible, it’s actually relatively straightforward to reason about. Similar to other state management solutions, you still are creating a persistent store of some kind, and making it available as a module to your renderer — but with WebAssembly modules, you can get the performance boost for computation intensive methods, added type safety, and the other features that make Rust great.
This example only covers surface level problem spaces. We’re not using complex types or having to manage serialization or de-serialization — that does make things a little more complicated, but I will be writing another post that addresses all that in the context of <form />
creation, in the coming weeks.
This content originally appeared on DEV Community and was authored by Sean Watters
Sean Watters | Sciencx (2021-05-18T19:56:18+00:00) State Management With WebAssembly & Rust. Retrieved from https://www.scien.cx/2021/05/18/state-management-with-webassembly-rust/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.