Question

Ruby begin, rescue, and ensure Explained: Is ensure Like finally?

rubyexceptionerror-handling

Question

In Ruby, is ensure the equivalent of finally in C#?

For example, should file handling be written like this?

file = File.open("myFile.txt", "w")

begin
  file << "#{content}\n"
rescue
  # handle the error here
ensure
  file.close unless file.nil?
end

Or should it be written like this?

file = File.open("myFile.txt", "w")

begin
  file << "#{content}\n"
  file.close
rescue
  # handle the error here
ensure
  file.close unless file.nil?
end

Does ensure run no matter what, even when no exception is raised?

Short Answer

By the end of this page, you will understand how Ruby's begin, rescue, and ensure blocks work together, when ensure is executed, and how to use them safely for file handling and cleanup. You will also see the preferred Ruby style for opening and closing files.

Concept

Ruby uses begin, rescue, and ensure for exception handling and cleanup.

  • begin wraps code that might fail.
  • rescue handles exceptions.
  • ensure runs cleanup code.

Yes: ensure is the Ruby concept most similar to finally in C#.

What ensure does

The ensure block is executed whether:

  • an exception happens
  • no exception happens
  • the exception is rescued
  • the method returns early

Its main purpose is cleanup:

  • closing files
  • releasing locks
  • disconnecting from databases
  • cleaning temporary resources

Why this matters

When you open a file, socket, or database connection, you should make sure it gets closed even if something goes wrong. Without cleanup, your program can leak resources or leave data in an inconsistent state.

Important detail

ensure does not mean "only run on error." It means "always run when leaving this block."

That is why file.close usually belongs in ensure, not both in the main code and in ensure. If you close it in both places, you are duplicating cleanup logic.

Better Ruby style for files

In Ruby, the most idiomatic approach is often to use block form with File.open:

File.open("myFile.txt", "w") do |file|
  file << "#{content}\n"
end

Ruby automatically closes the file when the block ends, even if an exception occurs. This is usually better than manually calling close.

Mental Model

Think of begin, rescue, and ensure like visiting a workshop:

  • begin: you start the job
  • rescue: if something goes wrong, you handle the problem
  • ensure: before leaving, you always clean up your tools and lock the door

Even if the work goes perfectly, you still lock the door. Even if the work fails halfway through, you still lock the door.

That is exactly how ensure behaves: it is the "always clean up before exiting" step.

Take Quiz

Syntax and Examples

Basic syntax

begin
  # code that might raise an exception
rescue
  # code that handles the exception
ensure
  # code that always runs
end

Example: cleanup always runs

begin
  puts "Writing data"
rescue
  puts "Something went wrong"
ensure
  puts "Cleanup happens here"
end

Output:

Writing data
Cleanup happens here

Even though no exception happened, ensure still ran.

Example: with an exception

begin
  puts "Before error"
  raise "Oops"
rescue
  puts "Error handled"
ensure
  puts "Cleanup happens here"
end

Output:

Before error
Error handled
Cleanup happens here

File example with manual cleanup

file = File.open("myFile.txt", "w")

begin
  file << "Hello\n"
rescue => e
  puts "Error: #{e.message}"
ensure
  file.close unless file.nil?
end

This works because the file is closed whether writing succeeds or fails.

Preferred file example

begin
  File.open("myFile.txt", "w") do |file|
    file << "Hello\n"
  end
rescue => e
  puts "Error: #{e.message}"
end

This is cleaner because Ruby closes the file automatically at the end of the block.

Step by Step Execution

Consider this code:

file = File.open("myFile.txt", "w")

begin
  file << "Hello\n"
rescue => e
  puts "Error: #{e.message}"
ensure
  puts "Closing file"
  file.close unless file.nil?
end

Step by step

  1. File.open("myFile.txt", "w") opens the file for writing.
  2. The begin block starts.
  3. file << "Hello\n" writes text to the file.
  4. No exception happens, so rescue is skipped.
  5. The ensure block runs.
  6. puts "Closing file" prints a message.
  7. file.close unless file.nil? closes the file.

If writing fails

Imagine line 3 raises an exception.

  1. Ruby jumps out of the remaining code in begin.
  2. The rescue block runs and stores the exception in e.
  3. The error message is printed.
  4. The ensure block still runs.
  5. The file is closed.

Key takeaway

ensure runs in both cases:

  • success
  • failure

Real World Use Cases

Common uses for ensure

Closing files

file = File.open("log.txt", "a")
begin
  file.puts("New log entry")
ensure
  file.close unless file.nil?
end

Releasing database connections

A connection might need to be returned to a pool even if a query fails.

Unlocking resources

If your code acquires a mutex or lock, ensure is a safe place to release it.

Cleaning temporary state

You may create a temp file, change a config value, or switch directories temporarily. ensure helps restore the original state.

API and network code

Sockets, streams, and HTTP resources often need cleanup, especially when exceptions happen unexpectedly.

Take Quiz

Real Codebase Usage

In real Ruby projects, developers often use ensure for cleanup that must always happen.

Common patterns

Guarding resource cleanup

file = nil

begin
  file = File.open("data.txt", "w")
  file.write("example")
rescue => e
  warn "Write failed: #{e.message}"
ensure
  file.close if file
end

This pattern is useful when File.open itself might fail. Initializing file = nil prevents a variable error in ensure.

