Correct error handling separates scripts that silently corrupt data from programs you can trust.
Signal an error with raise ArgumentError, "bad id" (or raise CustomError.new(...)). Handle it with begin/rescue/else/ensure/end:
rescue SomeError => ehandles that class and its subclasses; list several classes or several clauses, most specific first.elseruns when no exception occurred.ensurealways runs, which makes it the place for cleanup.retryinsiderescuerestarts thebeginblock; always cap attempts.
Method bodies and do ... end blocks act as an implicit begin, so you can put rescue and ensure directly in a def.
The hierarchy matters. A bare rescue catches StandardError and its subclasses: ArgumentError, TypeError, NameError, NoMethodError, ZeroDivisionError, KeyError, IOError and nearly everything an application raises. It deliberately does not catch Exception subclasses like Interrupt (Ctrl-C), SystemExit or NoMemoryError. Never write rescue Exception except at the very top of a process.
Define your own errors by subclassing StandardError, usually under one base class per library (MyGem::Error), so callers can rescue all of them at once. Wrapping a low-level error inside rescue automatically sets the new error's cause, preserving the original.
The inline modifier value = Integer(s) rescue 0 exists but hides all StandardErrors; prefer explicit handling.
Example
class ShopError < StandardError; end
class OutOfStock < ShopError
def initialize(item) = super("#{item} is out of stock")
end
def buy(item, stock)
raise OutOfStock, item if stock.fetch(item).zero?
stock[item] -= 1
end
stock = { tea: 1, cake: 0 }
%i[tea cake pie].each do |item|
buy(item, stock)
puts "bought #{item}" # bought tea
rescue ShopError => e
puts "shop: #{e.message}" # shop: cake is out of stock
rescue KeyError
puts "unknown item #{item}" # unknown item pie
end
def with_log(name)
yield
ensure
puts "closed #{name}" # runs even when the block raises
end
with_log("db") { Integer("42") } # closed dbRemember
- rescue without a class catches StandardError, not Exception
- ensure always runs; use it for cleanup
- Give each library one Error base class subclassing StandardError
Watch out for
- Writing rescue Exception, which also traps Ctrl-C and exit.