Skip to content

08 — Errors and Exceptions

One line: Ruby's exceptions are objects raised by a method and rescued by class, with a default — StandardError — that decides what a plain rescue sees, a cause chain that keeps the original error, and two things Python folds into exceptions kept apart: the throw/catch jump and the warning channel.

A Python programmer arrives with the right model: try/except/else/finally is begin/rescue/else/ensure, raise is raise, custom errors are subclasses, and sys.exit really raises SystemExit in both languages. What changes is the defaults and the extras. A bare rescue catches StandardError — the half of the tree a program should handle — and lets SystemExit, Interrupt, NoMemoryError, SystemStackError and every ScriptError (including, surprisingly, NotImplementedError) pass, which is the opposite of Python's bare except: and the same as its except Exception:. raise has four spellings and a hook (Klass.exception, obj.exception) that lets any object become an error. retry re-runs a block from inside its rescue; Python needs a loop. rescue has a one-line modifier form; Python rejected the idea (PEP 463). A raise inside a rescue records a cause, one field where Python has __context__, __cause__ and __suppress_context__.

The extras are the two mechanisms Ruby keeps separate. throw/catch is a labelled non-local jump that rescue never sees; Python uses an exception class for the same job, and an except Exception in between will intercept it. warn and Warning are a channel to stderr with category switches and a lint mode (-w), not exceptions; Python's Warning is an Exception, which is why -W error works there. The chapter ends with the library's errors page — twenty-nine messages, what each means, the fix, and the lesson that explains it. Every claim on every page is printed by a program: Ruby 4.0 messages are stable and appear in full; the Python twins print exception types only, because CPython rewords its messages between 3.12 and 3.14.

Lesson Level The one thing
rescue, else and ensure run in a fixed order 101 body, then rescue or else, then ensure — even on return; retry re-runs the body; begin … end is an expression
A bare rescue catches StandardError, not everything 201 SystemExit, Interrupt, SystemStackError and NotImplementedError slip past rescue => e; rescue Exception swallows exit
raise has four forms 201 raise "msg", raise Klass, raise Klass, "msg", raise Klass.new; a bare raise re-raises $!; a non-exception is a TypeError
Custom exceptions subclass StandardError 201 a default message via super, readers for extra data, a family caught by its base, detailed_message, the exception hook
Exceptions have a cause 201 a raise inside rescue records the handled exception; cause: sets it, cause: nil hides it, a loop is an ArgumentError
rescue works as a modifier, for StandardError only 201 expr rescue fallback binds after =, cannot stand bare in an argument list, is legal in { } where a rescue clause is not, and hides typos
throw and catch are not exceptions 201 a labelled jump that returns a value, skips rescue, runs ensure, matches tags by identity; unmatched, it is UncaughtThrowError
exit raises SystemExit 201 exit unwinds and runs at_exit handlers in reverse; exit! does not; abort is exit 1 plus stderr; an unrescued Interrupt ends the process by the signal
Warnings go to stderr, and -w finds more of them 201 warn, $VERBOSE, Warning[:deprecated], what -w reports on a sloppy file, and capturing warnings in-process
Common error messages 101 twenty-nine messages with the code, the mistake, the fix and the lesson — and the Python type that means the same

Read more