Back: Dynamic Strings Up: Tutorial Forward: Creating exceptions   Top: GNU Smalltalk User's Guide Contents: Table of Contents Index: About: About this document

6.11 Exception handling in Smalltalk

Up to this point of the tutorial, you used the original Smalltalk-80 error signalling mechanism:

 
   check: num [
       | c |
       c := history
           at: num
           ifAbsent: [ ^self error: 'No such check #' ].
       ^c
   ]

In the above code, if a matching check number is found, the method will answer the object associated to it. If no prefix is found, Smalltalk will unwind the stack and print an error message including the message you gave and stack information.

 
CheckingAccount new: 31 "<0x33788>" error: No such check #
...blah blah...
CheckingAccount>>#error:
[] in Dictionary>>#at:ifAbsent:
Dictionary(HashedCollection)>>#findIndex:ifAbsent:
Dictionary>>#at:ifAbsent:
[] in CheckingAccount>>#check:
CheckingAccount>>#check:
UndefinedObject(Object)>>#executeStatements

Above we see the object that received the #error: message, the message text itself, and the frames (innermost-first) running when the error was captured by the system. In addition, the rest of the code in methods like CheckingAccount>>#check: was not executed.

So simple error reporting gives us most of the features we want:

However, there is a more powerful and complex error handling mechanism, that is exception. They are like "exceptions" in other programming languages, but are more powerful and do not always indicate error conditions. Even though we use the term "signal" often with regard to them, do not confuse them with the signals like SIGTERM and SIGINT provided by some operating systems; they are a different concept altogether.

Deciding to use exceptions instead of #error: is a matter of aesthetics, but you can use a simple rule: use exceptions only if you want to provide callers with a way to recover sensibly from certain errors, and then only for signalling those particular errors.

For example, if you are writing a word processor, you might provide the user with a way to make regions of text read-only. Then, if the user tries to edit the text, the objects that model the read-only text can signal a ReadOnlyText or other kind of exception, whereupon the user interface code can stop the exception from unwinding and report the error to the user.

When in doubt about whether exceptions would be useful, err on the side of simplicity; use #error: instead. It is much easier to convert an #error: to an explicit exception than to do the opposite.

6.11.1 Creating exceptions  Starting to use the mechanism
6.11.2 Raising exceptions  What to do when exceptional events happen
6.11.3 Handling exceptions  The other side
6.11.4 When an exception isn't handled  Default actions
6.11.5 Creating new exception classes  Your own exceptions
6.11.6 Hooking into the stack unwinding  An alternative exception handling system
6.11.7 Handler stack unwinding caveat  Differences with other languages


6.11.1 Creating exceptions

GNU Smalltalk provides a few exceptions, all of which are subclasses of Exception. Most of the ones you might want to create yourself are in the SystemExceptions namespace. You can browse the builtin exceptions in the base library reference, and look at their names with Exception printHierarchy.

Some useful examples from the system exceptions are SystemExceptions.InvalidValue, whose meaning should be obvious, and SystemExceptions.WrongMessageSent, which we will demonstrate below.

Let's say that you change one of your classes to no longer support #new for creating new instances. However, because you use the first-class classes feature of Smalltalk, it is not so easy to find and change all sends. Now, you can do something like this:

 
Object subclass: Toaster [
    Toaster class >> new [
        ^SystemExceptions.WrongMessageSent
            signalOn: #new useInstead: #toast:
    ]

    Toaster class >> toast: reason [
        ^super new reason: reason; yourself
    ]

    ...
]

Admittedly, this doesn't quite fit the conditions for using exceptions. However, since the exception type is already provided, it is probably easier to use it than #error: when you are doing defensive programming of this sort.


6.11.2 Raising exceptions

Raising an exception is really a two-step process. First, you create the exception object; then, you send it #signal.

If you look through the hierarchy, you'll see many class methods that combine these steps for convenience. For example, the class Exception provides #new and #signal, where the latter is just ^self new signal.

You may be tempted to provide only a signalling variant of your own exception creation methods. However, this creates the problem that your subclasses will not be able to trivially provide new instance creation methods.

 
Error subclass: ReadOnlyText [
    ReadOnlyText class >> signalOn: aText range: anInterval [
        ^self new initText: aText range: anInterval; signal
    ]

    initText: aText range: anInterval [
        &lt;category: 'private'>
        ...
    ]
]