Error logging plus cleanup

begin
  perform_task
rescue => e
  logger.error(e.message)
  raise
ensure
  cleanup_temp_files
end

Here, the error is logged, then re-raised, and cleanup still happens.

Early returns

def save_data
  file = File.open("data.txt", "w")

  begin
    return if some_condition?
    file.write("saved")
  ensure
    file.close
  end
end

Even with return, the ensure block runs before leaving the method.

Preferred block-based resource management

In many Ruby codebases, developers avoid manual ensure for files when the API already supports a block:

File.open("data.txt", "w") do |file|
  file.write("saved")
end

This is shorter, safer, and more idiomatic.

Common Mistakes

1. Closing the file twice

Broken example:

file = File.open("myFile.txt", "w")

begin
  file << "Hello\n"
  file.close
ensure
  file.close unless file.nil?
end

Why it is a problem:

  • cleanup logic is duplicated
  • it is harder to maintain
  • double-closing is unnecessary

Better:

file = File.open("myFile.txt", "w")

begin
  file << "Hello\n"
ensure
  file.close unless file.nil?
end

2. Not handling failure when opening the file

Broken example:

file = File.open("missing/path/file.txt", "w")

begin
  file.write("data")
ensure
  file.close unless file.nil?
end

Why it is a problem:

  • if File.open raises an exception, execution never reaches the begin
  • ensure will not protect code that never entered the block

Better:

file = nil

begin
  file = File.open("missing/path/file.txt", "w")
  file.write("data")
rescue => e
  puts e.message
ensure
  file.close if file
end

3. Using rescue without capturing the exception

Less helpful:

begin
  risky_work
rescue
  puts "Something failed"
end

Better:

begin
  risky_work
rescue => e
  puts "Something failed: #{e.message}"
end

4. Rescuing too broadly

Sometimes beginners rescue everything and hide real bugs.

begin
  risky_work
rescue
  nil
end

This can make debugging very difficult. Handle errors intentionally and only when you know what to do with them.

5. Forgetting that block-form File.open is simpler

Instead of manual open/close:

file = File.open("myFile.txt", "w")
begin
  file.write("hello")
ensure
  file.close if file
end

Prefer:

File.open("myFile.txt", "w") do |file|
  file.write("hello")
end

Comparisons

ensure vs rescue

ConceptPurposeRuns on success?Runs on error?
rescueHandle an exceptionNoYes
ensureAlways perform cleanupYesYes

ensure in Ruby vs finally in C#

RubyC#Purpose
ensurefinallyRun code whether or not an exception occurs

Manual file closing vs block form

ApproachExampleRecommended?Why
Manual close with ensurefile = File.open(...)SometimesNeeded when you must manage the object manually
Block form`File.open(...) dofile... end`

Two versions from the question

VersionGood choice?Reason
Close only in ensureYesCleanup is centralized in one place
Close in begin and ensureNoRedundant and unnecessary
Take Quiz

Cheat Sheet

Quick reference

Basic structure

begin
  risky_code
rescue => e
  handle_error(e)
ensure
  cleanup_code
end

Rules

  • begin contains code that may fail.
  • rescue runs only if an exception is raised.
  • ensure runs whether an exception happens or not.
  • ensure is Ruby's closest equivalent to C# finally.

File handling

Manual style:

file = nil
begin
  file = File.open("data.txt", "w")
  file.write("hello")
ensure
  file.close if file
end

Preferred Ruby style:

File.open("data.txt", "w") do |file|
  file.write("hello")
end

Good uses for ensure

  • closing files
  • releasing locks
  • disconnecting resources
  • cleanup after temporary changes

Avoid

  • calling close both in begin and ensure
  • rescuing exceptions silently
  • assuming ensure means "only on error"

Important edge case

If an exception happens before entering the protected block, that ensure will not help. Initialize variables first or include resource creation inside the begin block.

FAQ

Does ensure always run in Ruby?

Yes. ensure runs when leaving the begin block, whether an exception happened or not.

Is ensure the same as finally in C#?

They serve the same main purpose: guaranteed cleanup.

Should I close a file inside both begin and ensure?

No. Usually you should close it only once, typically in ensure, or better, use block-form File.open.

What is the most idiomatic way to open a file in Ruby?

Use block form:

File.open("myFile.txt", "w") do |file|
  file.write("hello")
end

Ruby closes the file automatically.

Does rescue run when there is no exception?

No. rescue runs only when an exception is raised.

Can ensure run if I return early from a method?

Yes. ensure still runs before the method exits.

What if File.open itself fails?

Then the file may never be assigned. Use file = nil first, or use block form when possible.

Do I always need rescue if I use ensure?

No. You can use ensure without rescue if you only need cleanup and want the exception to continue upward.

Take Quiz

Mini Project

Description

Build a small Ruby script that writes log messages to a file while safely handling errors. This project demonstrates when ensure is useful and also shows the cleaner block-based alternative for file handling.

Goal

Create a script that writes text to a file, handles write errors, and guarantees the file is closed properly.

Requirements

  • Open a file for writing.
  • Write at least two lines to the file.
  • Handle any exception that occurs during writing.
  • Guarantee that the file is closed.
  • Print a message showing whether the operation succeeded or failed.
Take Quiz

Keep learning

Related questions

View more