Working with API Requests in Smithy v0.0.6

Robert Balicki
6 min readAug 9, 2019

I’m extremely excited to release Smithy v0.0.6. Since v0.0.3, the following major improvements have been made:

smd_no_move has been renamed to smd_borrowed.

The results of calls to smd! and smd_borrowed! are now cached in ~/.smd. Caching will (hopefully) decrease compile times.

create-smithy-app has been released! Getting started is as easy as npm init smithy-app my_smithy_app && cd my_smithy_app && npm start!

Smithy is a framework for building WebAssembly apps using idiomatic Rust. Learn more about Smithy or dive into the source code.

I don’t have a soundcloud to promote, but that shouldn’t stop this tweet from going viral

But that’s not what this blog post is about! In this blog post, we’ll go over making API calls in Smithy and rendering the results, using the Smithy iTunes music search website (source) as a reference.

This site allows you to query the iTunes API and look, in more detail, at an individual result. My hope is that this example is simple enough to follow, yet advanced enough to show off some of Smithy’s features.

If you’d like to run the Smithy iTunes search locally, follow the instructions in the README to get started with local development. However, this blog post should still be understandable if you only follow along with the source code.

Using Rust, WebAssembly and Smithy to search for your favorite songs is not* overkill!
Go, Smithy, go! Look up those songs!

What makes working with API calls difficult?

Before we dive into the code, lets take a second to understand why working with API calls is difficult in Rust and WebAssembly at the moment.

Immature ecosystem

First, the Rust to WebAssembly ecosystem is fairly immature, though getting better by the day. As far as I know, at the current time, there are no crates that expose a simple API for making AJAX requests. Thus, we’ll be using awkwardly low-level primitives when actually making the API request.

Problems with lifetimes

Second, there is the issue of how to work with promises. A promise can resolve at any time. Therefore, it can contain no references to non-'static variables. Thus, it cannot modify any variables that it does not own in a callback.

To illustrate, the following idiomatic Javascript would not be straightforward to translate into Rust without complaint from the borrow checker:

For one, the user may have navigated away from the home page, and stateOnHomePage will have been deallocated and dropped. We may get an error similar to:

closure may outlive the current function, but it borrows `stateOnHomePage`, which is owned by the current function

Instead, if we moved the stateOnHomePage variable into the closure and attempted to access it in a render method, the borrow checker would complain that we were attempting to use a moved variable. Alas! Stymied at every turn.

What will we do?

The solution, of course, is to use interior mutability. We will solve this problem by moving the stateOnHomePage variable into the promise, but controlling access to it using an Rc<RefCell>. To do this, we will use Smithy's unwrapped_promise_from_future method to turn those futures into an object we can render as follows:

https://gist.github.com/06339b766e8bb1d0140f5a003f917026

Note that we are calling clone here. As of right now, this papercut is necessary, though I'm sure that a future version of Smithy will be able to avoid this.

Why must we call &*unwrapped_promise.borrow()?

unwrapped_promise.promise_state has type Rc<RefCell<PromiseState<S,E>>>. It is held in an Rc<RefCell> because the promise's callbacks need to be able to mutate the PromiseState. We also need to be able to access the PromiseState in order to be able to render it.

There is no clear owner of promise_state, hence we rely on runtime-checked interior mutability.

Learn more about interior mutability.

Why must we clone?

In an ideal world, we would not need to! However, right now, we cannot move the variable out of the promise state, as we are only borrowing the internal value.

We also cannot return a reference, as the reference will go out of scope at the end of the match statement.

I’m impatient. Let’s dive into the code!

Ok, let’s go!

Let’s start by looking at the main() function in app/lib.rs. This function is annotated with #[wasm_bindgen(start)], which instructs wasm_bindgen to execute this function when the module loads.

Then, it gets a reference to a node with id #app, and creates a SmithyComponent by calling app::render(). Then, it calls smithy::mount(Box:new(app), node), telling Smithy to start running this app at this node.

