Tired of Typescript? Check out ReScript!
Tags: rescript, typescript, javascript, webdev
Published: 2024-04-19 15:10:12

If you've spent any amount of time developing with JavaScript you have probably found it difficult to maintain a large code base and avoid runtime errors. The language wasn't designed to do all of the things we use it for today. We've been trying to improve our experience with the language by using tools like ESLint to improve and enforce a shared code quality with our teams. One of the largest shifts in the JavaScript world has been the adoption of TypeScript, which adds a nice layer of types on top of JavaScript. It has done a fantastic job assisting with code maintainability in large JavaScript projects.

However, TypeScript is not without its flaws. It can be slow to compile and show type hints in your IDE, the types require a lot of annotation and can create too much noise around simple code,  the type system has unsafe escape hatches like `any` that reduce our level of trust in the types, and since it's a superset of JavaScript it brings along all of JavaScript's warts and footguns. These issues have led some people to drop TypeScript in [favor of using JSDoc](https://news.ycombinator.com/item?id=35892250) with types or even [dropping types all together](https://world.hey.com/dhh/turbo-8-is-dropping-typescript-70165c01). 

Instead of moving back to un-typed JavaScript, perhaps there is another solution?

## ReScript

[ReScript](https://rescript-lang.org/) is a fully typed language with an easy to understand JS like syntax, blazing fast compiler, that compiles to JavaScript. You can [easily drop it into an existing project](https://rescript-lang.org/docs/manual/latest/installation#integrate-into-an-existing-js-project), [and there is even a way to generate TypeScript types if you want to add it to a TypeScript project](https://rescript-lang.org/docs/manual/latest/typescript-integration)!

While ReScript and TypeScript fill a similar role of improving JavaScript development with types they have different goals and ways of achieving those goals. TypeScript is a superset of JS, which means all valid JS is valid TS and that TypeScript's type system has to be able to add types to anything JavaScript can do. ReScript is a different language with a JS like syntax, so it doesn't have to try and add types to JavaScript code. It's built for types.

## Less type annotations
TypeScript requires you to add type annotations to your code in order for the compiler to understand what you are trying to do. 

```ts
function add(a: number, b: number) {
  return a + b
}
```

Thankfully it can usually infer the return type. If I omitted the types here I would get an error:

```ts
function add(a, b) /* Parameter 'a' implicitly has an 'any' type.ts(7006) */ {
  return a + b
}
```

ReScript can infer the types based on how you use the values, and when I say infer I don't mean "guess", the type is guaranteed to be correct.

```rescript
let add = (a, b) => a + b // fully typed!
```

Here's a slightly larger example:
```rescript
type person = {name: string}

let greet = person => `Hello ${person.name}`

let bill = {
  name: "bill",
}

let main = () => greet(bill)
```

That is all fully typed. ReScript knows that `greet` takes in a `person` since it accesses `name`. It knows that `bill` is a person because it's a record with a `name` field. So I can call `greet` with `bill`. You can take a look at this code with type hints on hover on the [ReScript playground](https://rescript-lang.org/try?version=v11.0.1&code=C4TwDgpgBJBODOB7AdlAvFA3sghgWwgC4p5hYBLZAcwF8AoOgGwmCgHdz4ALdGCBFOgB8UAAYAJCI0aIoAEkxwkyAHS4CNUQ2asARuWm9MdKFHVEoAIn3TLAGjr0mLKHhyVeACgCUw9py5PG0ZvOiA).

Here's how we can do the same in TypeScript:

```typescript
type Person = { name: string }

const greet = (person: Person) => `Hello ${person.name}`

const bill = {
  name: 'bill'
}

const main = () => greet(bill)
```
It's not much more in this example, but it starts to add up quickly as code gets more complex.

Another cool example is that this also works for React component props ([ReScript has first class support for React and JSX!](https://rescript-lang.org/docs/react/latest/introduction)).

```rescript
@jsx.component
let make = (~name) => <p> {React.string(`Hello ${name}!`)} </p>
```

It knows that `name` is a string! Here's the equivalent in TypeScript:

```typescript
type Props = { name: string }

const Greeting = ({ name }: Props) => `Hello ${name}`
```

My favorite part of this it not having to remember the correct type for every event handler in React.

```rescript
@jsx.component
let make = (~handleClick) =>
  <button onClick=handleClick> {React.string("Click me!")} </button>
```

Removing the need for explicit type definitions allows me to move quicker, and it allows me to add new props easily. You don't have to think about types unless you are defining a shape of data. The types are there acting as a strong guiding hand, but you don't need to tell ReScript what type is which, it just does that for you. Of course, you can always add type annotations if you would like.

```rescript
let add = (a: int, b: int): int => a + b
```

## Better type system
ReScript has a "sound" type system. In a nutshell this means that if the compiler assigns type `a` to something we know it has to be type `a`. I found [this conversation](https://langdev.stackexchange.com/q/3372) around the topic helpful.

How does that help us as developers?

If we see a function that returns a `string` can know without a doubt that it will return a `string`. 

Doesn't TypeScript do this? It can as long as everyone plays the game correctly, but it's very easy to trick or lie to TypeScript, or to just incorrectly assign something that has an `any` type.

```typescript
const t1 = 42 as unknown as string

// @ts-ignore
const t2: string = 42

const fn = (): any => 42

const t3: string = fn()

const t4: any = 42

const t5 = t4 as string
```

If you saw some of these pop up in a code review you would hopefully demand changes, but it's easy in real world code to let this type of error bleed out into the wild.

```typescript
type person = {
  name: string,
  age: number
}

function parse(t: string): person {
  return JSON.parse(t)
}

const t1 = parse('{"color": "blue"}')
```
I have seen something like this in real code, and it led to bugs. ReScript won't allow you to do this, you will have to parse the JSON with a library like [rescript-schema](https://github.com/DZakh/rescript-schema) (it's like Zod).

ReScript doesn't have an `any` type. Everything has to have some sort of type, and I can't tell the compiler to ignore the next line or force it to assign a type to a value that is incorrect.

Having a type system you can trust helps you write code with confidence.

## Faster compile times
TypeScript can slow down if you have a large enough code base. I've worked on projects with upwards of 150k lines of TypeScript and VSCode would crash frequently and often fail to show type hints on hover.

Your mileage may vary, but here are some numbers from projects I have been working on recently. In a project with almost 32k TypeScript files it takes me 2 minutes to run full type checking. When running tsc in watch mode it takes 4-10 seconds to type check after saving a file.

A ReScript app with 50k files can run full type checking with no cache in 1 minute. After a cache is made this takes no time if nothing has changed, or milliseconds if you have changed things. With all 50k files being watched it takes 338 milliseconds for ReScript to type check and compile after saving a file.

Even with 50k files VSCode intellisense picks up on every module with auto complete without any lag.

When using ReScript, you also don't need tools like ESLint or Prettier. The compiler enforces good practices and it has a built in formatter. 

Working on a large project with snappy intellisense and a quick feedback loop is amazing. ReScript doesn't slow down local development when your project grows, and even full builds scale well and can still run full type-checking in half the time it takes TypeScript.

## The good parts of JS and more!
ReScript has all of the [good parts of JS](https://rescript-lang.org/docs/manual/latest/overview#comparison-to-js) that we like using such as `await/async`, object and array spreading, destructuring, and it also has new features like pattern matching, variant types, and `Option` and `Result` types that should feel familiar to developers familiar with Rust, Elm, or OCaml.

You can read more about these features in my other posts:
- [ReScript: Rust like features for JavaScript ](https://dev.to/jderochervlk/rescript-rust-like-features-for-javascript-27ig)
- [Using variant types in ReScript to represent business logic](https://dev.to/jderochervlk/using-variant-types-in-rescript-to-represent-business-logic-34e3)