ZetCode

Ruby ensure Keyword

last modified October 18, 2026

This tutorial explains how to use Ruby's ensure keyword. The ensure clause guarantees code execution regardless of exceptions.

The ensure keyword defines a block that always executes after a begin block, whether an exception occurs or not. It's typically used for cleanup operations.

ensure works with begin, rescue, and else keywords. It runs after the begin body, after a rescue block, or after the else clause. It runs even if the code returns early or raises an exception.

Basic ensure Example

This example shows how ensure relates to rescue and else. The else clause runs only when no exception was raised, and ensure always runs last.

basic_ensure.rb
def operate(should_fail)
  begin
    puts "Performing operation"
    raise "Error occurred" if should_fail
    puts "Operation succeeded"
  rescue => e
    puts "Rescued: #{e.message}"
  else
    puts "No exception was raised"
  ensure
    puts "This always runs"
  end
end

operate(false)
puts "---"
operate(true)

On success, the body runs, then else, then ensure. On failure, rescue runs instead of else, and ensure still runs. This makes ensure ideal for cleanup tasks such as closing files or releasing resources.

File Handling with ensure

A common use of ensure is closing files. This example closes the file on both the success and failure paths.

file_handling.rb
def write_file(should_fail)
  file = File.open("data.txt", "w")
  file.puts "Writing important data"
  raise "Disk error" if should_fail
  puts "Write completed"
rescue => e
  puts "Error writing file: #{e.message}"
ensure
  if file
    file.close
    puts "File closed successfully"
  end
end

write_file(false)
puts "---"
write_file(true)

The file closes on both paths, preventing resource leaks. The if file check avoids calling close when File.open itself failed.

Database Connection with ensure

Database connections should always be properly closed. This example shows how ensure guarantees connection cleanup. It requires the sqlite3 gem.

database.rb
require 'sqlite3'

begin
  db = SQLite3::Database.new("test.db")
  db.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
  db.execute("INSERT INTO users (name) VALUES ('John')")
rescue SQLite3::Exception => e
  puts "Database error: #{e.message}"
ensure
  if db
    db.close
    puts "Database connection closed"
  end
end

The database connection closes whether the operations succeed or fail. This prevents connection leaks that could exhaust database resources.

ensure with Return Statements

ensure blocks run even when using return inside a method. This example demonstrates this behavior.

return_ensure.rb
def early_return
  begin
    puts "Starting method"
    return "From the method body"
  ensure
    puts "Ensure block executed"
  end
end

puts early_return

def overridden_return
  begin
    return "From the method body"
  ensure
    return "From the ensure block"
  end
end

puts overridden_return

The first method shows that ensure runs before the method returns. The second shows that a return inside ensure overrides the method's result and can discard a pending exception. Use this behavior sparingly.

Exceptions Raised in ensure

An exception raised inside ensure replaces the exception that was propagating. Ruby keeps the original exception as the new exception's cause.

ensure_exception.rb
begin
  begin
    raise "Original error"
  ensure
    raise "Error from ensure"
  end
rescue => e
  puts "Caught: #{e.message}"
  puts "Cause: #{e.cause.message}"
end

The outer rescue catches the exception raised by ensure, not the original one. The original error is still available through cause.

Nested ensure Blocks

Ruby allows nested begin blocks with their own ensure clauses. This example shows how they execute in order.

nested_ensure.rb
begin
  puts "Outer begin"

  begin
    puts "Inner begin"
    raise "Inner error"
  ensure
    puts "Inner ensure"
  end
rescue => e
  puts "Rescued: #{e.message}"
ensure
  puts "Outer ensure"
end

Each ensure belongs to its own begin block. The inner ensure runs before the outer rescue and ensure, so cleanup happens from the inside out.

ensure with Threads

ensure blocks in threads execute when the thread terminates, whether normally or due to an exception. This example demonstrates thread cleanup.

thread_ensure.rb
thread = Thread.new do
  begin
    puts "Thread working"
    raise "Thread error"
  ensure
    puts "Thread cleanup"
  end
end

thread.report_on_exception = false

begin
  thread.join
rescue => e
  puts "Main rescued: #{e.message}"
end

puts "Main program continues"

The thread's ensure block runs before the thread terminates. Thread#join re-raises the thread's exception in the calling thread, so the example rescues it. report_on_exception = false suppresses Ruby's default warning about the terminated thread.

ensure in Method Definitions

Methods can use ensure without explicit begin blocks. This example shows the shorthand syntax for method-level ensure.

method_ensure.rb
def process_data(should_fail)
  puts "Processing data"
  raise "Processing error" if should_fail
rescue => e
  puts "Error: #{e.message}"
ensure
  puts "Cleanup resources"
end

process_data(false)
puts "---"
process_data(true)

The entire method body acts as an implicit begin block, so rescue and ensure work without an explicit begin. The ensure clause runs regardless of how the method exits.

Source

Ruby Exception Handling Documentation

This tutorial covered Ruby's ensure keyword with practical examples showing resource cleanup, return values, nested blocks, threads, and method-level guarantees.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Ruby tutorials.