Skip to main content
Engineering LibreTexts

12.9: Handling Exceptions

  • Page ID
    45959
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    When an exception is signaled, the handler has several choices about how to handle it. In particular, it may:

    1. abandon the execution of the protected block by simply specifying an alternative result – it is part of the protocol but not used since it is similar to return;
    2. return an alternative result for the protected block by sending return: aValue to the exception object;
    3. retry the protected block, by sending retry, or try a different block by sending retryUsing:;
    4. resume the protected block at the failure point by sending resume or resume:;
    5. pass the caught exception to the enclosing handler by sending pass; or
    6. resignal a different exception by sending resignalAs: to the exception.

    We will briefly look at the first three possibilities, and then take a closer look at the remaining ones.

    Abandon the protected block

    The first possibility is to abandon the execution of the protected block, as follows:

    answer := [ |result|
        result := 6 * 7.
        Error signal.
        result "This part is never evaluated" ]
            on: Error
            do: [ :ex | 3 + 4 ].
    answer    →    7
    

    The handler takes over from the point where the error is signaled, and any code following in the original block is not evaluated.

    Return a value with return:

    A block returns the value of the last statement in the block, regardless of whether the block is protected or not. However, there are some situations where the result needs to be returned by the handler block. The message return: aValue sent to an exception has the effect of returning aValue as the value of the protected block:

    result := [Error signal]
        on: Error
        do: [ :ex | ex return: 3 + 4 ].
    result    →    7 
    

    The ANSI standard is not clear regarding the difference between using do: [:ex | 100 ] and do: [:ex | ex return: 100] to return a value. We suggest that you use return: since it is more intention-revealing, even if these two expressions are equivalent in Pharo.

    A variant of return: is the message return, which returns nil.
    Note that, in any case, control will not return to the protected block, but will be passed on up to the enclosing context.

    6 * ([Errorsignal] on: Error do: [ :ex| ex return: 3 + 4])    →    42
    

    Retry a computation with retry and retryUsing:

    Sometimes we may want to change the circumstances that led to the exception and retry the protected block. This is done by sending retry or retryUsing: to the exception object. It is important to be sure that the conditions that caused the exception have been changed before retrying the protected block, or else an infinite loop will result:

    [Error signal] on: Error do: [:ex | ex retry] "will loop endlessly"
    

    Here is another example. The protected block is re-evaluated within a modified environment where theMeaningOfLife is properly initialized:

    result := [ theMeaningOfLife * 7 ] "error -- theMeaningOfLife is nil"
        on: Error
        do: [:ex | theMeaningOfLife := 6. ex retry ].
    result    →    42 
    

    The message retryUsing: aNewBlock enables the protected block to be replaced by aNewBlock. This new block is executed and is protected with the same handler as the original block.

    x := 0.
    result := [ x/x ] "fails for x=0"
        on: Error
        do: [:ex |
            x := x + 1.
            ex retryUsing: [1/((x-1)*(x-2))]    "fails for x=1 and x=2"
        ].
    result    →    (1/2)    "succeeds when x=3"
    

    The following code loops endlessly:

    [1 / 0] on: ArithmeticError do: [:ex | ex retryUsing: [ 1 / 0 ]]
    

    whereas this will signal an Error:

    [1 / 0] on: ArithmeticError do: [:ex | ex retryUsing: [ Error signal ]]
    

    As another example, keep in mind the file handling code we saw earlier in which we printed a message to the Transcript when a file is not found. Instead, we could prompt for the file as follows:

    [(StandardFileStream oldFileNamed: 'error42.log') contentsOfEntireFile]
        on: FileDoesNotExistException
        do: [:ex | ex retryUsing: [FileList modalFileSelector contentsOfEntireFile] ]
    

    Resuming execution

    A method that signals an exception that isResumable can be resumed at the place immediately following the signal. An exception handler may therefore perform some action, and then resume the execution flow. This behavior is achieved by sending resume: to the exception in the handler. The argument is the value to be used in place of the expression that signaled the exception. In the following example we signal and catch MyResumableTestError, which is defined in the Tests-Exceptions category:

    result := [ | log |
        log := OrderedCollection new.
        log addLast: 1.
        log addLast: MyResumableTestError signal.
        log addLast: 2.
        log addLast: MyResumableTestError signal.
        log addLast: 3.
        log ]
            on: MyResumableTestError
            do: [ :ex | ex resume: 0 ].
    result    →    an OrderedCollection(1 0 2 0 3)
    

    Here we can clearly see that the value of MyResumableTestError signal is the value of the argument to the resume: message.

    The message resume is equivalent to resume: nil.

    The usefulness of resuming an exception is illustrated by the following functionality which loads a package. When installing packages, warnings may be signaled and should not be considered fatal errors, so we should simply ignore the warning and continue installing.

    The class PackageInstaller does not exist, though here is a sketch of a possible implementation.

    PackageInstaller»installQuietly: packageNameCollection
        ....
        [ self install ] on: Warning do: [ :ex | ex resume ].
    

    Another situation where resumption is useful is when you want to ask the user what to do. For example, suppose that we were to define a class ResumableLoader with the following method:

    ResumableLoader»readOptionsFrom: aStream
        | option |
        [aStream atEnd]
            whileFalse: [option := self parseOption: aStream.
                "nil if invalid"
                option isNil
                    ifTrue: [InvalidOption signal]
                    ifFalse: [self addOption: option]]. 
    

    If an invalid option is encountered, we signal an InvalidOption exception. The context that sends readOptionsFrom: can set up a suitable handler:

    ResumableLoader»readConfiguration
        | stream |
        stream := self optionStream.
        [self readOptionsFrom: stream]
            on: InvalidOption
            do: [:ex | (UIManager default confirm: 'Invalid option line. Continue loading?')
                ifTrue: [ex resume]
                ifFalse: [ex return]].
        stream close 
    

    Note that to be sure to close the stream, the stream close should guarded by an ensure: invocation.

    Depending on user input, the handler in readConfiguration might return nil, or it might resume the exception, causing the signal message send in readOptionsFrom: to return and the parsing of the options stream to continue.

    Note that InvalidOption must be resumable; it suffices to define it as a subclass of Exception.

    You can have a look at the senders of resume: to see how it can be used.

    Passing exceptions on

    To illustrate the remaining possibilities for handling exceptions such as passing an exception, we will look at how to implement a generalization of the perform: method. If we send perform: aSymbol to an object, this will cause the message named aSymbol to be sent to that object:

    5 perform: #factorial    →    120    "same as: 5 factorial"
    

    Several variants of this method exist. For example:

    1 perform: #+ withArguments: #(2)    →    3    "same as: 1 + 2"
    

    These perform:-like methods are very useful for accessing an interface dynamically, since the messages to be sent can be determined at run-time. One message that is missing is one that sends a cascade of unary messages to a given receiver. A simple and naive implementation is:

    Object»performAll: selectorCollection
        selectorCollection do: [:each | self perform: each]    "aborts on first error"
    

    This method could be used as follows:

    Morph new performAll: #( #activate #beTransparent #beUnsticky)
    

    However, there is a complication. There might be a selector in the collection that the object does not understand (such as #activate). We would like to ignore such selectors and continue sending the remaining messages. The following implementation seems to be reasonable:

    Object»performAll: selectorCollection
        selectorCollection do: [:each |
            [self perform: each]
                on: MessageNotUnderstood
                do: [:ex | ex return]]    "also ignores internal errors"
    

    On closer examination we notice another problem. This handler will not only catch and ignore messages not understood by the original receiver, but also any messages sent and not understood in methods for messages that are understood! This will hide programming errors in those methods, which is not our intent. To address this, we need our handler to analyze the exception to see if it was indeed caused by the attempt to perform the current selector. Here is the correct implementation.

    Code \(\PageIndex{1}\) (Pharo):

    Object»performAll: selectorCollection
        selectorCollection do: [:each |
            [self perform: each]
                on: MessageNotUnderstood
                do: [:ex | (ex receiver == self and: [ex message selector == each])
                    ifTrue: [ex return]
                    ifFalse: [ex pass]]]    "pass internal errors on"
    

    This has the effect of passing on MessageNotUnderstood errors to the surrounding context when they are not part of the list of messages we are performing. The pass message will pass the exception to the next applicable handler in the execution stack.

    If there is no next handler on the stack, the defaultAction message is sent to the exception instance. The pass action does not modify the sender chain in any way — but the handler that controls it to may do so. Like the other messages discussed in this section, pass is special — it never returns to the sender.

    The goal of this section has been to demonstrate the power of exceptions. It should be clear that while you can do almost anything with exceptions, the code that results is not always easy to understand. There is often a simpler way to get the same effect without exceptions; see Code 13.15.1 on page 289 for a better way to implement performAll:.

    Resending exceptions

    Suppose that in our performAll: example we no longer want to ignore selectors not understood by the receiver, but instead we want to consider an occurrence of such a selector as an error. However, we want it to be signaled as an application-specific exception, let’s say InvalidAction, rather than the generic MessageNotUnderstood. In other words, we want the ability to “resignal” a signaled exception as a different one.

    It might seem that the solution would simply be to signal the new exception in the handler block. The handler block in our implementation of performAll: would be:

    [:ex | (ex receiver == self and: [ex message selector == each])
        ifTrue: [InvalidAction signal]    "signals from the wrong context"
        ifFalse: [ex pass]] 
    

    A closer look reveals a subtle problem with this solution, however. Our original intent was to replace the occurrence of MessageNotUnderstood with InvalidAction. This replacement should have the same effect as if InvalidAction were signaled at the same place in the program as the original MessageNotUnderstood exception. Our solution signals InvalidAction in a different location. The difference in locations may well lead to a difference in the applicable handlers.

    To solve this problem, resignaling an exception is a special action handled by the system. For this purpose, the system provides the message resignalAs:. The correct implementation of a handler block in our performAll: example would be:

    [:ex | (ex receiver == self and: [ex message selector == each])
        ifTrue: [ex resignalAs: InvalidAction]    "resignals from original context"
        ifFalse: [ex pass]]
    

    This page titled 12.9: Handling Exceptions is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Alexandre Bergel, Damien Cassou, Stéphane Ducasse, Jannik Laval (Square Bracket Associates) via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.