Exceptions
The value of on:do: is the value of the protected block, or of the handler block if an exception was handled.
24.1 Signaling and handling
The value of on:do: is the value of the protected block, or of the handler block if an exception was handled.
[ 10 / 0 ] on: ZeroDivide do: [ :ex | 0 ]. "→ 0"
[ #(1 2 3) at: 10 ] on: SubscriptOutOfBounds do: [ :ex | nil ].
[ Dictionary new at: #missing ] on: KeyNotFound do: [ :ex | 'default' ].
[ nil foo ] on: MessageNotUnderstood do: [ :ex | ex message selector ]. "→ #foo"
[ self error: 'boom' ] on: Error do: [ :ex | ex messageText ]. "→ 'boom'"
[ 1/0 ] on: ZeroDivide, MessageNotUnderstood do: [ :ex | -1 ]. "ExceptionSet with ,"24.2 The exception hierarchy (simplified)
Exception
├── Error (not resumable by default)
│ ├── ZeroDivide (ArithmeticError)
│ ├── MessageNotUnderstood
│ ├── SubscriptOutOfBounds, NotFound, KeyNotFound
│ ├── AssertionFailure (TestFailure in SUnit)
│ ├── FileException, FileDoesNotExistException
│ └── your own errors
├── Notification (resumable; unhandled → returns nil, no debugger)
│ └── Warning (resumable; unhandled → debugger with Proceed)
└── Halt24.3 Signaling
self error: 'Something went wrong' signals a generic Error; equivalently Error signal: 'msg' or Error new signal: 'msg'. Warning signal: 'Disk almost full' is resumable; any exception class can be signaled with ZeroDivide new signal.
24.4 What a handler can do
Message to ex ex return: value Exit the on:do: expression with value (default if the handler just falls off the end) Re-evaluate the protected block ex retryUsing: aBlock Replace the protected block and retry ex resume: value
Return value from the signal expression and continue (resumable exceptions only)
Re-signal to an outer handler (as if not handled here) Like pass, but if the outer handler resumes, come back here Re-signal the exception ex messageText / ex description ex signalerContext The stack frame where it was signaled
"Retry with a counter"
| attempts result |
attempts := 0.
result := [ attempts := attempts + 1.
attempts < 3 ifTrue: [ Error signal: 'flaky' ].
'succeeded after ', attempts printString ]
on: Error
do: [ :ex | ex retry ].
result. "→ 'succeeded after 3'"
"Resume a warning with a value"
[ (Warning signal: 'use default?') ifNil: [ 'no answer' ] ]
on: Warning do: [ :ex | ex resume: 'yes' ]. "→ 'yes'"
"ZeroDivide is resumable in Pharo"
[ 10 / 0 + 1 ] on: ZeroDivide do: [ :ex | ex resume: 0 ]. "→ 1"24.5 ensure: and ifCurtailed:
writeStreamDo:/readStreamDo: already wrap ensure: for you.
| stream |
stream := 'data.txt' asFileReference readStream.
[ stream upToEnd ] ensure: [ stream close ]. "always runs (like finally)"
[ self riskyOperation ] ifCurtailed: [ self rollback ]. "runs only on abnormal exit (error, non-local return, process termination)"24.6 Custom exceptions
Create a hierarchy (BankError → InsufficientFunds, AccountClosed) so clients can catch broad or narrow categories.
Error << #InsufficientFunds
slots: { #requested . #available };
package: 'Bank-Core'
InsufficientFunds >> requested: r available: a
requested := r.
available := a
InsufficientFunds >> messageText
^ 'Insufficient funds: requested ', requested printString,
', available ', available printString
InsufficientFunds >> isResumable
^ false
BankAccount >> withdraw: anAmount
anAmount > balance ifTrue: [
^ InsufficientFunds new requested: anAmount available: balance; signal ].
balance := balance - anAmount[ (BankAccount owner: 'Ada' balance: 10) withdraw: 50 ]
on: InsufficientFunds
do: [ :ex | ex messageText ]. "→ 'Insufficient funds: requested 50, available 10'"24.7 Notifications for context passing
A Notification that nobody handles simply returns nil — handy for implicit parameters:
Pharo also provides DynamicVariable subclasses (MyVar value: x during: [...]) for the same purpose.
Notification << #CurrentUser slots: {}; package: 'App'.
"Deep in the call stack:"
(CurrentUser signal) ifNil: [ 'anonymous' ].
"At the top:"
[ self handleRequest ] on: CurrentUser do: [ :ex | ex resume: 'ada' ].24.8 Unhandled errors and the debugger
An unhandled Error opens the debugger — not a crash. You can inspect, fix the code, and proceed. In headless mode, unhandled errors are logged (to stdout/stderr or PharoDebug.log) and the process is terminated (or the image exits, depending on the command-line handler).