Skip to content

[rfc] feature to facilitate exception-free code #7754

@tx46

Description

@tx46

background: https://forum.rescript-lang.org/t/proposing-new-syntax-for-zero-cost-unwrapping-options-results/6227

with this feature, we will be able to do early returns and propagate errors as they arise, kind of like rust's question mark-operator. this has the same sort of effect as throwing an exception in that it will bubble up through all let? assignments until the error is handled somehwere, just like exceptions.

the line between exceptions and errors thus blurs, basically resulting in two different syntaxes to do roughly the same thing, but requiring different handling methods somewhere on the caller's end.

the one place where this is not trivial to address is on the ffi boundary, where external functions may throw for various (and even unknown/unexpected) reasons, requiring you to handle exceptions in your rescript code.

as a suggestion to prevent exceptions and facilitate exception-free code (instead using Errors and let?s), some way of "wrapping" an ffi binding should be provided, e.g.:

@toResult
external somethingThatCouldThrow: string => string = "somethingThatCouldThrow"

// the above would be sugar for:
// let somethingThatCouldThrow: string => result<string, JsExn.t>
// it also needs to be able to handle `promise`s:
external somethingThatCouldThrowAsync: string => promise<string> = "somethingThatCouldThrowAsync"

// let somethingThatCouldThrowAsync: string => promise<result<string, JsExn.t>>

here, somethingThatCouldThrow would be wrapped with a try-catch that catches all errors and returns them as Error<JsExn.t>

this would facilitate static analysis for unhandled Error cases, as well as allow us to write exception-free code, moving towards a sane, predictable and exhaustive handling of errors (again, think rust).

we should also think through how this would apply to async ffi. the external call would have to be awaited inside the try-catch. what if it spawns another unawaited promise internally (global unhandled rejection)? does it make the rejection case redundant, or should Error result in rejection with the error (then we won't be able to switch on the result in .then)?

let's discuss this and see if it makes sense and how it could be implemented.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions