< :前の番号
^ :番号順リスト
> :次の番号
P :前の記事(スレッド移動)
N :次の記事(スレッド移動)
|<:前のスレッド
>|:次のスレッド
^ :返事先
_:自分への返事
>:同じ返事先を持つ記事(前)
<:同じ返事先を持つ記事(後)
---:分割してスレッド表示、再表示
| :分割して(縦)スレッド表示、再表示
~ :スレッドのフレーム消去
.:インデックス
..:インデックスのインデックス
Issue #10481 has been updated by Alex Boyd.
Robert Klemme wrote:
> Do you have an idea of the runtime performance impact? I mean, a type check (as done today) is fast and effort cannot change. But with your proposal arbitrary code can be executed as part of the test. If you do that on multiple levels of the call stack the effect on performance may be noticeable.
For code that doesn't make use of this feature, the performance impact is 0 - no additional bytecodes are added to the compiled result. For code that does, the impact is 4 bytecodes *less* than, but otherwise identical to, adding an equivalent `raise unless e.error_code == 1` check to the top of the rescue body (and 6 bytecodes less than a full if/then/else raise end/ check):
~~~
irb(main):072:0> puts RubyVM::InstructionSequence.disassemble proc { begin; do_something; rescue => e if foo; handle_failure; end }
== disasm: <RubyVM::InstructionSequence:block in irb_binding@(irb)>=====
== catch table
| catch type: rescue st: 0004 ed: 0009 sp: 0000 cont: 0010
== disasm: <RubyVM::InstructionSequence:rescue in block in irb_binding@(irb)>
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s0)
[ 2] "\#$!"
0000 getlocal_OP__WC__0 2 ( 72)
0002 putobject StandardError
0004 checkmatch 3
0006 branchunless 23
0008 getlocal_OP__WC__0 2
0010 setlocal_OP__WC__1 2
0012 putself
0013 opt_send_simple <callinfo!mid:foo, argc:0, FCALL|VCALL|ARGS_SKIP>
0015 branchunless 23
0017 trace 1
0019 putself
0020 opt_send_simple <callinfo!mid:handle_failure, argc:0, FCALL|VCALL|ARGS_SKIP>
0022 leave
0023 getlocal_OP__WC__0 2
0025 throw 0
| catch type: retry st: 0009 ed: 0010 sp: 0000 cont: 0004
| catch type: redo st: 0002 ed: 0010 sp: 0000 cont: 0002
| catch type: next st: 0002 ed: 0010 sp: 0000 cont: 0010
|------------------------------------------------------------------------
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] e
0000 trace 256 ( 72)
0002 trace 1
0004 trace 1
0006 putself
0007 opt_send_simple <callinfo!mid:do_something, argc:0, FCALL|VCALL|ARGS_SKIP>
0009 nop
0010 trace 512
0012 leave
=> nil
~~~
~~~
irb(main):069:0> puts RubyVM::InstructionSequence.disassemble proc { begin; do_something; rescue => e; raise unless foo; handle_failure; end }
== disasm: <RubyVM::InstructionSequence:block in irb_binding@(irb)>=====
== catch table
| catch type: rescue st: 0004 ed: 0009 sp: 0000 cont: 0010
== disasm: <RubyVM::InstructionSequence:rescue in block in irb_binding@(irb)>
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s0)
[ 2] "\#$!"
0000 getlocal_OP__WC__0 2 ( 69)
0002 putobject StandardError
0004 checkmatch 3
0006 branchunless 29
0008 getlocal_OP__WC__0 2
0010 setlocal_OP__WC__1 2
0012 trace 1
0014 putself
0015 opt_send_simple <callinfo!mid:foo, argc:0, FCALL|VCALL|ARGS_SKIP>
0017 branchif 23
0019 putself
0020 opt_send_simple <callinfo!mid:raise, argc:0, FCALL|VCALL|ARGS_SKIP>
0022 pop
0023 trace 1
0025 putself
0026 opt_send_simple <callinfo!mid:handle_failure, argc:0, FCALL|VCALL|ARGS_SKIP>
0028 leave
0029 getlocal_OP__WC__0 2
0031 throw 0
| catch type: retry st: 0009 ed: 0010 sp: 0000 cont: 0004
| catch type: redo st: 0002 ed: 0010 sp: 0000 cont: 0002
| catch type: next st: 0002 ed: 0010 sp: 0000 cont: 0010
|------------------------------------------------------------------------
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] e
0000 trace 256 ( 69)
0002 trace 1
0004 trace 1
0006 putself
0007 opt_send_simple <callinfo!mid:do_something, argc:0, FCALL|VCALL|ARGS_SKIP>
0009 nop
0010 trace 512
0012 leave
=> nil
~~~
> Also it must be defined how you treat exceptions that are raised inside the test expression. It may be hard to hunt down errors because these exceptions would likely shadow the original exception and it may be hard to find out what content in the original exception triggered the exception in the testing code.
They're treated otherwise as if they had occurred within the rescue body, but with the line number set to the rescue clause itself:
~~~
irb(main):074:0> begin
irb(main):075:1* raise 'something'
irb(main):076:1> rescue if raise 'something else'
irb(main):077:1> puts "shouldn't get here"
irb(main):078:1> end
RuntimeError: something else
from (irb):76:in `rescue in irb_binding'
from (irb):74
from bin/irb:11:in `<main>'
~~~
I think this seems fairly clear. It would perhaps be nice if it said ``in `rescue condition in irb_binding` `` instead of ``in `rescue in irb_binding` ``, but that's beyond the scope of my knowledge (and I think the line number makes it fairly hard to miss what's going on). Thoughts?
> Also it must be made sure that code in "ensure" block is executed under all conditions. That may become more difficult with your change.
The additional bytecodes to check the condition and branch to `label_miss` are tacked on just before the rescue body itself, so they receive all the same protections. My changes are confined entirely to the `case NODE_RESBODY:` block (all of whose generated bytecodes are protected by the ensure clause), so I imagine it would be fairly hard to break this interaction without deliberately intending to do so - but feel free to contradict me if I'm wrong. At any rate, this does work correctly as-is:
~~~
irb(main):086:0> begin
irb(main):087:1* raise 'something'
irb(main):088:1> rescue if raise 'something else'
irb(main):089:1> puts "shouldn't get here"
irb(main):090:1> ensure
irb(main):091:1* puts "but we should get here"
irb(main):092:1> end
but we should get here
RuntimeError: something else
from (irb):88:in `rescue in irb_binding'
from (irb):92
from bin/irb:11:in `<main>'
~~~
----------------------------------------
Feature #10481: Add "if" and "unless" clauses to rescue statements
https://bugs.ruby-lang.org/issues/10481#change-50028
* Author: Alex Boyd
* Status: Open
* Priority: Normal
* Assignee: Yukihiro Matsumoto
* Category:
* Target version:
----------------------------------------
I'd like to propose a syntax change: allow boolean "if" and "unless" clauses to follow a rescue statement.
Consider the following:
~~~
begin
...
rescue SomeError => e
if e.error_code == 1
...handle error...
else
raise
end
end
~~~
This is a fairly common way of dealing with exceptions where some condition above and beyond the exception's type determines whether the exception should be rescued. It's verbose, though, and it's not obvious at first glance exactly what conditions are being rescued, especially if "...handle error..." is more than a few lines long. I propose that the following be allowed:
~~~
begin
...
rescue SomeError => e if e.error_code == 1
...handle error...
end
~~~
"unless" would, of course, be allowed as well:
~~~
begin
...
rescue SomeError => e unless e.error_code == 2
...handle error...
end
~~~
A rescue statement whose boolean condition failed would be treated the same as if the exception being raised didn't match the exception being rescued, and move on to the next rescue statement:
~~~
begin
...
rescue SomeError => e if e.error_code == 1
...handle error code 1...
rescue SomeError => e if e.error_code == 2
...handle error code 2...
end
~~~
And finally, catch-all rescue statements would be allowed as well:
~~~
begin
...
rescue => e if e.message == "some error"
...handle error...
end
~~~
---Files--------------------------------
rescue-conditions.diff (6.76 KB)
rescue-conditions.diff (6.57 KB)
--
https://bugs.ruby-lang.org/