Next: , Previous: , Up: Exception handling  


6.11.3 Handling exceptions

To handle an exception when it occurs in a particular block of code, use #on:do: like this:

^[someText add: inputChar beforeIndex: i]
    on: ReadOnlyText
    do: [:sig | sig return: nil]

This code will put a handler for ReadOnlyText signals on the handler stack while the first block is executing. If such an exception occurs, and it is not handled by any handlers closer to the point of signalling on the stack (known as "inner handlers"), the exception object will pass itself to the handler block given as the do: argument.

You will almost always want to use this object to handle the exception somehow. There are six basic handler actions, all sent as messages to the exception object:

return:

Exit the block that received this #on:do:, returning the given value. You can also leave out the argument by sending #return, in which case it will be nil. If you want this handler to also handle exceptions in whatever value you might provide, you should use #retryUsing: with a block instead.

retry

Acts sort of like a "goto" by restarting the first block. Obviously, this can lead to an infinite loop if you don’t fix the situation that caused the exception.

#retry is a good way to implement reinvocation upon recovery, because it does not increase the stack height. For example, this:

  frobnicate: n [
    ^[do some stuff with n]
        on: SomeError
        do: [:sig | sig return: (self frobnicate: n + 1)]
    ]

should be replaced with retry:

  frobnicate: aNumber [
    | n |
    n := aNumber.
    ^[do some stuff with n]
        on: SomeError
        do: [:sig | n := 1 + n. sig retry]
  ]
retryUsing:

Like #retry, except that it effectively replaces the original block with the one given as an argument.

pass

If you want to tell the exception to let an outer handler handle it, use #pass instead of #signal. This is just like rethrowing a caught exception in other languages.

resume:

This is the really interesting one. Instead of unwinding the stack, this will effectively answer the argument from the #signal send. Code that sends #signal to resumable exceptions can use this value, or ignore it, and continue executing. You can also leave out the argument, in which case the #signal send will answer nil. Exceptions that want to be resumable must register this capability by answering true from the #isResumable method, which is checked on every #resume: send.

outer

This is like #pass, but if an outer handler uses #resume:, this handler block will be resumed (and #outer will answer the argument given to #resume:) rather than the piece of code that sent #signal in the first place.

None of these methods return to the invoking handler block except for #outer, and that only in certain cases described for it above.

Exceptions provide several more features; see the methods on the classes Signal and Exception for the various things you can do with them. Fortunately, the above methods can do what you want in almost all cases.

If you don’t use one of these methods or another exception feature to exit your handler, Smalltalk will assume that you meant to sig return: whatever you answer from your handler block. We don’t recommend relying on this; you should use an explicit sig return: instead.

A quick shortcut to handling multiple exception types is the ExceptionSet, which allows you to have a single handler for the exceptions of a union of classes:

^[do some stuff with n]
    on: SomeError, ReadOnlyError
    do: [:sig | ...]

In this code, any SomeError or ReadOnlyError signals will be handled by the given handler block.


Next: , Previous: , Up: Exception handling