avatar

Enyert Vinas

Posted on 21st January 2022

WASM PART II: Using Rust and ClojureScript

news-paper Clojure | News | Software Development |

In the last post we studied what WebAssembly(WASM) is, it’s influence in modern web development and we presented a simple demo using WAT to WASM compilation.

In this post, we will start by solving a more complex problem ClojureScript, Reagent and Rust (more details in the next post) compiled to WASM.

Wait what? WAT is not enough?!

In the previous session we talked about WAT, but we have to tell you something: Yes, WAT is not enough because probably we will need to write more complex code to cover business requirements.

We don’t want to write complex problems using a stack and a bunch of binary operations. For this reason, we will use Rust.

What is Rust? Why so much love is coming from the community?

Rust is programming language that focus on speed, safety, concurrency and memory safe. The speed is almost naturally attached to the language, because the language ensure a low level access to memory and safety.

Rust solves problems that languages like C/C++ have been battling for a long time such as memory errors and concurrent programming.

We are convinced that safety is one of the biggest advantages of Rust. Just to mention one topic here, we have the Rust’s dual-mode model: Safe Rust and Unsafe Rust.

With Safe Rust we will have restrictions over certain parts of the code. These restrictions maintain certain parts of the code safe, to keep working properly.

On the other hand, Unsafe Rust gives more flexibility to the developers, but they have to be extra careful to ensure that their code is safe.

These high level features and many others, make Rust one of the most loved languages by developers.

Maybe we have to mention another features such as the useful packages from crates.io, but we can talk more about this topic in another article.

Given this summary about Rust, we can continue to our little game application: TiedUp!

TiedUp!

TiedUp is a simple game about guessing the missing last three characters of a word located in the right side and the initial three characters of a word located in the left side. For example, if the right word is docTOR and the left word is TORtoise, these words are tied by the three characters TOR.

In this sample project we will present to you three pairs of words loaded from an external file using rust, and we will receive this data calling the respective function from ClojureScript.

Technical Design

Technically this problem is not complicated to solve but add many topics that are interesting.

The first topic is the ClojureScript+Reagent code. We created a repository here with the initial source code. We are using here only one namespace for simplicity purposes but we accept any criticism and improvement opportunities, so fork/clone it and provide feedback please, we will appreciate it.

The second topic, is the Rust => WASM code. For this purpose, we will use wasm-bindgen. This tool provides to us the super power to compile Rust code to WASM.

In this project we will use Rust to load a *.json file with a few pair of words. It’s a pretty simple task but it will show you how to integrate Rust, WASM and ClojureScript.

This task will be explained in the next part of this post because it will require to work with serialization process for the data shared between all the layers of the architecture.

The image below presents the way we are organizing the application:

Application components

In this article, we will only talk about the client code(ClojureScript layer), but in the third part of this series we will cover the “Serde” process.

ClojureScript + Reagent – Presentation layer and Game Logic

In this layer, we are creating a simple GUI to present and control the user interaction with the game. It looks pretty much like the following image:

Basic GUI

The code architecture

The code architecture is super simple, we are using only one namespace for game logic, the core namespace.

The core namespace contains the basic application components, logic and rendering. The most important functions of this namespace are: get-pairs, verify-answer, update-game-state and create-board.

get-pairs load the data required to present the right word, the left word and the correct answer. This part will be replaced by a WASM function call for the next part.

(defn get-pairs
  "This will be replaced by rust wasm function"
  []
  [{:left-word "headache"
    :right-word "chemical"
    :answer "che"}
   {:left-word "ukelele"
    :right-word "element"
    :answer "ele"}
   {:left-word "dialogue"
    :right-word "guessing"
    :answer "gue"}])

verify-answer determines if the three characters used are correct. Almost all the logic depends on this function.

(defn verify-answer
  [first-box second-box third-box pair]
  (let [first-letter (:value (get first-box 1))
        second-letter (:value (get second-box 1))
        third-letter (:value (get third-box 1))
        answer (str first-letter second-letter third-letter)]
    (= answer (:answer pair))))

update-game-state modifies the game state everytime the user try a new answer.

(defn update-game-state
  []
  (swap! app-state assoc :actual-pair-index (+ (:actual-pair-index @app-state) 1))
  (js/alert guessed-one-word-message))

create-board returns every component of the GUI to be used by the reagent render function.

(defn create-board
  []
  (let [first-letter (character-box (:first-letter @app-state))
        first-letter-minus-button (character-change-button - first-letter :first-letter)
        first-letter-plus-button (character-change-button + first-letter :first-letter)
        second-letter (character-box (:second-letter @app-state))
        second-letter-minus-button (character-change-button - second-letter :second-letter)
        second-letter-plus-button (character-change-button + second-letter :second-letter)
        third-letter (character-box (:third-letter @app-state))
        third-letter-minus-button (character-change-button - third-letter :third-letter)
        third-letter-plus-button (character-change-button + third-letter :third-letter)
        pair (get (get-pairs) (:actual-pair-index @app-state))]
    [:div
     [:tbody
      [:tr
       [:td (left-word (:left-word pair))]
       [:td (character-component first-letter-plus-button first-letter first-letter-minus-button)]
       [:td (character-component second-letter-plus-button second-letter second-letter-minus-button)]
       [:td (character-component third-letter-plus-button third-letter third-letter-minus-button)]
       [:td (right-word (:right-word pair))]]]
     [:div (verify-button first-letter second-letter third-letter pair)]]))

How to run the project?

Please refer to the following file for instruction about running the project.

Conclusion

In this post, we shared the first part of the tiedup code covering the most important part of the presentation layer and a very basic explanation about Rust and the structure of our project.

We know the most interesting part will be the integration with Rust + WASM but don’t worry, we will cover it in the next post.

Happy Hacking! =)