Here, if you ever want to subclass ReadOnlyText and add new information to the instance before signalling it, you'll have to use the private method #initText:range:.

We recommend leaving out the signalling instance-creation variant in new code, as it saves very little work and makes signalling code less clear. Use your own judgement and evaluation of the situation to determine when to include a signalling variant.


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.


6.11.4 When an exception isn't handled

Every exception chooses one of the above handler actions by default when no handler is found, or they all use #pass. This is invoked by sending #defaultAction to the class.

One example of a default action is presented above as part of the example of #error: usage; that default action prints a message, backtrace, and unwinds the stack all the way.

The easiest way to choose a default action for your own exception classes is to subclass from an exception class that already chose the right one, as explained in the next section. For example, some exceptions, such as warnings, resume by default, and thus should be treated as if they will almost always resume.

Selecting by superclass is by no means a requirement. Specializing your Error subclass to be resumable, or even to resume by default, is perfectly acceptable when it makes sense for your design.


6.11.5 Creating new exception classes

If you want code to be able to handle your signalled exceptions, you will probably want to provide a way to pick those kinds out automatically. The easiest way to do this is to subclass Exception.

First, you should choose an exception class to specialize. Error is the best choice for non-resumable exceptions, and Notification or its subclass Warning is best for exceptions that should resume with nil by default.

Exceptions are just normal objects; include whatever information you think would be useful to handlers. Note that there are two textual description fields, a description and a message text. The description, if provided, should be a more-or-less constant string answered from a override method on #description, meant to describe all instances of your exception class. The message text is meant to be provided at the point of signalling, and should be used for any extra information that code might want to provide. Your signalling code can provide the messageText by using #signal: instead of #signal. This is yet another reason why signalling variants of instance creation messages can be more trouble than they're worth.


6.11.6 Hooking into the stack unwinding

More often useful than even #on:do: is #ensure:, which guarantees that some code is executed when the stack unwinds, whether because of normal execution or because of a signalled exception.

Here is an example of use of #ensure: and a situation where the stack can unwind even without a signal:

 
Object subclass: ExecuteWithBreak [
  | breakBlock |

  break: anObject [
    breakBlock value: anObject
  ]

  valueWithBreak: aBlock [
    "Sets up breakBlock before entering the block,
     and passes self to the block."
    | oldBreakBlock |
    oldBreakBlock := breakBlock.
    ^[breakBlock := [:arg | ^arg].
      aBlock value]
        ensure: [breakBlock := oldBreakBlock]
  ]
]

This class provides a way to stop the execution of a block without exiting the whole method as using ^ inside a block would do. The use of #ensure: guarantees (hence the name "ensure") that even if breakBlock is invoked or an error is handled by unwinding, the old "break block" will be restored.

The definition of breakBlock is extremely simply; it is an example of the general unwinding feature of blocks, that you have probably already used:

 
       (history includesKey: num)
           ifTrue: [ ^self error: 'Duplicate check number' ].

You have probably been using #ensure: without knowing. For example, File>>#withReadStreamDo: uses it to ensure that the file is closed when leaving the block.


6.11.7 Handler stack unwinding caveat

One important difference between Smalltalk and other languages is that when a handler is invoked, the stack is not unwound. The Smalltalk exception system is designed this way because it's rare to write code that could break because of this difference, and the #resume: feature doesn't make sense if the stack is unwound. It is easy enough to unwind a stack later, and is not so easy to wind it again if done too early.

For almost all applications, this will not matter, but it technically changes the semantics significantly so should be kept in mind. One important case in which it might matter is when using #ensure: blocks and exception handlers. For comparison, this Smalltalk code:

 
| n |
n := 42.
[[self error: 'error'] ensure: [n := 24]]
    on: Error
    do: [:sig | n printNl. sig return].
n printNl.

will put "42" followed by "24" on the transcript, because the n := 24 will not be executed until sig return is invoked, unwinding the stack. Similar Java code acts differently:

 
int n = 42;
try
  {
    try {throw new Exception ("42");}
    finally {n = 24;}
  }
catch (Exception e)
  {
    System.out.println (n);
  }
System.out.println (n);

printing "24" twice, because the stack unwinds before executing the catch block.



Back: Hooking into the stack unwinding Up: Exception handling Forward: Behind the scenes   Top: GNU Smalltalk User's Guide Contents: Table of Contents Index: About: About this document


This document was generated on May, 22 2008 using texi2html