Quick Links
  • -Overview
  • -Language Features
  • -JS Interop
  • -Build System
Documentation
Language Manual
Reference for all language features
ReScript & React
First class bindings for ReactJS
GenType
Seamless TypeScript integration
Reanalyze
Dead Code & Termination analysis
Exploration
Packages
Explore third party libraries and bindings
Syntax Lookup
Discover all syntax constructs
APIPlaygroundBlogCommunity
  • Playground
  • Blog
  • Twitter
  • GitHub
  • Forum
Language Manual
Overview
  • Introduction
  • Installation
  • Editor Plugins
  • Migrate to ReScript Syntax
  • Try
Language Features
  • Overview
  • Let Binding
  • Type
  • Primitive Types
  • Tuple
  • Record
    • Type Declaration
    • Creation
    • Access
    • Immutable Update
    • Mutable Update
    • Tips & Tricks
    • Design Decisions
  • Object
  • Variant
  • Null, Undefined and Option
  • Array & List
  • Function
  • If-Else & Loops
  • Pipe
  • Pattern Matching / Destructuring
  • Mutation
  • JSX
  • Exception
  • Lazy Values
  • Promise
  • Module
  • Import & Export
  • Reserved Keyword
JavaScript Interop
  • Embed Raw JavaScript
  • Shared Data Types
  • External (Bind to Any JS Library)
  • Bind to JS Object
  • Bind to JS Function
  • Import from / Export to JS
  • Bind to Global JS Values
  • JSON
  • Use Illegal Identifier Names
  • Generate Converters & Helpers
  • Browser Support & Polyfills
  • Interop Cheatsheet
Build System
  • Build System Overview
  • Build System Configuration
  • Interop with JS Build Systems
  • Build Performance
Guides
  • Converting from JS
  • Libraries
Extra
  • Newcomer Examples
  • Project Structure
  • FAQ
Docs / Language Manual / Record
Edit

You are currently looking at the v6.0 - v8.2 docs (Reason v3.6 syntax edition). You can find the latest manual page here.

(These docs are equivalent to the old BuckleScript docs before the ReScript rebrand)

Record

Records are like JavaScript objects but:

  • are immutable by default

  • have fixed fields (not extensible)

Type Declaration

A record needs a mandatory type declaration:

Reason (Old Syntax)ML (Older Syntax)JS Output
type person = {
  age: int,
  name: string
};

Creation

To create a person record (declared above):

Reason (Old Syntax)ML (Older Syntax)JS Output
let me = {
  age: 5,
  name: "Big ReScript"
};

When you create a new record value, ReScript tries to find a record type declaration that conforms to the shape of the value. So the me value here is inferred as of type person.

The type is found by looking above the me value. Note: if the type instead resides in another file or module, you need to explicitly indicate which file or module it is:

Reason (Old Syntax)ML (Older Syntax)JS Output
// School.re
type person = {age: int, name: string};
Reason (Old Syntax)ML (Older Syntax)JS Output
// Example.re

let me: School.person = {age: 20, name: "Big ReScript"};
/* or */
let me2 = {School.age: 20, name: "Big ReScript"};

Either of the above 3 says "this record's definition is found in the School file". The first one, the regular type annotation, is preferred.

Access

Use the familiar dot notation:

Reason (Old Syntax)ML (Older Syntax)JS Output
let name = me.name;

Immutable Update

New records can be created from old records with the ... spread operator. The original record isn't mutated.

Reason (Old Syntax)ML (Older Syntax)JS Output
let meNextYear = {...me, age: me.age + 1};

Note: spread cannot add new fields to the record value, as a record's shape is fixed by its type.

Mutable Update

Record fields can optionally be mutable. This allows you to efficiently update those fields in-place with the = operator.

Reason (Old Syntax)ML (Older Syntax)JS Output
type person = {
  name: string,
  mutable age: int
};

let baby = {name: "Baby ReScript", age: 5};
baby.age = baby.age + 1; // `baby.age` is now 6. Happy birthday!

Tips & Tricks

Record Types Are Found By Field Name. With records, you cannot say "I'd like this function to take any record type, as long as they have the field age". The following won't work as intended:

Reason (Old Syntax)ML (Older Syntax)JS Output
type person = {age: int, name: string};
type monster = {age: int, hasTentacles: bool};

let getAge = (entity) => entity.age;

Instead, getAge will infer that the parameter entity must be of type monster, the closest record type with the field age. The following code's last line fails:

Reason (Old Syntax)ML (Older Syntax)
let kraken = {age: 9999, hasTentacles: true};
let me = {age: 5, name: "Baby ReScript"};

getAge(kraken);
getAge(me); // type error!

The type system will complain that me is a person, and that getAge only works on monster. If you need such capability, use ReScript objects, described here.

Design Decisions

After reading the constraints in the previous sections, and if you're coming from a dynamic language background, you might be wondering why one would bother with record in the first place instead of straight using object, since the former needs explicit typing and doesn't allow different records with the same field name to be passed to the same function, etc.

  1. The truth is that most of the times in your app, your data's shape is actually fixed, and if it's not, it can potentially be better represented as a combination of variant (introduced next) + record instead.

  2. Since a record type is resolved through finding that single explicit type declaration (we call this "nominal typing"), the type error messages end up better than the counterpart ("structural typing", like for tuples). This makes refactoring easier; changing a record type's fields naturally allows the compiler to know that it's still the same record, just misused in some places. Otherwise, under structural typing, it might get hard to tell whether the definition site or the usage site is wrong.

TupleObject

© 2024 The ReScript Project

Software and assets distribution powered by KeyCDN.

About
  • Community
  • ReScript Association
Find us on