Issue #15567 has been updated by ioquatix (Samuel Williams).


@eregon - thanks for your response.

> Ensure should always be executed no matter the circumstances IMHO, so I don't like to make it conditional with extra syntax.

This isn't about not executing ensure, but allowing user to handle situations like "returned normally" vs "non-standard flow control".

> I think the workaround using a variable set after yield (success = true) is not too bad, and clear enough. And the performance would be fine in this case (i.e. there would be no overhead if the JIT profiles the branch and only one branch is taken, like TruffleRuby).

Personally, I think it's ugly, and I also think it is inefficient. I also don't think it's clearly conveying what the user is trying to do.

The problem is, people write code like this:

```
begin
  ..
ensure
  abnormal_path if $!
end
```

This actually wrong and fails in the case of `throw` wrapped in `catch` as given in the original examples.

It's actually not clear from the code if this is what the user wanted or not. Because you can't express clearly what you are actually interested in.

There should be no overhead when exiting normally in the following situation:

```
begin
  yield
ensure when not return
  return :abnormal
end
```

As soon as you write code like:

```
begin
  yield
  success = true
ensure
  return :abnormal unless success
end
```

you guarantee that the code must pass through the ensure block. But your intention is, only execute this code if the code didn't return normally (or some other flow control).

So, I don't think the argument about always executing ensure holds up - this isn't about changing the semantics of `ensure` but extending it to handle explicitly what people are already doing, albeit probably incorrectly and inefficiently.

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76524

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 2.7
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>