RFC: private by default for values

Hi all,

I made some breakthrough to implement private by default for values from the technical point of view, and yes, it is efficient.

For example you can write this in theory in ReScript:

let x  = 3
let hello = 3
pub let y = 2 // only this `y` will be exported

So there are several things to discuss:

  • How to turn it on, introducing a flag, shall we move to private by default eventually?

  • What’s the concrete syntax?

cc @cristianoc who want this for a long time

Relevant threads:
Use _ to mark properties as private - General - ReScript Forum (rescript-lang.org)


Isn’t it already the case while we don’t write the types/values in a resi file to make them public?
How would be the behavior with resi file ? Does the behavior will change or it only concern the lonely res file situation ?

1 Like

We could match the JS syntax: export let x = 1

For migration, we could phase it in by copying the unambiguous modules idea from John-David Dalton. For major version X, if there’s an export keyword in the file, only the things marked as exported are exported. Otherwise, everything is exported.

For major version X + 1, only the things marked as exported are exported - regardless of the export keyword.

1 Like

Beautiful! The only thing that is not clear is how it’s going to work with .resi. I assume that if there is a mismatch in res and resi, it would be a compiler error?

1 Like

I would advocate for pub as it’s shorter. export is tedious to type.


Beautiful indeed! Will it also work for types? E.g., pub type t = string. And would it be possible to specify the type you export, e.g.:

pub type t: t = string
pub let x: t = "foo"
// or
pub as t type t = string
pub as t x: t = "foo"

(Yes, I’m thinking of the ability to get rid of the interfaces but keep the opaque types.)


That would be awesome! Yes please! This is something that I miss from typescript. As someone wrote in this forum, it is far easier to remember to explicitly make public methods to expose than to make them private.

Its perfect !! :+1::+1::+1::+1::+1::+1:

Or pub opaque type t = string - like Flow? Does this miss any use-cases?

What’s the concrete syntax?

Is the pub keyword inspired by Rust? It is nice and succinct.

pub is a nice keyword fitting current trends, but it doesn’t align with JS.

On the other hand a keyword like export is not as trendy but it does align with JS.

If something is exported, that something should be imported at some point. But we don’t import, we open instead or use fully-qualified module paths. So, cross-module communication is misaligned with JS already and pub alone doesn’t make ReScript further from JS significantly.

Just a thought.


That’s a good point @nkrkv, thanks.

Private by default (backed by an efficient implementation) would be a really nice thing. It aligns with what we’re used to in JS. Exposing it as a flag and then eventually (if things go well) making it the default behaviour is a sensible approach to me. (Given tooling to help migrate codebases.)

I agree with @kevanstannard on the naming. pub is trending in other languages, but it might not be the best fit from a design perspective.


This is great. I am currently tinkering with Rust and just yesterday I thought I’d really prefer private-by-default in ReScript as well and now you are suggesting exactly that.

Thank you, this is a must-have.

1 Like

I’m also curious about how this would work, and if it could completely replace resi files. For example, would you be able to implement code like this without an interface?

// Even.res
type t = int
let make = i =>
  if mod(i, 2) == 0 {
  } else {
let toInt = i => i

// Even.resi
type t
let make: int => option<t>
let toInt: t => int
1 Like

This sounds great! What about private submodules?


Well, syntax-wise, this looks unambiguous to me:

pub type t: t = int
pub let make: int => option<t> = i =>
  if mod(i, 2) == 0 {
  } else {
pub let toInt: t  => int = i => i

But maybe the pub as syntax is better because it separates declaration and implementation. :t => int = i => i is starting too look like Haskell :grin:


@Hongbo do you have a spec of how this is going to work under the hood?
And do the compiler changes impact the general performance of projects without .resi files? Or do you still need .resi files for that?


This is not in conflict with resi files, you can still add resi. The compilation of resi is independent from res file, it is like a filter for existing res files

Note type is much more complicated. Since it may be implicitly referred in the inferred types