mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 06:08:41 +01:00
79 lines
3.5 KiB
Plaintext
79 lines
3.5 KiB
Plaintext
Q: Can we use fibers for error-handling?
|
|
|
|
The goal here is to avoid adding support for exception handling if we're already
|
|
going to support fibers. A potential bonus would be being able to have
|
|
restartable error-handling.
|
|
|
|
The general idea is that instead of putting code in a "try" block, you throw it
|
|
onto a new fiber. If an error occurs, that fiber is paused, and returns control
|
|
back to the spawning fiber. The parent fiber can then decipher the error and
|
|
either abandon the fiber, or try to fix the error and resume somehow.
|
|
|
|
The first question is what kinds of errors is this useful for. For things like
|
|
parsing strings where failure is common and error-handling needs to be
|
|
lightweight, I think using fibers is too heavy, both in performance and code.
|
|
A better answer there is to lean on dynamic typing and return null on parse
|
|
failure.
|
|
|
|
On the other hand, it might be nice to be able to resume here if the code that
|
|
provided the string is far away and you don't want to have to manually propagate
|
|
the error out.
|
|
|
|
Programmatic errors like invalid argument types should halt the fiber but the
|
|
programmer will not want to resume that at runtime. Using the mechanism here is
|
|
fine since it would then dump a stack trace, etc. But it won't take advantage
|
|
of resuming.
|
|
|
|
Resuming is probably useful for things like IO errors where the error can't be
|
|
easily predicted beforehand but where you may want to handle it gracefully. For
|
|
example, if a file can't be opened, the caller may want to wait a while and
|
|
try again.
|
|
|
|
--
|
|
|
|
After thinking about it, maybe resuming is a bridge too far. Erlang's model is
|
|
that a failure just kills the process. I'll note that Erlang does have try and
|
|
catch, though.
|
|
|
|
The goals for error-handling in a scripting language are:
|
|
|
|
0. Have simple semantics and implementation.
|
|
|
|
1. Make it easy for developers to track down programmatic errors so they can
|
|
fix them. This means bugs like wrong argument types should fail immediately
|
|
and loudly, and should provide context (a callstack) about where the error
|
|
occurred.
|
|
|
|
2. For runtime errors like parsing an invalid string or opening a missing file,
|
|
the program should be able to easily detect the error at handle it.
|
|
|
|
3. It *may* be useful for programmers to be able to trap all errors and try to
|
|
keep the program alive, or at least log the error in a meaningful way. When
|
|
you have user-defined scripts, or a lot of code, or code authored by
|
|
non-technical people, it's nice if a failure in one part can be reported but
|
|
not take down the entire system.
|
|
|
|
Two close-at-hand examples:
|
|
|
|
- The REPL. A bug in code in the REPL shouldn't kill the whole REPL session.
|
|
|
|
- The test framework. In order to write tests in Wren that test programmatic
|
|
runtime errors, we need to be able to detect them and output something.
|
|
The test runner could just parse the error output when the entire process
|
|
dies, but that means you can only have one error test per test file.
|
|
|
|
Given those, I'm thinking:
|
|
|
|
1. Programmatic errors take down the entire fiber and dump a callstack.
|
|
Normally, they will also take down the parent fiber and so on until the
|
|
entire program goes down.
|
|
|
|
2. Runtime errors return error codes (or null). Things like parsing a string to
|
|
a number, etc. should just return an error that you are responsible for
|
|
handling.
|
|
|
|
3. When handing off control to a fiber, there is a "guarded run" method that
|
|
will run the fiber. If it fails with a programmatic error, the invoked fiber
|
|
dies, but the parent does not. It gets the callstack and error as some sort
|
|
of object it can poke at.
|