Project

General

Profile

Actions

Feature #12906

closed

do/end blocks work with ensure/rescue/else

Added by josh.cheek (Josh Cheek) over 7 years ago. Updated over 5 years ago.

Status:
Closed
Target version:
[ruby-core:78021]

Description

When you want to rescue in a block, you must do this:

lambda do
  begin
    raise 'err'
  rescue
    $! # => #<RuntimeError: err>
  end
end.call

I've wished on numerous occasions that I could omit the begin/end and not need the extra wrapper:

lambda do
  raise 'err'
rescue
  $! # => #<RuntimeError: err>
end.call

This would be consistent with how classes and methods work:

class C
  raise 'err'
rescue
  $! # => #<RuntimeError: err>
end

send def m
  raise 'err'
rescue
  $! # => #<RuntimeError: err>
end

It's not really clear to me how to submit this since it may require some discussion, but this is the diff:

diff --git a/parse.y b/parse.y
index 54ccc52..223e5d3 100644
--- a/parse.y
+++ b/parse.y
@@ -3757,7 +3757,7 @@ brace_body	: {$<vars>$ = dyna_push();}
 
 do_body 	: {$<vars>$ = dyna_push();}
 		  {$<val>$ = cmdarg_stack >> 1; CMDARG_SET(0);}
