Why does rescript have first-class exceptions instead of using Result for most error handling?

Per the title, as a newcomer to rescript it seems a bit odd to me that exceptions appear to be the preferred method of error handling. The Result type IIRC isn’t mentioned anywhere except the API docs.

I understand that people coming from the JS world are most familiar with the exception and null model so it makes some sense for the docs to focus on exceptions and options. However, why not confine exceptions to the JS boundary and almost exclusively use Result within rescript?

The Result type is I think one of the easiest FP concepts to understand the immediate benefits of. Especially given what I think are the fairly clear downsides of using exceptions for error handling (any function could throw from any point in the stack beneath it, you aren’t forced to handle them, all function calls are therefore unsafe at runtime).

Having played with Rust IMO the error handling model there is quite good. Result is the standard for error handling to prevent errors from being ignored. In cases where an error really is entirely unrecoverable and not representable as a resultt it has panic, which is essentially an exception to be used rarely and be well documented.

Using result everywhere means that you need to be explicit about points where you’re ignoring errors instead of the other way around. With exceptions for error handling the default is to ignore all errors and have all function calls be unsafe unless you wrap it in try/catch or manually verify that every line of code within that function can not throw.

I’m surprised you got that impression. I don’t think exceptions are preferred at all. In fact, I think they’re actively discouraged in favor of option, result, or custom error-handling types.

All of the stdlib Belt modules default to returning option types to handle possible errors (Belt.Array.get, Belt.Map.get etc.) And the versions of those functions that raise exceptions are named accordingly (Belt.Array.getExn, Belt.Map.getExn etc.)

The (non-Belt) OCaml stdlib does use exceptions more, but it’s not documented or recommended.

It’s true that the result type isn’t used much by the stdlib, but that’s mainly because the stdlib functions don’t have much need for it (option works fine for almost everything). In userland, result is pretty common.

This is a popular topic, a quick search should turn up some past discussions on this: Search results for 'result exception' - ReScript Forum

E.g.,

2 Likes

I guess I’ll see how it feels once I write some more rescript code, I love the concept of a fast, strongly typed, functional-but-practical language with great interop to the mountains of existing JS/TS code out there.

I got the impression from the docs that:

  1. exceptions are the way you handle errors in rescript. IIRC none of the guide even mentions result, I had to look at the API reference to check if it was even builtin or if I had to use my own result type.
  2. Usage of “Belt” is discouraged in favor of using things in the JS library. I don’t really understand why this is and it’s somewhat unclear why there are two separate standard libraries instead of just one library that specifies which methods map directly to JS and which don’t.

Perhaps this is an opportunity for improvements to the onboarding docs?

Perhaps some docs give this impression, but nowadays the recommendation is to use Belt unless you have a good reason not to. Docs which say otherwise should probably be updated.

1 Like

The main API docs here API | ReScript API say

  • Default to using the Js module. Most of the APIs in it are runtime-free and compile down to clean, readable JavaScript, which is our priority.
  • For other APIs that aren’t available in regular JavaScript (and thus don’t exist in our Jsbindings), use Belt. For example, prefer Js.Array2 over Belt.Array.

I think that’s pretty explicit advice to use Js over Belt. If that’s not the case then that should probably be updated or some further explanation of why it is the way it is might be useful.

It’s gone back and forth over the years.

  • The built-in stdlib is the OCaml one, so yes it’s completely exception based. But it will be removed eventually (I believe the current roadmap puts that in early 2023).
  • For a long time Belt was the recommendation, that’s why it was created.
  • More recently zero-cost ReScript has been the focus, the overhead of object wrappers created by Result and similar techniques can be significant in hot code. Exceptions work out to be more performant in scenarios where failures are rare. So Js is more heavily promoted now.

All three approaches have people using them in this community.

3 Likes

I’m really surprised to see that, in average Belt is much more performant than Js libraries and it tree-shakes very well.

5 Likes

I only use Js when there is no Belt equivalent in most projects. It really does not make a big difference concerning bundle size and yes its faster most of the time.

The requirement to only use Js would apply if you want to create a library without additional dependencies to serve JS/TS devs as well. Or for small self-contained node scripts, but rescript is not really designed for that since it needs a bsconfig for everything.

The docs really should clarify that.

3 Likes

+1 I think the docs should flip Js and Belt usage encouragement.
javascript lacks a proper/rich stdlib (that’s the reason why people tend to use third party libraries like immutable.js etc). why should we encourage people to use binding to those apis when we got a polished stdlib?
proper stdlib is always a selling point for me when introducing rescript to other people.

Another thing to mention is it’s better to see a unified stlib usage in projects/community. (vs using JS in one place and Belt in the other). I’m aware that it’s not possible atm but it can be achieved in the future by improving Belt (something we can’t do with Js).

2 Likes

Biggest downside of Belt is that it’s another piece to learn when starting out with ReScript. The Js api yields pretty close JS code that is easy to learn and understand. Also you’ll probably know the Js apis by heart before you even start with ReScript. That’s why we pushed for the Js namespace, and it’s not an accident that everything is mixed up like that.

Also as for right now the editor extension is also preferring the Js namespace equivalents over the Belt ones afaik.

3 Likes

Thanks for all the informative answers! It makes more sense to me now. One of the reasons I’m attracted to rescript is to avoid being bound by all the quirky, inconsistent, generally bad JS APIs. IMO providing a really solid standard library is a big selling point, especially for a language that aims to improve upon the mess that is JS.

I think that spending too much effort to keep things “familiar” for those coming from plain JS is what has made typescript become such a slow, complex, and pitfall ridden beast. If somebody is putting in the effort to learn rescript then it seems likely to me that they’re unsatisfied with what JS offers and would be willing (or even eager like I am!) to adopt different/better APIs.

That being said I’m going to spend some more time playing with rescript because right now my opinion isn’t well informed enough. I’m just someone who’s sick of typescript and wants a saner language to use within the JS ecosystem so I can spend more time writing interesting software and less time dealing with the decades of built up problems that come with JS/TS.

3 Likes

ReScript is what the community makes it. If newcomers learn Js to get them hooked and end up gravitating towards Belt for good reasons (e.g. Belt.Array.map is far better performing than Js.Array2.map) then I think that’s fine.

Not everyone needs to learn the “ultimate best rescript techniques ™” immediately. Sometimes you gotta make it easy to start so they don’t bounce off it before they see the benefits.

1 Like