ReScript: Rust like features for JavaScript
Tags: rescript, rust, javascript, webdev
Published: 2024-01-18 18:48:55


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

## What is ReScript?
[ReScript](https://rescript-lang.org/) 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](https://reasonml.github.io/).

ReScript is growing and adding features to make it more appealing as an alternative to JavaScript. ReScript v11 was [recently released](https://rescript-lang.org/blog/release-11-0-0) 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
- [The Rust Reference: Types](https://doc.rust-lang.org/reference/types.html)
- [ReScript Docs: Type](https://rescript-lang.org/docs/manual/latest/type)

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. 
```rescript
// Typescript
let add = (a, b) => a + b // infers (any, any) => any

// ReScript
let add = (a, b) => a + b // infers (int, int) => int
```
You can add type annotations if you would like.
```rescript
// ReScript
let add = (a: int, b: int): int => a + b
```

## Expression based
- [The Rust Book: statements and expressions](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html#statements-and-expressions)
- [ReScript Docs: blocks](https://rescript-lang.org/docs/manual/latest/overview#blocks)

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
// rust
fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {y}"); // the value of y is: 4
}
```
```rescript
// 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
}
```

## Pattern Matching
- [Rust by Example: match](https://doc.rust-lang.org/rust-by-example/flow_control/match.html)
- [ReScript Docs: Pattern Matching / Destructuring](https://rescript-lang.org/docs/manual/latest/pattern-matching-destructuring)

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
// 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."),
    }
}
```
```rescript
// 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.")
  }
}
```

### Tagged Unions and Variant types
- [Rust by Example: Enums](https://doc.rust-lang.org/rust-by-example/custom_types/enum.htm)
- [ReScript Docs: Variant](https://rescript-lang.org/docs/manual/latest/variant)

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](https://dev.to/jderochervlk/using-variant-types-in-rescript-to-represent-business-logic-34e3) shows how you can use ReScript's variant types to connect business logic to the compiler.

```rust
// 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!"),
    }
}
```
```rescript
// 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!")
  }
}
```

## `Option` instead of `undefined` or `null`
- [Rust by Example: Option](https://doc.rust-lang.org/rust-by-example/std/option.html)
- [ReScript Docs: Null, Undefined and Option](https://rescript-lang.org/docs/manual/latest/null-undefined-option)

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
// Rust
fn main() {
    let n = Some("Josh");

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

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

## `Result` instead of throwing errors
- [Rust by Example: Result](https://doc.rust-lang.org/rust-by-example/error/result.html)
- [ReScript Docs: result](https://rescript-lang.org/docs/manual/latest/api/core/result )

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
// rust
fn main() {
    let n: Result<&str, &str> = Ok("Josh");

    match n {
        Ok(name) => println!("Hello there {:?}!", name),
        Err(err) => println!("Error: {:?}", err),
    }
}
```
```rescript
// 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}`)
  }
}
```

## Learn more about ReScript
If you are interested in learning more about ReScript check out the [official site](https://rescript-lang.org/) or come over to the [forum](https://forum.rescript-lang.org/)!