Some time ago
Fairy tale starts with “Once upon a time” but this is recent past. So, we start with some time ago. Additionally, this is not long back in time that WebAssembly was ratified as standard. Rust also gained popularity in the recent past. That said let us bring few things from our past dispatches for our context in the current dispatch.
WebAssembly is a standard which allows browsers to understand the binary code format thus allowing binary code execution in the browser.
To gain perspective, right now browsers support markup languages and script both of which are not compiled. They are interpreted. Okay, for the new breed of developers who started directly with Angular, ReactJS sort of environments; what you do with Angular CLI is not compiling. It is transpiling.
Rust on the other hand is a powerful new programming language and shot to fame by open expression of admiration by Microsoft for this language recently.
Rust is used for various scenarios from traditional application development, embedded programming and yes for the scenarios like Web Assembly.
Let us now focus more on Rust as WebAssembly will boil down to more final action
Talk about the introduction we had given in our earlier dispatch for both WebAssembly and the programming language Rust
Rust again!
Diving deeper into the ocean requires preparation. We will do this by motivating ourselves on why we should use Rust with WebAssembly.
In the binary realm i.e. of WebAssembly, javascript drops the ball by becoming clunky and difficult to steer. That is developer has to write a lot and it is unpredictable with garbage collection. Put in different words for the garbage collection comment; developers do a small quirky change in code (which by the way is a correct functional code) performance could degrade much worse.
Being a compiled language and the best part natively compiled to a WASM (i.e. WebAssembly) the .wasm file need to carry the weight of an interpreter or a garbage collector. Let us untangle that. Let us take python as the language that one wants to write WASM. Now, if we write it in python language we know that can run it only if we the python interpreter i.e. the python command on the command line. Now, visualize how can that happen in the browser. One will need the binary for the python executable i.e. the interpreter itself to be in the browser. That can happen only if you compile the python ground up and yes push in to the WAS file itself. Now, we see what it means to write in a compiled language. i.e. language like Rust for this dispatch. Other viable choices are C or C++. Yes, they are very good candidates but let them park for a different dispatch.
We hope that is more than attractive reasons to start programming for web assembly. If these reasons are not sufficient; we can throw this on the table which any developer cannot neglect to pick – You can write code in Rust which you can target for a desktop or in browser. Isn’t that like write once and run everywhere parallel.
We hope this should be more than enough now let us proceed to the journey.
Let us hit the road
A journey into an unknown land teaches us many things. To capitalize that learning we also have to keep our mind open to new possibilities. Let us dive in creating vector operations in Rust using WebAssembly.
Hope by now you would have installed the rust toolchain which involves rustup, rustc, and cargo.
Provided we have them we will need additionally wasm-pack, cargo-generate for rust whereas we will use the standard npm for the javascript dependency management.
You can install both the dependencies of rust using the cargo command –
cargo install [wasm-pack] |
We get started by creating the directory and initializing the project –
cargo new vector_arithmetic |
You should have obtained a new folder named vector_arithmetic with GIT enabled by default in that folder. Now let us get to the business. Within this folder the toml file contains metadata for the project and the dependencies. We will now add crate of ndarray which is specialized for vector and matrix operation to our project. We do that by adding ndarray to the dependency in the Cargo.toml file like this –
[dependencies]
ndarray = “0.13.1” |
Post this addition let us run the build command to compile the basic code and also include the ndarray to our project structure –
cargo build |
Let us create a file called vector_operations.rs inside src folder. Let us add these following lines to that file.
use ndarray::arr2;
use ndarray::Array2; pub fn add(arr_one: &[[i32;3];1], arr_two: &[[i32;3];1]) ->Array2<i32> { let nda1 = arr2(arr_one); let nda2 = arr2(arr_two); let sum = &nda1 + &nda2; return sum; } |
This is a fairly simple function from a complexity point of view. This is only to begin with. There are few nuances to this function. E.g. modules, data types, macros, traits and functions as such. Let us start one by one.
The code imports two types i.e. arr2 and Array2. These are types which are part of the ndarray crate. The ndarray is the same crate that includes the same it in toml file. We use functions as primary ways of introducing feature to the library. Function is declared with the keyword fn. We in this declare the function as pub which stands for public function. A function that can be accessed from any other rust file. Within the function signature we have a complex looking text which is actually two variables arr_one and arr_two.
Function parameters in this case has only one signature i.e. datatype of the variable. We use two multidimensional arrays. The multi-dimensional array is represented within nested square brackets [ [ ] ]. The data type of the multidimensional array is placed within the first square bracket. The trait which stands equivalent to a class in object-oriented paradigm mandate us for using the arr2 function we will need a fixed size array for all dimensions. That constraint enforces us to define the size, we define 3 for the columns and 1 for the row of 2-dimensional array.
The function returns the value which is a datatype. The ndarray crate has been used here to refer the function called arr2 and the type Array2. Thus, the function arr2 yields a type Array2. The return type should use the type and not the function. Array2 type is qualified by the type of contained elements like generics if you have coded in say .NET or in Java.
Variables are declared with the use of keyword ‘let’. Value is returned from the function using the keyword ‘return’.
Now let us take a look at the main.rs. This is a file which you will have noticed already. This was created when we initialized the project.
mod vector_operation;
fn main() { println!(“{}”, vector_operation::add(&[[1, 2, 3]], &[[4, 5, 6]])); println!(“Hello, world!”); } |
The line which prints “Hello, world!” was gifted to us by cargo. We added only couple of lines. First, we declared a local module. Module are organisational units of the codebase. They are close equivalent to namespace in object-oriented code.
Module name i.e. the text after the keyword “mod” has to be the same one as the file name which is placed next to main.rs in the same folder!
Within a module we can then call all public functions available in that module. Modules also help us have an easy approach to build. i.e. we do not have to compile each file and then link etc. Also, since we are using cargo, we can get the following snippet run the code for us –
cargo run |
One last important that cannot be overlooked here is the use of “!” mark after the println function call. In rust it is a macro. For developers with C and C++ background would have got the outline of what macros are used for but for developers with .NET and Java background; macros are code which generates high level code as output. i.e. println macro will generate rust code which is run before the code is compiled. Then this generated code is compiled. You can explore more in rustdocs if you are still interested in knowing more about macro.
The above code should print an output like this –
Finished dev [unoptimized + debuginfo] target(s) in 0.65s
Running `target\debug\vector_arithmetic.exe` [[5, 7, 9]] Hello, world! |
We have only so far got the code which we will need at the least the basic part of it. Next, we will need to pack it for wasm and launch this code in browser. We will take a pause now, and resume in the next immediate dispatch for the wasm code.
Picture courtesy – https://markusspiske.com/