Carrying the momentum
Let us catch up, we in our last dispatch have created a simple vector addition program. You would recollect vector arithmetic is fundamental to many machine learning and advanced programming. We took a modest first step in that direction. In this dispatch we carry that momentum and take it forward by exposing this work in web app using WebAssembly. We had a basic introduction to WebAssembly in the last dispatch. Please do take a moment to browse through that in case you haven’t already.
Story so far
Quick recap could be helpful. So, we have created project using cargo command –
cargo new vector_arithmetic |
Following that we added dependency of a crate. The one that exposes the data type ndarray. This was required to make the library easy for consumption. We wrote the code and verified our production by compiling the code as follows –
cargo build |
Followed by
cargo run |
Now let us expand towards the web and prepare our tiny library (to begin with) be ready towards that goal.
Declare your type!
When we started, we did not bother much about the type of output that our cargo build command will yield. We were focused on doing that simple vector operation that we could use in our javascript projects.
For us to get to that step, we would require to declare our output to be a library instead of a binary which we can execute in a console. We could have done that easily by the following switch while creating the project –
cargo new vector_arithmetic –lib |
However, we cannot go in the past and change that. All is not lost :). Our miracle saviour is the toml file. We need to add –
[lib]
crate-type = [“cdylib”] [dependencies] ndarray= “0.13.1” //We had this earlier wasm-bindgen = “0.2” |
Had we used the lib switch we would have obtained the crate-type and wasm-bindgen dependencies in the toml by the cargo finished creating the project. But by this way we now have learnt what is required for creating the library in rust.
Next, we need to tell the runtime outside of the rust runtime to learn about what external library can invoke. This is performed by creating the lib.rs file. This is very typical to the type definition in typescript or the export calls you would have written in the node application.
Publish your contract
We will be creating the lib.rs file in the src directory. Needless to say the cargo new with lib switch would have created the scaffolding of this file. However, we will be changing the scaffolding code by and large –
mod vector_operation;
use wasm_bindgen::prelude::*; use std::convert::TryFrom; #[wasm_bindgen] extern { fn alert(s: &str); } #[wasm_bindgen] pub fn add_vectors(left_dim1: &[i32], right_dim1: &[i32]){ let left = <&[i32;3]>::try_from(left_dim1); let right = <&[i32;3]>::try_from(right_dim1); alert(&vector_operation::add(&[*left.unwrap()], &[*right.unwrap()]).to_string()) } |
In this declaration we are introducing ourselves to two new concepts. Primary one is that we are making rust aware of javascript function. Next important step is we are letting javascript engine know something that is declared and defined within rust.
The javascript function that we are referring to is alert. In this simplistic implementation we have used the standard way (of probably little old) of development. We alert the user on something. This is defined in the javascript library. The keyword to focus here is the “extern”.
The rust function that we mentioned earlier is defined in the library here. It is called as add_vectors. Inside the function it invokes the add method we defined in the earlier dispatch. It converts the output to string and invokes the javascript function alert.
This is best example for both rust to javascript and javascript to rust invocation.
If you have noticed, we are also introducing to attributes here. Never mind, the syntax. The one used here with the character “#”. Concept of attributes, however, will be familiar to you from the classic programming languages i.e. java or .NET. The attribute in focus here is the wasm_bindgen. This attribute instructs the rust compiler to consider a logical unit that succeeds this declaration to be something that it should parse and let the different worlds know (world of rust and javascript).
For the attribute to be recognizable, like in many other programming languages we have imported the symbols. This attribute resides in the wasm_bindgen::prelude::* scope.
Within the add_vectors function there is a lot that is happening. It might be too much to digest for a developer not exposed to rust. Be assured, that it is just interplay of data types and limitations present at the time of writing this article on wasm.
Ideally for a function like add_vectors one would expect same data type being used as parameters as that of the function that it is calling. However, the limitation of wasm supported data type for serialization, array is not supported. The closest data type that is supported is slice. We use the standard crate to initialize an array from slice data type. In this process we assume (though not recommended as we progress with complex rust programming) that the calling javascript app will do justice and limit the array i.e. the vector to be of dimensions 3,1. While we wrangle with the data type, one should also notice; primarily; on the pointer and value type interplay. i.e. the use of symbols “ & ” and “ * ”.
Do not be scared! This is not to haunt you or to give you nightmare on null pointer errors etc. This language uses these symbols to exercise control and power over the operations.
Check is “All well”!
We are almost there. Compile and obviously link the program to see if all is well.
cargo build |
If all was good you should see the following printed on the console.
warning: output filename collision.
The bin target `vector_arithmetic` in package `vector_arithmetic v0.1.0 (C:\Users\Author\Documents\GitHub\vector_arithmetic)` has the same output filename as the lib target `vector_arithmetic` in package `vector_arithmetic v0.1.0 (C:\Users\Author\Documents\GitHub\vector_arithmetic)`. Colliding filename is: C:\Users\Author\Documents\GitHub\vector_arithmetic\target\debug\deps\vector_arithmetic.pdb The targets should have unique names. Consider changing their names to be unique or compiling them separately. This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>. warning: output filename collision. The bin target `vector_arithmetic` in package `vector_arithmetic v0.1.0 (C:\Users\Author\Documents\GitHub\vector_arithmetic)` has the same output filename as the lib target `vector_arithmetic` in package `vector_arithmetic v0.1.0 (C:\Users\Author\Documents\GitHub\vector_arithmetic)`. Colliding filename is: C:\Users\Author\Documents\GitHub\vector_arithmetic\target\debug\vector_arithmetic.pdb The targets should have unique names. Consider changing their names to be unique or compiling them separately. This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>. Finished dev [unoptimized + debuginfo] target(s) in 1.16s |
Do not worry about the warnings. We will not be bothered much with that till we advance bit deeper with crates and multiple modules in a project.
Now that all is well, let us go and pack it to a wasm module which can be consumed by a javascript library.
wasm-pack build |
The default target of this build will be a wasm32-unknown-unknown. We will delve much upon the considerations of that in a latter dispatch.
You can inspect the pkg folder which is the outcome of the wasm-pack command. Go ahead and explore the files there. We will pick your from right there in our next dispatch.