Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between "errors/exceptions" and "throw/catch"?

I'm a bit confused over error handling in Rebol. It has THROW and CATCH constructions:

>> repeat x 10 [if x = 9 [throw "Nine!"]]
** Throw error: no catch for throw: make error! 2

>> catch [repeat x 10 [if x = 9 [throw "Nine!"]]]
== "Nine!"

But this throwing and catching is unrelated to the handler passed to the /EXCEPT refinement on TRY:

>> try/except [throw "Nine!"] [print "Exception handled!"]
** Throw error: no catch for throw: make error! 2

That's very specific to Rebol's error type:

>> try/except [print 1 / 0] [print "Error handled!"]
Error handled!

If you want you can trigger your own errors, but not using THROW as you would in other languages. Throwing errors will just lead to a complaint about not being caught, as any other value type:

>> try/except [throw make error! "Error string"] [print "Error handled!"]
** Throw error: no catch for throw: make error! 2

You must DO them to get the evaluator to try and execute something of type error! to cause what it considers an "exception":

>> try/except [do make error! "Error string"] [print "Error handled!"]
Error handled!

(Note: You can use pre-made errors apparently such as cause-error 'Script 'invalid-type function! -- see system/catalog/errors for more.)

Leaving off the /EXCEPT refinement will let you receive any errors as a value. However, that seems to make it impossible to distinguish between whether the error was invoked or not:

>> probe try [do make error! "Some error"]
make error! [
    code: 800
    type: 'User
    id: 'message
    arg1: "some error"
    arg2: none
    arg3: none
    near: none
    where: none
]
** User error: "Some error"

>> probe try [make error! "Some error"]
make error! [
    code: 800
    type: 'User
    id: 'message
    arg1: "Some error"
    arg2: none
    arg3: none
    near: none
    where: none
]
** User error: "Some error"

It would seem that CATCH has a similar lack of distinction between returning a value and having a value thrown. But there's a tool to get around that with "named throws":

>> code: [repeat x 10 [if x = 9 [throw/name "Nine!" 'number]]]

>> catch/name [do code] 'number
== "Nine!"

>> catch/name [do code] 'somethingelse
** Throw error: no catch for throw: make error! 2

So now for the questions:

  • Is there a practical value to this separation? How does one decide whether to use THROW and CATCH or a DO of an error and handle it with TRY/EXCEPT?

  • Does this distinction have precedent in other languages, and would /EXCEPT be better named /ON-ERROR or something?

  • Why does it say "no catch for throw: make error! 2" instead of something more informative? What is the make error! 2?

like image 819
HostileFork says dont trust SE Avatar asked Jan 10 '23 08:01

HostileFork says dont trust SE


2 Answers

"Is there a practical value to this separation? How does one decide whether to use THROW and CATCH or a DO of an error and handle it with TRY/EXCEPT?"

As has been pointed out in the other answers as well, THROW and CATCH form an unwinding construct (a non-local exit) that in and of itself is concerned with control flow and doesn't necessarily have anything to do with error handling. THROW and CATCH is first and foremost a straightforward method to influence control flow and is a rather fundamental building block for other custom control flow constructs.

As such, THROW and CATCH can of course also be used for building an "exception handling"-like error system as seen in many contemporary mainstream languages, because those "exception handling" systems are one instance of non-local control flow.

Rebol's error! on the other hand is the primary method to signal and propagate evaluation errors.

So generally, the decision is easy to make: if you want to cause an error, use error!. If you want to influence control flow to unwind controllably, use THROW / CATCH.

Two more remarks regarding terminology:

  • Discussion around Rebol errors has become more careful to use "cause an error" as a phrase instead of "throw an error", to avoid confusion.
  • In a similar vein, references that call THROW / CATCH "Rebol's exception handling" (as @HostileFork alludes to in one comment) need to be reworked.

"Does this distinction have precedent in other languages"

Yes, the distinction between evaluation errors and non-local exits has precedent in other languages, especially the Lisp family. A few quick references:

  • Common Lisps's catch and throw vs condition signaling
  • Emacs Lisp's catch and throw vs error signaling
  • Dylan's block vs the condition system

"Why does it say "no catch for throw: make error! 2" instead of something more informative? What is the make error! 2?"

That's a bug. (Good catch!) I'd say that the core of the error message ("no catch for throw") is rather informative already, but the make error! 2 is a bug (it should rather show the thrown value here.)

"would /EXCEPT be better named /ON-ERROR or something?"

Renaming /EXCEPT is debatable. As such, I'd say this is a discussion not that suitable for SO Q&A and better left to other fora.

like image 100
earl Avatar answered Jan 12 '23 20:01

earl


I would say that the regular throw is not for error handling, but for shortcuts or goto. It serves something as a break in loops etc. You could use it also to mimic a continue

See is-there-an-equivalent-to-continue

like image 45
sqlab Avatar answered Jan 12 '23 20:01

sqlab