Let’s build a JavaScript Engine in Rust

Have you wondered how JS engines work? This past year I built an engine from scratch in Rust. It was fun, weird, exciting and sometimes exhausting. I will share my experience as well as what it is like to work on the specification, collaborate with TC39, and lessons from engines in use today.

Thanks for reading

If you liked this post, please do share/like it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading about JavaScript

The Complete JavaScript Course 2019: Build Real Projects!

Programming in Rust: the good, the bad, the ugly.

The cost of JavaScript in 2019

JavaScript vs Python : Can Python outperform JavaScript in the next five years?



Rust for JavaScript Developers

Rust for JavaScript Developers

Rust is a multi-paradigm system programming language focused on safety, especially safe concurrency. In this post, I am gonna present Rust from a JavaScript developer perspective doing some side-to-side comparisons. I hope it will motivate you to give it a try!

The first time I read about Rust 🦀 it didn't pique my interest. Around two years ago, I was working as a web developer, mostly using JavaScript and I thought Rust wasn't for me because it looked really difficult at that moment.

At the start of this year, I decided to start learning Rust on my own. What has changed during this time? I am still a web developer but knowing that I will be able to write a program in Rust, compile to WebAssembly and execute it in the browser has been the spark that ignited my motivation.

In this post, I am gonna present Rust from a JavaScript developer perspective doing some side-to-side comparisons. I hope it will motivate you to give it a try!

What is Rust?

The Rust programming language was created by Mozilla and its first stable version was released around 2015. It looks like this:

fn main() {
    println!("Hello, World!");
}

It doesn't look so scary, right? You could say it almost looks like JavaScript but that's just the hello world program and it's a bit more complicated! Before looking at some features of the language let's place Rust in the programming languages spectrum like this:

[

There is a clear distinction between programming languages:

  • Low-level: Languages like C++ that has access to memory management are considered low level and they are very fast. They are also very insecure because it's very easy to mess up with memory and bad things can happen!
  • High-level: On the other hand, languages like JavaScript don't have that granular access to memory (there is the Garbage Collector that handles everything for us) and are considered safe languages so sometimes they can be slow.

Rust can be considered both fast and safe but it doesn't come for free: there is a steep learning curve and the compilation time can be a bit high even for small examples.

Rust key features

Like any programming language, there is a lot to cover but I decided to focus on four topics that are essential to learn the language and start working on it.

Types

JavaScript is a dynamically typed language, and we can do some fun things like subtracting the number 1 to the string wat and obtain unexpected results. This is possible because the type system is not strict. In Rust, if you try to perform a simple addition of two numbers that are not of the same type you get a compilation error:

fn main() {
    let a: i32 = 42;
    let b: f64  1.0;
    println!("{}", a + b); // ERROR: a and b are not of the same type.
}

You will get a lot of errors when you start working in Rust and you will probably hate the compiler at first:

If you feel like this dog and you are constantly fighting the Rust compiler don't worry! We all have been there.

Immutability

Functional languages are very well-known for working with immutable structures. As JavaScript developers, we are not forced to work with immutability but popular libraries like Redux and Immutable.js taught us these good practices. Today we have the let and const keywords to declare mutable and immutable variables respectively.

In Rust, we are gonna use just let to declare variables, and they will be immutable by default. If we want to use mutable data we need to add the mut keyword to the declaration like this:

fn main() {
    let a = 42;
    let mut b = 1;

    a = 43; // ERROR: a is not mutable
    b = 2;
}

Ownership

In my opinion, this is the hardest concept to learn Rust because it is really different from other languages that I worked on but this is the key that makes Rust fast and safe to use!

When you assign some data to a variable it is said that the variable owns it and every piece of data can only have one owner. Let's see this in action:

fn main() {
    let x = String::from("hello"); // x owns the "hello" string
    let a = x; // At this point a owns the "hello" string and x is no longer valid
    do_something(x); // ERROR: x cannot be used anymore!
}

In Rust there is no null and undefined values, so we cannot use a variable that doesn't have a value. In the previous example when we assigned a to x we are moving the value from x to a so at this point x doesn't have a valid value. The same happens with functions:

fn main() {
    let x = String::from("hello");
    do_something(x);
    do_other_thing(x); // ERROR: x cannot be used anymore!
}

fn do_something(s: String) {
    // Do something with s
}

When we call the method do_something we are moving the value from x to s, the argument received by the function. After executing the function we return to main and x doesn't have a valid value anymore.

The previous behavior is not always desired and that's the reason that in Rust we can borrow things! If you don't want to move a value from a variable to another one use references like this:

fn main() {
    let x = String::from("hello");
    do_something(&x);
    do_other_thing(&x); // This is ok now because we are not moving the value
}

fn do_something(s: &String) {
    // Do something with s
}

When we are dealing with ownership and borrowing the Rust compiler wants us to play nice so it will warn us if you we try to do something wrong:

[

If you are confused after learning about ownership and borrowing it is ok! Your brain has started to deal with memory management and it can hurt sometimes. I recommend you watching this video to learn more about this topic.

Structs

Rust is not an object-oriented language but it has some features that can emulate some behavior present in that kind of languages. When we work with classes in JavaScript we are dealing with both data and methods in the same place. In Rust, we are gonna separate the data representation from the methods that manipulate it like this:

struct Dog {
    name: String,
    score: i32
}

impl Dog {
    fn say_something(self: &Dog) {
        println!("Hey, my name is {}... I mean WOOF!", self.name);
    }
}

fn main() {
    let dog = Dog { name: String::from("Boira"), score: 13 };
    dog.say_something();
}

The struct Dog looks pretty similar to a JavaScript object but it's not. The struct is the shape of some data that will have two named fields: name and score. Below the struct you can see an implementation block (impl for short). We can declare methods that will manipulate the data like this and notice that if we want to associate the function with that data we need to pass self as the first argument. It kinda looks like Python, doesn't it?

If we omit the self value we are declaring a method that is not associated with any particular piece of data. You can think of it as a static method in a JavaScript class.

What can I do with Rust?

The first thing you need to do is install Rust and it cannot be easier. Visit the https://rustup.rs/ web to download the official toolchain installer. It is kinda similar to the nvm project that is commonly used with JavaScript.

Then you are gonna need some libraries so don't start from scratch. In the same way as we have Node packages in JavaScript we are gonna deal with crates in Rust. Visit crates.io, the official crates registry, to know more about Rust crates.

Since Rust is very versatile there are a lot of topics where Rust can be used and the community has made a good effort to track them in different websites:

  • www.arewewebyet.org: Even if there are no frameworks as mature as Ruby on Rails you could build some stuff! I recommend taking a look at the Rocket framework if you want to do some web development. You can even create GraphQL APIs using Juniper!
  • www.arewegameyet.com: Having full control of memory management is a necessary feature to create some kind of games so Rust is a wonderful candidate! If you are interested in game development I recommend you checking the Amethyst game engine.
  • www.arewelearningyet.com: Another topic that is widely popular right now is machine learning. The Rust ecosystem isn't very complete right now and it may not be as good as Python right now to do machine learning but If you are interested in the topic check their website!

Finally, if you are doing web development you are lucky! You can create programs, compile them and use it all together with your existing JavaScript code. The technology that made this possible is WebAssembly and it can be used right now in all modern browsers.

If you want to try it I recommend you to read the official Rust and WebAssembly book.

Conclusion

Rust is a really cool language to learn and you could build a lot of things with it! If you are a web developer like me you will find the whole WebAssembly topic very interesting and I hope I can write future posts about it.

If you want to start working with Rust I recommend you checking the official book and try to write some existing JavaScript programs with Rust. As a lot of things practice is the key!

Rust in the Browser for JavaScripters: New Frontiers, New Possibilities

Rust in the Browser for JavaScripters: New Frontiers, New Possibilities

Thanks to wasm, Rust can reach the platform with the largest reach: the browser. We’ll take a look at how thanks to the impressive language design, thoughtful compiler error messages, and great documentation, JavaScripters can unlock high-performance concurrency and graphics thanks to Rust. We’ll step through Rust/wasm/JS interop, see what it’s like to get a reference to a canvas instance, to communicate with services workers, and to pass data between all the pieces involved. We’ll take a look at what’s enabled, as well as pitfalls around the data boundaries involved, and the size of the final payload, so that it’s clear where the cost of introducing Rust is outweighed by its benefits. Finally, we’ll speculate on how the web may develop, with a Rust-core/JavaScript-surface design, combining high performance, safety, while maintaining ease-of-use.

Why even though Rust requires more effort, it’s approachable for JavaScript developers. How Rust combines with service workers and WebGL open new possibilities? What does Rust/JS interop look like, and what are the steps involved?

There’s a reasonable chance that the future of WebUI will have Rust at its core.



Why you should learn the Rust programming language

Why you should learn the Rust programming language

Rust has many features that make it useful, but developers and their needs differ. I cover five of the key concepts that make Rust worth learning

Discover the history, key concepts, and tools for using Rust

A recent Stack Overflow survey found that almost 80% of respondents loved using or wanted to develop with the Rust language. That’s an incredible number! So, what’s so good about Rust? This article explores the high points of this C-like language and illustrates why it should be next on your list of languages to learn.

Rust and its genealogy

First, let’s start with a quick history lesson. Rust is a new language relative to its predecessors (most importantly C, which preceded it by 38 years), but its genealogy creates its multiparadigm approach. Rust is considered a C-like language, but the other features it includes create advantages over its predecessors (see Figure 1).

First, Rust is heavily influenced by Cyclone (a safe dialect of C and an imperative language), with some aspects of object-oriented features from C++. But, it also includes functional features from languages like Haskell and OCaml. The result is a C-like language that supports multiparadigm programming (imperative, functional, and object oriented).

Figure 1. Rust and its family tree

Key concepts in Rust

Rust has many features that make it useful, but developers and their needs differ. I cover five of the key concepts that make Rust worth learning and show these ideas in Rust source.

First, to get a feel for the code, let’s look at the canonical “Hello World” program that simply emits that message to the user (see Listing 1).

Listing 1. “Hello World” in Rust
fn main()
{
   println!( "Hello World.");
}

This simple program, similar to C, defines a main function that is the designated entry point for the program (and every program has one). The function is defined with the fnkeyword followed by an optional set of parameters within parentheses (()). The curly braces ({}) delineate the function; this function consists of a call to the println! macro, which emits formatted text to the console (stdout), as defined by the string parameter.

Rust includes a variety of features that make it interesting and worth the investment to learn. You’ll find concepts like modules for reusability, memory safety and guarantees (safe vs. unsafe operations), unrecoverable and recoverable error handling features, support for concurrency, and complex data types (called collections).

Reusable code via modules

Rust allows you to organize code in a way that fosters its reuse. You achieve this organization by using modules, which contain functions, structures, and even other modules that you can make public (that is, expose to users of the module) or private (that is, use only within the module and not by the module users — at least not directly). The module organizes code as a package that others can use.

You use three keywords to create modules, use modules, and modify the visibility of elements in modules.

  • The mod keyword creates a new module
  • The use keyword lets you use the module (expose the definitions into scope to use them)
  • The pub keyword makes elements of the module public (otherwise, they’re private).

Listing 2 provides a simple example. It starts by creating a new module called bits that contains three functions. The first function, called pos, is a private function that takes a u32 argument and returns a u32 (as indicated by the -> arrow), which is a 1 value shifted left bit times. Note that a return keyword isn’t needed here. This value is called by two public functions (note the pub keyword): decimal and hex. These functions call the private pos function and print the value of the bit position in decimal or hexadecimal format (note the use of  to indicate hexadecimal format). Finally, it declares a main function that calls the bits module’s two public functions, with the output shown at the end of Listing 2 as comments.

Listing 2. Simple module example in Rust
mod bits {
fn pos(bit: u32) ‑> u32 {
1 << bit
}

pub fn decimal(bit: u32) {
println!("Bits decimal {}", pos(bit));
}

pub fn hex(bit: u32) {
println!("Bits decimal 0x{:x}", pos(bit));
}
}

Modules enable you to collect functionality in public or private ways, but you can also associate methods to objects by using the impl keyword.

Safety checks for cleaner code

The Rust compiler enforces memory safety guarantees and other checking that make the programming language safe (unlike C, which can be unsafe). So, in Rust, you’ll never have to worry about dangling pointers or using an object after it has been freed. These things are part of the core Rust language. But, in fields such as embedded development, it’s important to do things like place a structure at an address that represents a set of hardware registers.

Rust includes an unsafe keyword with which you can disable checks that would typically result in a compilation error. As shown in Listing 3, the unsafe keyword enables you to declare an unsafe block. In this example, I declare an unmutable variable x, and then a pointer to that variable called raw. Then, to de-reference raw (which in this case would print 1 to the console), I use the unsafe keyword to permit this operation, which would otherwise be flagged at compilation.

Listing 3. Unsafe operations in Rust
fn main() {
let a = 1;
let rawp = &a as const i32;

unsafe {
println!("rawp is {}", rawp);
}
}

You can apply the unsafe keyword to functions as well as blocks of code within a Rust function. The keyword is most common in writing bindings to non-Rust functions. This feature makes Rust useful for things like operating system development or embedded (bare-metal) programming.

Better error handling

Errors happen, regardless of the programming language you use. In Rust, errors fall into two camps: unrecoverable errors (the bad kind) and recoverable errors (the not-so-bad kind).

Unrecoverable errors

The Rust panic! function is similar to C‘s assert macro. It generates output to help the user debug a problem (as well as stopping execution before more catastrophic events occur). The panic! function is shown in Listing 4, with its executable output in comments.

Listing 04. Handling unrecoverable errors in Rust with panic!

fn main() {
panic!("Bad things happening.");
}

// thread 'main' panicked at 'Bad things happening.', panic.rs:2:4
// note: Run with RUST_BACKTRACE=1 for a backtrace.

From the output, you can see that the Rust runtime indicates exactly where the issue occurred (line 2) and emitted the provided message (which could emit more descriptive information). As indicated in the output message, you could generate a stack backtrace by running with a special environment variable called RUST_BACKTRACE. You can also invoke panic! internally based on detectable errors (such as accessing an invalid index of a vector).

Recoverable errors

Handling recoverable errors is a standard part of programming, and Rust includes a nice feature for error checking (see Listing 5). Take a look at this feature in the context of a file operation. The File::open function returns a type of Result<T, E>, where T and Erepresent generic type parameters (in this context, they represent std::fs::File and std::io::Error). So, when you call File::open and no error has occurred (E is Ok), T would represent the return type (std::fs::File). If an error occurred, E would represent the type of error that occurred (using the type std::io::Error). (Note that my file variable fuses an underscore [] to omit the unused variable warning that the compiler generated.)

I then use a special feature in Rust called match, which is similar to the switch statement in C but more powerful. In this context, I match _f against the possible error values (Okand Err). For Ok, I return the file for assignment; for Err, I use panic!.

Listing 5. Handling recoverable errors in Rust with Result<t, e>

use std::fs::File;

fn main() {
let _f = File::open("file.txt");

let _f = match _f {
Ok(file) => file,
Err(why) => panic!("Error opening the file {:?}", why),
};
}

// thread 'main' panicked at 'Error opening the file Error { repr: Os
// { code: 2, message: "No such file or directory" } }', recover.rs:8:23
// note: Run with RUST_BACKTRACE=1 for a backtrace.

Recoverable errors are simplified within Rust when you use the Result enum; they’re further simplified through the use of match. Note also in this example the lack of a File::close operation: The file is automatically closed when the scope of _f ends.

Support for concurrency and threads

Concurrency commonly comes with issues (data races and deadlocks, to name two). Rust provides the means to spawn threads by using the native operating system but also attempts to mitigate the negative effects of threading. Rust includes message passing to allow threads to communicate with one another (via send and recv as well as locking through mutexes). Rust also provides the ability to permit a thread to borrow a value, which gives it ownership and effectively transitions the scope of the value (and its ownership) to a new thread. Thus, Rust provides memory safety along with concurrency without data races.

Consider a simple example of threading within Rust that introduces some new elements (vector operations) and brings back some previously discussed concepts (pattern matching). In Listing 6, I begin by importing the thread and Duration namespaces into my program. I then declare a new function called my_thread, which represents the thread that I’ll create later. In this thread, I simply emit the thread’s identifier, and then sleep for a short time to permit the scheduler to allow another thread to run.

My main function is the heart of this example. I begin by creating an empty mutable vector that I can use to store values of the same type. I then create 10 threads by using the spawn function and push the resulting join handle into the vector (more on this later). This spawn example is detached from the current thread, which allows the thread to live after the parent thread has exited. After emitting a short message from the parent thread, I finally iterate the vector of JoinHandle types and wait for each child thread to exit. For each JoinHandle in the vector, I call the join function, which waits for that thread to exit before continuing. If the join function returns an error, I’ll expose that error through the match call.

Listing 6. Threads in Rust

use std::thread;
use std::time::Duration;

fn mythread() {
println!("Thread {:?} is running", std::thread::current().id());
thread::sleep(Duration::from_millis(1));
}

fn main() {
let mut v = vec![];

for _i in 1..10 {
v.push( thread::spawn(|| { my_thread(); } ) );
}

println!("main() waiting.");

for child in v {
match child.join() {
Ok() => (),
Err(why) => println!("Join failure {:?}", why),
};
}
}

On execution, I see the output provided in Listing 7. Note here that the main thread continued to execute until the join process had begun. The threads then executed and exited at different times, identifying the asynchronous nature of threads.

Listing 7. Thread output from the example code in Listing 6

main() waiting.
Thread ThreadId(7) is running
Thread ThreadId(9) is running
Thread ThreadId(8) is running
Thread ThreadId(6) is running
Thread ThreadId(5) is running
Thread ThreadId(4) is running
Thread ThreadId(3) is running
Thread ThreadId(2) is running
Thread ThreadId(1) is running

Support for complex data types (collections)

The Rust standard library includes several popular and useful data structures that you can use in your development, including four types of data structures: sequences, maps, sets, and a miscellaneous type.

For sequences, you can use the vector type (Vec), which I used in the threading example. This type provides a dynamically resizeable array and is useful for collecting data for later processing. The VecDeque structure is similar to Vec, but you can insert it at both ends of the sequence. The LinkedList structure is similar to Vec, as well, but with it, you can split and append lists.

For maps, you have the HashMap and BTreeMap structures. You use the HashMap structure to create key-value pairs, and you can reference elements by their key (to retrieve the value). The BTreeMap is similar to the HashMap, but it can sort the keys, and you can easily iterate all the entries.

For sets, you have the HashSet and BTreeSet structures (which you’ll note follow the maps structures). These structures are useful when you don’t have values (just keys) and you easily recall the keys that have been inserted.

Finally, the miscellaneous structure is currently the BinaryHeap. This structure implements a priority queue with a binary heap.

Installing Rust and its tools

One of the simplest ways to install Rust is by using curl through the installation script. Simply execute the following string from the Linux® command line:

curl -sSf https://static.rust-lang.org/rustup.sh | sh

This string transfers the rustup shell script from rust-lang.org, and then passes the script to the shell for execution. When complete, you can execute rustc -v to show the version of Rust you installed. With Rust installed, you can maintain it by using the rustuputility, which you can also use to update your Rust installation.

The Rust compiler is called rustc. In the examples shown here, the build process is simply defined as:

rustc threads.rs

…where the rust compiler produces a native executable file called threads. You can symbolically debug Rust programs by using either rust-lldb or rust-gdb.

You’ve probably noticed that the Rust programs I’ve demonstrated here have a unique style. You can learn this style through the automatic Rust source formatting by using the rustfmt utility. This utility, executed with a source file name, will automatically format your source in a consistent, standardized style.

Finally, although Rust is quite strict in what it accepts for source, you can use the rust-clippy program to dive further in to your source to identify elements of bad practice. Think of rust-clippy as the C lint utility.

Windows considerations

On Windows, Rust additionally requires the C++ build tools for Visual Studio 2013 or later. The easiest way to acquire the build tools is by installing Microsoft Visual C++ Build Tools 2017 which provides just the Visual C++ build tools. Alternately, you can install Visual Studio 2017, Visual Studio 2015, or Visual Studio 2013 and during the install, select C++ tools.

For further information about configuring Rust on Windows, see the Windows-specific rustup documentation.

Going further

In mid-February 2018, the Rust team released version 1.24. This version includes incremental compilation, automatic source formatting with rustfmt, new optimizations, and library stabilizations. You can learn more about Rust and its evolution at the Rust blog and download Rust from the Rust Language website. There, you can read about the many other features Rust offers, including pattern matching, iterators, closures, and smart pointers.

Originally published on https://developer.ibm.com