In Ruby, how do you attach rescue and ensure to a method body or a do...end block without begin, and when is begin still needed?
answer
- def body is an implicit begin
- class and module bodies too
- do...end blocks since Ruby 2.5
- braces blocks cannot
- Style/RedundantBegin
basics
~20 sA def body, a class or module body and a do...end block act as an implicit begin, so rescue, else and ensure can follow directly. Use begin to cover only part of the body, or inside a brace block, which cannot take rescue.
solid answer
~40 sIn Ruby 4.0, a method body is already a protected region: `def`, then the code, then `rescue`, `else` and `ensure` clauses, then the method's own `end`. The same holds for `class` and `module` bodies and, since Ruby 2.5, for `do...end` blocks, so a block passed to `each` or `map` can rescue per iteration without an inner `begin`. Brace blocks `{ ... }` cannot take these clauses; `rescue` there is a syntax error. You still need `begin` when only part of a method should be covered, when the handler's value is assigned to a variable, or in a brace block. RuboCop's `Style/RedundantBegin` removes a `begin` that wraps a whole method body, and `retry` in a method-level rescue restarts the whole method body.
code
ruby · 14 linesrequire "time"
def import_dates(lines)
lines.each_with_index.filter_map do |line, row|
Time.iso8601(line.strip)
rescue ArgumentError
warn "row #{row}: bad date"
nil
end
ensure
puts "import finished"
end
import_dates(["2026-09-30T10:00:00Z", "not a date"])go deeper
Know that def bodies and do...end blocks can take rescue, else and ensure directly, and brace blocks cannot.
Explain what the method returns from each clause, how block-level rescue works per iteration, and when a narrower begin is better.
Choose the handler scope deliberately: whole method for cleanup, a tight begin for retries, per-element rescue in batch loops.
Set conventions for handler scope so reviewers can see at a glance which lines a rescue protects and what reruns on retry.
## Bodies that act as begin Ruby lets several constructs double as an exception handler, so you can write `rescue`, `else` and `ensure` without an explicit `begin`: | Construct | Clauses allowed directly | Since | |---|---|---| | `def ... end` method body | `rescue`, `else`, `ensure` | long-standing | | `class` / `module` body | `rescue`, `else`, `ensure` | long-standing | | `do ... end` block | `rescue`, `else`, `ensure` | Ruby 2.5 | | `{ ... }` block | none; use `begin` inside | not allowed in Ruby 4.0 | The syntax documentation says that inside a method you do not need `begin` or `end` unless you wish to limit the scope of rescued exceptions, and that the same is true for a class, module and block. ## Method-level rescue The method form is the most common: - The protected region is the **whole method body**. - The method's return value on failure is the **rescue body's last value**; on success it is the `else` value if present, otherwise the body's last value. - **`retry`** in a method-level rescue restarts the whole method body, so any setup at the top runs again. - `ensure` runs whenever the method is left, which makes it the natural place for cleanup that belongs to the whole method. RuboCop's **`Style/RedundantBegin`** flags a `begin ... end` that wraps an entire method body, since the method already provides the handler. ## Block-level rescue A `do ... end` block can carry the clauses too, which is handy for handling failures **per iteration**: 1. Each call to the block is its own protected region. 2. A rescue that returns a value makes that the block's result for that element, so `map` collects it. 3. `retry` in a block-level rescue re-runs that block body for the same element. The syntax documentation's own example maps over `[0, 1, 2]`, dividing 10 by each value and rescuing `ZeroDivisionError` with `nil`, which returns `[nil, 10, 5]`. Brace blocks do not accept the clauses. `items.map { |i| 10 / i rescue nil }` is legal only because that `rescue` is the one-line modifier, which rescues any `StandardError`; to name a class inside braces, switch to `do ... end` or wrap the body in `begin ... end`. ## When begin is still the right tool - **Part of a method**: wrap only the risky lines, so an error in unrelated code is not mistaken for the one you handle. - **Assigning the handler's value**: `rate = begin ... rescue ... end`. - **Retrying a subset**: `retry` restarts the enclosing `begin`, so a tight `begin` limits what reruns. - **Inside a brace block** when a named class is needed. ## Example In a newsletter importer, a method-level `ensure` closes the CSV file once, while a `do...end` block around each row rescues `ArgumentError` from a malformed date and records the row number, so one bad row does not abort the import. ## Where the clauses are not allowed Not every body is an implicit `begin`. Besides brace blocks, the bodies of `while` and `until` loops, `if` branches and `case` branches cannot take `rescue` clauses directly; writing one there is a syntax error. Wrap the statements in `begin ... end`, or move them into a method whose body can carry the clauses. Moving the work into a small, named method is often the cleaner fix, because the method name documents what is being protected. ## Choosing between the forms A short decision list: - Cleanup for everything the method does: method-level `ensure`. - A failure that should turn into a return value for the whole method: method-level `rescue`. - A failure per element of a collection: `do...end` block with its own `rescue`. - A failure in two lines out of twenty: a `begin` around those two lines. - A retry of one network call: a tight `begin` with `retry`, so setup code above it does not rerun. The same clause rules apply to every form: the first matching rescue wins, `else` runs only on success and `ensure` always runs last.
- What does retry do inside a method-level rescue?It restarts the method body from its first statement, not from the failing line, so everything before the failure runs again. If only one call should be repeated, wrap that call in its own `begin ... rescue ... retry ... end` so the rest of the method is not re-executed.
- Why does items.map { |i| 10 / i rescue nil } work if brace blocks cannot take rescue?That `rescue` is the one-line modifier applied to `10 / i`, not a rescue clause of the block. It rescues any `StandardError` and cannot name a class. To rescue only `ZeroDivisionError`, use a `do...end` block with a rescue clause or a `begin` inside the braces.
saying these in an interview costs you the question
- Every method-level rescue needs an explicit begin inside the def
- Brace blocks accept rescue clauses exactly like do...end blocks
- retry in a method-level rescue resumes at the failing line
- A rescue in a do...end block ends the whole iteration over the collection
- A method-level ensure runs once per rescued exception