Introducing Smithy — A WebAssembly framework for Rust

I’m extremely excited to announce the 0.0.2 release of Smithy, a web development framework for Rust! While it is a very pre-alpha version, it should be functional enough for others to start playing around with. Please, get your feet wet and provide feedback.

In this post, we’ll make the case for Smithy and take a whirlwind tour of its internal workings. We’ll learn how Smithy creates components and manages their lifecycles, before diving into some advanced topics and gotchas.

What is Smithy?

Smithy is a framework for writing WebAssembly applications entirely in Rust. Its goal is to allow you to do so using ergonomic, idiomatic Rust, without giving up any of the compiler’s safety guarantees.

You can see an example site, written entirely in Rust, at: You can see the source code here:

Please also see this README to learn how to build and deploy your first Smithy site.

A simple example

A basic click counter is as follows:

That’s it?

Yup! That’s all the code it takes to write a simple Smithy app!

Check out this readme for detailed instructions on how to run your Smithy app locally.

The important parts are:

  • You exported a public function that was annotated with #[wasm_bidgen]
  • You created a smithy app using the smd! macro
  • You passed this app to smithy::mount

Whirlwind tour

Wow! That’s simple. How does the smd! work under the hood?

At it’s simplest, the smd! macro returns a Box<FnMut(Phase) -> PhaseResult that can execute the app's behavior at various times, called phases. The most important of these are the rendering and event handling phases.

If we expand the above macro (by passing smithy the "smd-logs" feature), we see that it expands into:

(This is a simplified representation, of course.)

What does smithy::mount do with app?

Once you pass your app to smithy::mount, the framework takes over. It keeps track of which phase you are in, and executes the appropriate code.

When Smithy enters a particular phase, app(phase) is called. The phases are as follows:

  • Rendering: First, Smithy will ask the app for the "virtual DOM" representing how that app would like to be rendered. Smithy then writes that to the DOM.
  • RefAssignment: Next, Smithy will take any ref's passed to dom nodes (as ref={ref /* &mut Option<web_sys::HtmlElement> */}) and assign them.
  • PostRendering: Next, Smithy executes any code that must happen after the DOM has been updated, or after you have a reference to the actual DOM node. Examples include modifying the value of an input in response to changes in state.
  • At this point, Smithy waits for events. Smithy can respond to DOM events (such as clicks, in the UiEventHandlingphase) and window events (such as hash change events, in the WindowEventHandling phase.) After an event occurs, Smithy will re-render the app.

The smd! macro is nothing but a way of re-arranging your code into the above five phases. For example:

Mutable and immutable references

Notice that in the original example (<div on_click={|_| count = count + 1}>{ count }</div>), there was a mutable reference to count (in |_| count = count + 1) and an immutable reference ({count}). This would normally not be allowed by the compiler.

However, when these statements are re-arranged by the smd! macro, they end up in separate match arms. Thus, we never run afoul of the borrow checker.


There you have it! That’s a very brief explanation of how Smithy works under the hood. Go try it out!

Advanced topics

What is valid smd! syntax?

Here is an smd! macro invocation that shows off many facets of the supported syntax:

Why does the smd! macro create a Box<FnMut> instead of a struct?

Imagine if the above had turned into a struct. A fictionalized and simplified version of this could be:

Nice and simple, right?

But notice that in this struct, we have both a mutable reference to count (via the on_click event_handler) and an immutable reference (as DomElement::String(count.to_string())). The compiler will prevent this from being compiled.

Early attempts at Smithy behaved like this.

How are Dom elements represented?

When app(phase::Rendering) is called, a PhaseResult::Rendering(Node) is returned.

This Node is then turned into a Vec<CollapsedNode>, which is a similar struct, but with all adjacent String nodes concatenated and which has no Node::Vec elements.

How does Smithy know what to update? Does it re-render the entire DOM tree?

No. Smithy will calculate the diff between the Vec<CollapsedNode>'s from the last and current rendering phases, and only update the DOM where it has actually changed.

How does interpolation work?

Interpolation is when you include another value in curly brackets. For example:

Anything: other components, values, function calls, etc. can be interpolated in this way, as long as the return value implements smithy::Component.

Many common types have smithy::Component implemented for them already.

What gotchas are there with interpolation?

The smd! macro cannot look within a set of curly brackets to discover what type of item we are interpolating. As far as smd! is concerned, { plane } is a black box. Since it doesn't know whether it's a component (which can respond to events) or a simple value (which usually does not), { plane } will be repeated many more times in the expanded macro.

Thus, in the interest of file size, it may be better to include less text in an interpolation, when possible.



More interpolation gotchas

The following will compile:

As will the following:

But, the following will not compile:

The compiler will complain that you cannot move out of captured variable in an FnMut closure. What gives? If the first two compiled, so should the third!

However, the smd! macro cannot split the interpolated value ({ inner(&mut count )}) by phase. Thus, it will need to be evaluated in the Rendering phase, along with { count }, and referenced simultaneously in the same Node. This violates the borrow checker's rules.

Okay, so what do I do?

In situations like that, I would recommend wrapping the data in an Rc<RefCell<T>>, as in the following:

This is a huge usability concern and I hope to fix this in a future version of Smithy!

How do I call child components?

Components are interpolated either as values:

or by calling them in the smd! macro:

What are some gotchas when using child components?

There is a subtle error in the following code:

The error is that render_child_component is called every phase change. Thus, input_ref is set to None at the beginning of the RefAssignment phase, and again at the beginning of the PostRendering phase. Thus, the inside of the if Some(el) = input_ref block will never be called!

The solution to this is to store the ref at a higher level and pass it in as a parameter to render_child_component.

What about futures?

You can transform a future into an UnwrappedPromise (see for example, this). An UnwrappedPromise will then notify smithy to re-render when the future completes or errors out. UnwrappedPromise's can be used in a match statement, as well.

Does Smithy compile on stable Rust?

No, but I would love for it to be able to one day. There are a few proc-macro-related features that would need to stabilize, first.

I’m currently developing Smithy on rustc 1.32.0-nightly (6f93e93af 2018-11-14).

I want to be involved!

I’d love to hear from you! Tweet at me @statisticsftw!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store