Misc #19054
closed`else` in exception-handling context vs early return
Description
else
in exception-handling context is rarely used (at least in codebase I saw), so we encountered it just recently:
def foo
puts "body"
return
rescue => e
p e
else
puts "else"
ensure
puts "ensure"
end
foo # prints "body", then "ensure"
[1].each do
puts "body"
next
rescue => e
p e
else
puts "else"
ensure
puts "ensure"
end
# also prints "body" then "ensure"
E.g. else
is ignored in both cases. Intuitively, I would expect that if no exception is raised in block, else
is performed always—like ensure
is performed always, exception or not, early return or not.
I found only a very old discussion of this behavior in #4473 (it was broken accidentally on the road to 1.9.2, but then fixed back), but it doesn't explain the reason for it.
Can somebody provide an insight on this decision, and whether it is justified at all?.. At least, it should be documented somewhere, exception handling docs doesn't mention this quirk.
Updated by jeremyevans0 (Jeremy Evans) over 2 years ago
I think the existing behavior is expected. else
with rescue
operates similar to else
with if
. In pseudocode:
begin
if e = exception_raised_by{puts "body"; return}
p e
else
puts "else"
end
ensure
puts "ensure"
end
I agree with you that the documentation could be improved, though it kind of hints at the current behavior:
- (Regarding
else
): "You may also run some code when an exception is not raised" - "To always run some code whether an exception was raised or not, use ensure:"
It would be useful to document that else is not called on early exits or exceptions, and how to use ensure
to run code on all non-exception scenarios (by using rescue => local_var
and if local_var
).
Updated by zverok (Victor Shepelev) over 2 years ago
Well, my pseudo-code-expressed intuition can be rather expressed like this:
begin
# whatever happens here, is covered by rescue/else/ensure block
e = exception_raised_by{puts "body"; return}
ensure
if e
# we go here if there was an exception
p e
else
# we go here if there was none
puts "else"
end
# we go here in any case
puts "ensure"
end
It is implied by else
and rescue
being on the same level as ensure
, making me think there are 3 blocks of equal priority
- one that performs always (
ensure
) - one that performs if there was exception ([one of the]
rescue
) - one that performs if there was no exception (
else
)
If we'll imagine more realistic code, there can be, like, 30 lines of methods body, and overall structure on reading looking like this:
def my_method
# a LOT can go here,
# ...but while reading
# ...to the very end
# ...you can always rely
# ...on the fact that
# even if THIS last line is not performed due to some reason,
rescue
# this WILL perform if any exception happens, however deep it was
else
# this WILL perform if no exception happened, no matter what
ensure
# this WILL perform no matter what, period
end
Again, for me it somewhat theoretical, but I can imagine good uses for else
like, at the very least, log.debug 'success'
. With current behavior, it seems completely redundant feature, because imagine this:
def my_method
return :early
puts "(1) printed at the end in normal, no early return-flow"
:regular
rescue
# ...
else
puts "I would hope this will print for both :early, and :regular, but it behaves JUST like (1)"
end
...e.g. NOTHING is achieved by else
that can't be achieved by putting exactly the same code at the end of the body.
Updated by jeremyevans0 (Jeremy Evans) over 2 years ago
zverok (Victor Shepelev) wrote in #note-2:
...e.g. NOTHING is achieved by
else
that can't be achieved by putting exactly the same code at the end of the body.
Incorrect. The primary reason for else
with rescue
is that code in else
that raises exceptions does not call into the rescue
blocks (though it is still handled by ensure
). Code that the end of the body obviously would.
In any case, I would guess there is no chance of changing the behavior at this point. It would break way too much code.
Updated by jeremyevans0 (Jeremy Evans) over 1 year ago
- Status changed from Open to Closed