-		  opt_block_param compstmt
+		  opt_block_param bodystmt
 		    {
 			$$ = new_do_body($3, $4);
 			dyna_pop($<vars>1);

I added tests for ensure to rubyspec, but there wasn't an obvious place to talk about rescue/else in this context (the spec for rescue only uses it in a begin/end block) It's probably fine as the spec for ensure does hit rescue, too, and they ultimately delegate to the same pieces. Not totally clear, though. I can do more with that if you need.

diff --git a/language/ensure_spec.rb b/language/ensure_spec.rb
index 13575fc..b14b0b5 100644
--- a/language/ensure_spec.rb
+++ b/language/ensure_spec.rb
@@ -124,3 +124,74 @@ describe "An ensure block inside a method" do
     @obj.explicit_return_in_method_with_ensure.should == :ensure
   end
 end
+
+describe "An ensure block inside a do block" do
+  before :each do
+    ScratchPad.record []
+  end
+
+  it "is executed when an exception is raised in it's corresponding do block" do
+    begin
+      lambda do
+        ScratchPad << :begin
+        raise "An exception occured!"
+      ensure
+        ScratchPad << :ensure
+      end.should raise_error(RuntimeError)
+
+      ScratchPad.recorded.should == [:begin, :ensure]
+    end
+  end
+
+  it "is executed when an exception is raised and rescued in it's corresponding do block" do
+    begin
+      lambda do
+        ScratchPad << :begin
+        raise "An exception occured!"
+      rescue
+        ScratchPad << :rescue
+      ensure
+        ScratchPad << :ensure
+      end.call
+
+      ScratchPad.recorded.should == [:begin, :rescue, :ensure]
+    end
+  end
+
+  it "is executed even when a symbol is thrown in it's corresponding do block" do
+    begin
+      catch(:symbol) do
+        lambda do
+          ScratchPad << :begin
+          throw(:symbol)
+        rescue
+          ScratchPad << :rescue
+        ensure
+          ScratchPad << :ensure
+        end.call
+      end
+
+      ScratchPad.recorded.should == [:begin, :ensure]
+    end
+  end
+
+  it "is executed when nothing is raised or thrown in it's corresponding do block" do
+    lambda do
+      ScratchPad << :begin
+    rescue
+      ScratchPad << :rescue
+    ensure
+      ScratchPad << :ensure
+    end.call
+
+    ScratchPad.recorded.should == [:begin, :ensure]
+  end
+
+  it "has no return value" do
+    lambda do
+      :begin
+    ensure
+      :ensure
+    end.call.should == :begin
+  end
+end

Related issues 4 (0 open4 closed)

Is duplicate of Ruby master - Feature #7882: Allow rescue/else/ensure in do..endClosedmatz (Yukihiro Matsumoto)Actions
Is duplicate of Ruby master - Feature #11337: Allow rescue without begin inside blocksClosedmatz (Yukihiro Matsumoto)Actions
Is duplicate of Ruby master - Feature #12623: rescue in blocks without begin/endClosedActions
Is duplicate of Ruby master - Feature #13212: Syntax proposal: don't require begin-end to rescue exceptions inside do-end blocksClosedActions
Actions #1

Updated by shyouhei (Shyouhei Urabe) over 7 years ago

  • Is duplicate of Feature #7882: Allow rescue/else/ensure in do..end added
Actions #2

Updated by shyouhei (Shyouhei Urabe) over 7 years ago

  • Is duplicate of Feature #11337: Allow rescue without begin inside blocks added
Actions #3

Updated by shyouhei (Shyouhei Urabe) over 7 years ago

  • Is duplicate of Feature #12623: rescue in blocks without begin/end added

Updated by shyouhei (Shyouhei Urabe) over 7 years ago

  • Status changed from Open to Assigned
  • Assignee set to matz (Yukihiro Matsumoto)

Josh Cheek wrote:

but this is the diff:

diff --git a/parse.y b/parse.y
index 54ccc52..223e5d3 100644
--- a/parse.y
+++ b/parse.y
@@ -3757,7 +3757,7 @@ brace_body	: {$<vars>$ = dyna_push();}
 
 do_body 	: {$<vars>$ = dyna_push();}
 		  {$<val>$ = cmdarg_stack >> 1; CMDARG_SET(0);}
-		  opt_block_param compstmt
+		  opt_block_param bodystmt
 		    {
 			$$ = new_do_body($3, $4);
 			dyna_pop($<vars>1);

So from the patch you sent, I guess you are implicitly proposing to forget about {...}-style blocks for a while and focus on do ... end-style blocks. That is in fact a wise idea. To start small is a wisdom we learned in this forum.

Updated by josh.cheek (Josh Cheek) over 7 years ago

My error on the duplication, I tried searching a couple different ways but the first results weren't about this and I didn't see how to use the filters and the keywords together. Fortunately it seems congruent with https://bugs.ruby-lang.org/issues/7882

Shyouhei Urabe wrote:

So from the patch you sent, I guess you are implicitly proposing to forget about {...}-style blocks for a while and focus on do ... end-style blocks. That is in fact a wise idea. To start small is a wisdom we learned in this forum.

Ty :) the existing places it works are begin/end, def/end, class/end, so it felt unifying to add do/end, but seemed less natural with curly braces. I'm sure I'd get used to it, if it were there, though.

Updated by shevegen (Robert A. Heiler) over 7 years ago

I like the idea. It would get rid of one begin/end clause if I understood
it correctly and be on the same level as class C/rescue/end definitions.

Updated by josh.cheek (Josh Cheek) over 7 years ago

Checking that I submitted this correctly, I usually do Github, so might have gotten this wrong.

Updated by matz (Yukihiro Matsumoto) about 7 years ago

  • Assignee changed from matz (Yukihiro Matsumoto) to nobu (Nobuyoshi Nakada)
  • Target version set to 2.5

Although I am not a big fan of this syntax, mostly because I don't like fine grain exception handling.
But I found out many developers prefer the syntax. After some consideration, I decided to accept this.

Matz.

Actions #9

Updated by nobu (Nobuyoshi Nakada) about 7 years ago

  • Status changed from Assigned to Closed

Applied in changeset r57376.


parse.y: rescue/else/ensure in do-end

  • parse.y (do_body): allow rescue/else/ensure inside do/end
    blocks. [Feature #12906]
Actions #10

Updated by hsbt (Hiroshi SHIBATA) about 7 years ago

  • Is duplicate of Feature #13212: Syntax proposal: don't require begin-end to rescue exceptions inside do-end blocks added

Updated by perlun (Per Lundberg) almost 6 years ago

Old issue, but still perhaps the right place to mention this: the new syntax ONLY works in do/end, not in {} blocks (as mentioned above.)

It also does not work in "block-like" places like a for loop. So this is not valid syntax:

for i in 1..100
  puts 'hello'
rescue # Syntax error; not valid code in existing Ruby versions like 2.5.0 and 2.5.1.
end

Suggested workaround: Use a (1..100).each do |i| construct instead. Then you can use a rescue or ensure within the block in Ruby 2.5 and newer.

(This is actually an arguably valid use case for "fine grained error handling", in cases where the loop can fail in n of the 100 cases and you want the failing ones to be ignored. I agree that too-finegrained error handling can otherwise become an antipattern.)

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0