Labs ICT
โญ Pro Login

Exception Handling

begin/rescue/ensure for robust code.

Exception Handling

Ruby uses exceptions to handle errors. When something goes wrong, Ruby raises an exception object. You can catch these exceptions and handle them gracefully instead of letting your program crash. The begin/rescue/ensure/end structure is your safety net.

begin
  result = 10 / 0
rescue ZeroDivisionError => e
  puts "Error: #{e.message}"
ensure
  puts "This runs no matter what"
end
# => Error: divided by 0
# => This runs no matter what
Try it Yourself ->

Exception Class Hierarchy

Ruby has a hierarchy of exception classes. Exception is the base class. StandardError is the main class you'll rescue from. RuntimeError is raised by raise. Knowing the hierarchy helps you catch the right exceptions.

# The hierarchy:
# Exception
#   โ”œโ”€โ”€ StandardError
#   โ”‚     โ”œโ”€โ”€ RuntimeError
#   โ”‚     โ”œโ”€โ”€ TypeError
#   โ”‚     โ”œโ”€โ”€ ArgumentError
#   โ”‚     โ”œโ”€โ”€ NameError
#   โ”‚     โ”‚     โ””โ”€โ”€ NoMethodError
#   โ”‚     โ”œโ”€โ”€ IOError
#   โ”‚     โ”œโ”€โ”€ Errno::* (system errors)
#   โ”‚     โ””โ”€โ”€ ZeroDivisionError
#   โ”œโ”€โ”€ SignalException
#   โ”‚     โ””โ”€โ”€ Interrupt
#   โ””โ”€โ”€ SystemExit

# Always rescue StandardError, not Exception
begin
  risky_operation
rescue StandardError => e
  puts "Caught: #{e.class} โ€” #{e.message}"
end
Try it Yourself ->

Custom Exceptions

You can create your own exception classes by inheriting from StandardError. This lets you raise and rescue domain-specific errors in your application.

class InsufficientFundsError < StandardError
  attr_reader :balance, :amount

  def initialize(balance, amount)
    @balance = balance
    @amount = amount
    super("Cannot withdraw $#{amount} โ€” balance is $#{balance}")
  end
end

class BankAccount
  def initialize(balance)
    @balance = balance
  end

  def withdraw(amount)
    if amount > @balance
      raise InsufficientFundsError.new(@balance, amount)
    end
    @balance -= amount
  end
end

account = BankAccount.new(100)
begin
  account.withdraw(150)
rescue InsufficientFundsError => e
  puts e.message
  puts "Tried: $#{e.amount}, Available: $#{e.balance}"
end
# => Cannot withdraw $150 โ€” balance is $100
# => Tried: $150, Available: $100
Try it Yourself ->

raise and retry

raise creates and throws an exception. retry runs the rescue block from the beginning. Use retry carefully โ€” make sure the problem is actually fixable or you'll loop forever.

attempts = 0

begin
  attempts += 1
  puts "Attempt #{attempts}..."
  raise "Connection failed" if attempts < 3
  puts "Connected!"
rescue RuntimeError => e
  puts "Error: #{e.message}"
  retry if attempts < 3
  puts "Giving up after #{attempts} attempts"
end
# => Attempt 1...
# => Error: Connection failed
# => Attempt 2...
# => Error: Connection failed
# => Attempt 3...
# => Connected!
Try it Yourself ->

catch/throw for Flow Control

catch and throw aren't for error handling โ€” they're for non-local exits. They let you break out of deeply nested code instantly. Think of them as labeled gotos that are safe and structured.

catch(:done) do
  puts "Starting process..."
  
  10.times do |i|
    if i == 5
      puts "Found the target at #{i}"
      throw(:done, "Success!")
    end
    puts "Processing #{i}"
  end
  
  puts "This never runs"
end
# => Starting process...
# => Processing 0
# => Processing 1
# => Processing 2
# => Processing 3
# => Processing 4
# => Found the target at 5

# Useful for breaking out of nested loops
def find_in_matrix(matrix, target)
  catch(:found) do
    matrix.each_with_index do |row, i|
      row.each_with_index do |val, j|
        throw(:found, [i, j]) if val == target
      end
    end
    nil  # not found
  end
end
Try it Yourself ->

๐Ÿงช Quick Quiz

What keyword catches exceptions?