Experiment: Allow inline records in externals to simplify writing bindings

In v12 we’ve added support for inline/nested records - being able to define records nested in records, without having to define a type for each record separately. It looks like this:

type person = {
  age: int,
  name: string,
  notificationSettings: {
    sendEmails: bool,
    allowPasswordLogin: bool,
  },
}

let person = {
  age: 90,
  name: "Test Person",
  notificationSettings: {
    sendEmails: true,
    allowPasswordLogin: false,
  },
}

This is great for one-off definitions where you don’t expect to use the nested records by themselves, just as a part of the general structure.

As an extension to this mechanism, we’ve made a PoC for allowing inline records in externals. The intention is to make writing bindings easier and more ergonomic, since bindings often are one-off, with types that are never used elsewhere except for with that external. It looks like this:

@module("node:fs")
external readFileSync: (
  string,
  ~options: {
    encoding?: [#utf8 | #ascii | #base64],
    flag?: string,
    misc?: {
      mode?: int,
    },
  },
) => option<{filename: string, size: string}> = "fs.readFileSync"

In this PoC, inline records are allowed in these positions:

  • On labelled/optional arguments
  • On the return type of the function

So, it’s only in unlabelled arguments that inline records aren’t allowed.

The mechanism is the same under the hood as regular nested records. Actual record definitions will be created and used behind the scenes, with a naming format that follows where they were found (readFileSync.options, readFileSync.return.type above).

This is early experimentation, but we’re interested in hearing your thoughts. What do you think about this feature?

(link to PoC PR: [PoC] support inline/nested records inside of externals by zth · Pull Request #7791 · rescript-lang/rescript · GitHub)

6 Likes

I often define long and one-off records for the optional parameters/options argument of a js function, so this can be very useful to simplify bindings!

Out of curiosity, why can’t this be used in unlabeled parameters?

It could be extended to allow that, just wasn’t included in the initial PoC. But it shouldn’t be hard to include if it adds value.

1 Like