Question
Ruby begin, rescue, and ensure Explained: Is ensure Like finally?
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.
beginwraps code that might fail.rescuehandles exceptions.ensureruns 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 jobrescue: if something goes wrong, you handle the problemensure: 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.
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
File.open("myFile.txt", "w")opens the file for writing.- The
beginblock starts. file << "Hello\n"writes text to the file.- No exception happens, so
rescueis skipped. - The
ensureblock runs. puts "Closing file"prints a message.file.close unless file.nil?closes the file.
If writing fails
Imagine line 3 raises an exception.
- Ruby jumps out of the remaining code in
begin. - The
rescueblock runs and stores the exception ine. - The error message is printed.
- The
ensureblock still runs. - 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.
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.openraises an exception, execution never reaches thebegin ensurewill 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
| Concept | Purpose | Runs on success? | Runs on error? |
|---|---|---|---|
rescue | Handle an exception | No | Yes |
ensure | Always perform cleanup | Yes | Yes |
ensure in Ruby vs finally in C#
| Ruby | C# | Purpose |
|---|---|---|
ensure | finally | Run code whether or not an exception occurs |
Manual file closing vs block form
| Approach | Example | Recommended? | Why |
|---|---|---|---|
Manual close with ensure | file = File.open(...) | Sometimes | Needed when you must manage the object manually |
| Block form | `File.open(...) do | file | ... end` |
Two versions from the question
| Version | Good choice? | Reason |
|---|---|---|
Close only in ensure | Yes | Cleanup is centralized in one place |
Close in begin and ensure | No | Redundant and unnecessary |
Cheat Sheet
Quick reference
Basic structure
begin
risky_code
rescue => e
handle_error(e)
ensure
cleanup_code
end
Rules
begincontains code that may fail.rescueruns only if an exception is raised.ensureruns whether an exception happens or not.ensureis 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
closeboth inbeginandensure - rescuing exceptions silently
- assuming
ensuremeans "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.
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.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.