DEV Community

Cover image for ReScript: Rust like features for JavaScript
Josh Derocher-Vlk
Josh Derocher-Vlk

Posted on

ReScript: Rust like features for JavaScript

If Rust is "C++ in ML clothing" then ReScript is "JavaScript in ML clothing".

What is ReScript?

ReScript is "Fast, Simple, Fully Typed JavaScript from the Future". What that means is that ReScript has a lightning fast compiler, an easy to learn JS like syntax, strong static types, with amazing features like pattern matching and variant types. Until 2020 it was called "BuckleScript" and is closely related to ReasonML.

ReScript is growing and adding features to make it more appealing as an alternative to JavaScript. ReScript v11 was recently released and adds some very nice quality of life improvements.

Let's take a quick look at a few features that Rust and ReScript share with some code examples.

Types

Rust and ReScript are both strong, statically-typed languages with roots in OCaml. Everything has a type and that type is guaranteed to be correct. Unlike TypeScript you won't find the any type in ReScript.

ReScript relies heavily on type inference which helps remove some clutter from type annotations.

// Typescript
let add = (a, b) => a + b // infers (any, any) => any

// ReScript
let add = (a, b) => a + b // infers (int, int) => int
Enter fullscreen mode Exit fullscreen mode

You can add type annotations if you would like.

// ReScript
let add = (a: int, b: int): int => a + b
Enter fullscreen mode Exit fullscreen mode

Expression based

Rust and ReScript are expression based languages, which means most lines of code or blocks of code return a value. You also don't have to write an explicit return for an expression, the last value in a block is the value returned.

// rust
fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {y}"); // the value of y is: 4
}
Enter fullscreen mode Exit fullscreen mode
// rescript
let main = () => {
  let y = {
    let x = 3
    x + 1
  }

  Console.log(`The value of y is: ${y->Int.toString}`) // the value of y is: 4
}
Enter fullscreen mode Exit fullscreen mode

Pattern Matching

Pattern matching is a powerful way to do something conditionally based on types or data. You can think of it as a super powered switch statement that can match on the type or the value of the data. Rust and ReScript will give you a compiler error if you fail to handle all possible cases, which makes refactoring much easier to handle. If you ever have to add a new case you'll know exactly where to go in the code to make sure you handle it correctly.

// Rust
fn main() {
    let n = 42;

    match n {
        10 => println!("The number ten."),
        42 => println!("Answer to the Ultimate Question of Life, the Universe, and Everything."),
        _ => println!("Some other number."),
    }
}
Enter fullscreen mode Exit fullscreen mode
// rescript
let main = () => {
  let x = 42
  switch x {
  | 10 => Console.log("The number ten.")
  | 42 =>
    Console.log(
      "Answer to the Ultimate Question of Life, the Universe, and Everything.",
    )
  | _ => Console.log("Some other number.")
  }
}
Enter fullscreen mode Exit fullscreen mode

Tagged Unions and Variant types

Rust's tagged unions and ReScript variant types give you a way to not only create a type, but to also give that type data. This can be combined with pattern matching to make sure you handle all possible cases.

This article I wrote shows how you can use ReScript's variant types to connect business logic to the compiler.

// Rust
enum Widget {
    Red(u8),
    Blue(u8),
    None,
}

fn main() {
    let x = Widget::Red(42);

    match x {
        Widget::Red(_) => println!("You have a red widget!"),
        Widget::Blue(_) => println!("You have a blue widget!"),
        Widget::None => println!("You have no widget!"),
    }
}
Enter fullscreen mode Exit fullscreen mode
// ReScript
type widget =
  | Red(int)
  | Blue(int)
  | None

let main = () => {
  let x = Red(42)

  switch x {
  | Red(_) => Console.log("You have a red widget!")
  | Blue(_) => Console.log("You have a blue widget!")
  | None => Console.log("You have no widget!")
  }
}
Enter fullscreen mode Exit fullscreen mode

Option instead of undefined or null

Not having to deal with null or undefined errors saves so much time and prevents errors and headaches in production. Option forces you to explicitly deal with anything that might not be defined and having it typed this way makes it clear when you expect something to be undefined.

// Rust
fn main() {
    let n = Some("Josh");

    match n {
        Some(name) => println!("Hello there {:?}!", name),
        None => println!("Who are you?")
    }
}
Enter fullscreen mode Exit fullscreen mode
// rescript
let main = () => {
  let n = Some("Josh")

  switch n {
  | Some(name) => Console.log(`Hello there ${name}!`)
  | None => Console.log("Who are you?")
  }
}
Enter fullscreen mode Exit fullscreen mode

Result instead of throwing errors

Both languages have the Result type which can represent a response from an expression that might have failed. It's useful to handle expected errors without blowing up the program. Expected errors could be things like an api timing out, or a 400 response for a bad request. It allows the program to track information about an error and handle it gracefully without throwing an exception.

// rust
fn main() {
    let n: Result<&str, &str> = Ok("Josh");

    match n {
        Ok(name) => println!("Hello there {:?}!", name),
        Err(err) => println!("Error: {:?}", err),
    }
}
Enter fullscreen mode Exit fullscreen mode
// rescript
let main = () => {
  let n: result<string, string> = Ok("Josh")

  switch n {
  | Ok(name) => Console.log(`Hello there ${name}`)
  | Error(err) => Console.log(`Error: ${err}`)
  }
}
Enter fullscreen mode Exit fullscreen mode

Learn more about ReScript

If you are interested in learning more about ReScript check out the official site or come over to the forum!

Top comments (7)

Collapse
 
zth profile image
Gabriel Nordeborn

Great write up!

Collapse
 
olddutchcap profile image
Onorio Catenacci

It's nice to see BuckleScript (ReScript) get some love.

I've always been a fan of OCaml but even more than Haskell community the OCaml'ers seem to have adopted the maxim "avoid 'success at all costs'!" which has led to an awesome if sadly very niche language. ReScript taking the excellent ideas of OCaml to JS seems like a real step forward.

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk • Edited

It's an amazing language that solves so many issues with JS while not being as difficult to set up and use as others like Elm, F# Fable, or PureScript. It uses npm and all the normal JS tooling, and it has the easiest interop with JS. It's a great mix of strong, sound types while not getting on the way of getting shit done.

Collapse
 
youngkidwarrior profile image
VictorGinelli

Love the title and the execution. Been thinking in a similar vein when I heard popular dev influencers talking about Rust and it's various language features.

Keep it up dude

Collapse
 
fuqua profile image
Aaron Fuqua

I assume that "ML" does not refer to Machine Learning in this context. But I can't seem to track down what it stands for. The ReasonML website also seems to not define what is meant by "ML". What does ML mean here?

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

"ML" stands for "Meta Language".

en.wikipedia.org/wiki/ML_(programm...

You're not the first person to be confused by that :)

Collapse
 
fuqua profile image
Aaron Fuqua

Thanks!