This is the bare minimum to start Smithy. All of the magic happens in the render method.

Creating the SmithyComponent

Now, let’s look at the render method in src/app.rs. Some things you'll notice:

  • We use the smd! macro to create a SmithyComponent, which we return. It should look familiar to those who have seen JSX.
  • The pieces of app state (page, search_api_call_opt and detail_api_call_opt) are stored as variables that are then moved into the returned SmithyComponent. The Smithy framework neither knows nor cares about what state its components are managing.
  • There is nothing special about this render method. It can take any parameters and be called anything.
  • render is only ever called once. The returned SmithyComponent contains all of the information Smithy needs to manage it throughout the lifecycle of the app.
  • Inside of curly braces, you see regular Rust. Anything that implements the Component trait can be interpolated in this way. In fact, that's what we're doing when we're calling match page.

Let’s look at what happens when we’re on the search page (which is the home page).

Updating the API call state

If the page variable matches Page::Search, we call render_search(&mut page, &mut search_api_call_opt). Let's look at that function.

When the user types, we call *search_api_call_opt = Some(smithy::unwrapped_promise_from_future(crate::api::search(&value)));. (&value is the current text in the input.)

We then store the UnwrappedPromise in an Option<UnwrappedPromise>.

Making the API call

Let’s take a closer look at what crate::api::search(&value) does by looking at api.rs. This methods in this file closely follow the wasm-bindgen fetch example. The nitty gritty is unimportant. Things to note:

We’re returning a plain-ol impl Future here. This means that the techniques outlined here will work with any other future (as long as its associated Item and Error items implement Clone).

— This file is unnecessarily low level. Much of this complexity can and should be abstracted away by another crate.

Rendering the results of the API call

The reason we are calling unwrapped_promise_from_future, consuming the Future, and storing an UnwrappedPromise (in an option), is that the UnwrappedPromise can easily be rendered and will tell Smithy to re-render at the appropriate time.

So, in order to actually do the rendering, we have the following code:

Remember that any time we are interpolating something in a smd! macro, the value of that block must implement Component. In our case, this entire block has the type Option<SmithyComponent>, which implements that trait.

The search_api_call_opt.as_ref().map(|ref mut search_api_call| ...) is a way of converting an &Option<UnwrappedPromise<S, E>> into an Option<&UnwrappedPromise<S, E>>, and then map ping the inner value into something else.

Looking back. What went well? What went poorly?

Okay, that was a whirlwind tour of making and updating an API call in a Smithy application. Let’s take stock of what went well:

  • We, in a completely type-safe fashion, made API calls,
  • These API calls are completely self-contained, so there is no possibility that an API call will mutate state it is not supposed to, leading to a UI that is out of sync,
  • And, there was nothing non-Rusty about how we stored and manipulated our data.

But all is not well. There were of course a few paper cuts:

  • The results of the API call are stored in a Rc<RefCell>, and thus require interior mutability.
  • In order to render the results of the API call, we must call clone.

Hopefully, a future version of Smithy (or another crate) can figure out how to tackle these issues. In particular, the cloning does not seem to me to be necessary in theory.

Lastly, we stored the api call as a top-level variable, when in fact, it is only used on an individual page (the Search page). It would have been ideal to store the Future on the Page enum, and extract it using a match, like so:

(However, future.shared() implements clone. We may yet have hope!)

Conclusion

I hope this example has shown you that it is possible and easy to write idiomatic Rust and develop WebAssembly apps, even when dealing with Futures! In particular, I hope that by including first-class support for rendering Future s in a declarative fashion, Smithy will make it even easier to write correct, asynchronous front-end code.

Although there is a lack of comprehensive examples and tutorials on the smithy website, hopefully this example is enough to inspire you to hack around!

Smithy is looking for contributors!

Please, tweet at me @statisticsftw, email me or open an issue! I’d love to hear from you and help you get involved.

--

--