Bug #17105
closedA single `return` can return to two different places in a proc inside a lambda inside a method
Description
A single return
in the source code might return to 2 different lexical places.
That seems wrong to me, as AFAIK all other control flow language constructs always jump to a single place.
def m(call_proc)
r = -> {
# This single return in the source might exit the lambda or the method!
proc = Proc.new { return :return }
if call_proc
proc.call
:after_in_lambda
else
proc
end
}.call # returns here if call_proc
if call_proc
[:after_in_method, r]
else
r.call
:never_reached
end
end
p m(true) # => [:after_in_method, :return]
p m(false) # :return
We're trying to figure out the semantics of return
inside a proc in
https://github.com/oracle/truffleruby/issues/1488#issuecomment-669185675
and this behavior doesn't seem to make much sense.
@headius (Charles Nutter) also seems to agree:
I would consider that behavior to be incorrect; once the proc has escaped from the lambda, its return target is no longer valid. It should not return to a different place.
https://github.com/jruby/jruby/issues/6350#issuecomment-669603740
So:
- is this behavior intentional? or is it a bug?
- what are actually the semantics of
return
inside a proc?
The semantics seem incredibly complicated to a point developers have no idea where return
actually goes.
Also it must get even more complicated if one defines a lambda
method as the block in lambda { return }
is then non-deterministically a proc or lambda.
Updated by Eregon (Benoit Daloze) over 4 years ago
I should also note some of these semantics might significantly harm the performance of Ruby.
CRuby seems to walk the stack on every return
.
On others VMs there need to be some extra logic to find if the frame to return to is still on the stack.
It's already quite complicated but then if return
can go to two places, it becomes a huge mess.
Updated by Hanmac (Hans Mackowiak) over 4 years ago
i think this is by design:
https://www.rubyguides.com/2016/02/ruby-procs-and-lambdas/
A lambda will return normally, like a regular method.
But a proc will try to return from the current context.
Procs return from the current method, while lambdas return from the lambda itself.
Updated by chrisseaton (Chris Seaton) over 4 years ago
Hans I don't think anyone is debating the basic idea of what return in a proc or lambda does - I think we're talking about the edge-case for a proc in a return in the example above, which isn't explained by the text you have.
Updated by Dan0042 (Daniel DeLorme) over 4 years ago
I think the behavior makes sense to some extent, because the proc is within 2 nested contexts. Since the proc is within the lambda context, calling it in the lambda returns from the lambda. And since the proc is also within the method context, calling it in the method returns from the method.
The call_proc
branching logic makes this look more complicated than it really is, but if you separate the logic I feel the behavior is rather reasonable. What do you think should be the behavior of m2
below?
def m1
r = -> {
proc = Proc.new{ return :return }
proc.call #return from lambda
:after_in_lambda
}.call
[:after_in_method, r]
end
def m2
r = -> {
proc = Proc.new { return :return }
}.call
r.call #return from method
:never_reached
end
p m1 #=> [:after_in_method, :return]
p m2 #=> :return
Updated by Eregon (Benoit Daloze) over 4 years ago
IMHO it should be a LocalJumpError. The Proc should return to the lambda, that's syntactically the closest scope it should return to.
Since it's not possible to return to it (the lambda is no longer on stack), it should be a LocalJumpError.
Updated by shyouhei (Shyouhei Urabe) over 4 years ago
+1 to @Eregon (Benoit Daloze) ’s interpretation. Current behaviour is at least very cryptic.
Updated by headius (Charles Nutter) over 4 years ago
Just to be clear I am +1 on single return target, as described here: https://github.com/jruby/jruby/issues/6350#issuecomment-669603740
In addition to the confusing (and possibly inefficient) behavior that results from having two possible return targets, there's also a bug potential here if someone "accidentally" allows a proc containing a return to escape from its lambda container. Rather than returning from the lambda as it should have done, it will now return from the next "returnable" scope, and likely interrupt execution in an unexpected way.
I would challenge anyone to explain why the current behavior should exist, since I can't think of a single valid use case. If there's no use case for a confusing "feature", we should remove it.
Updated by matz (Yukihiro Matsumoto) about 4 years ago
It is intentional since 1.6.0. But I am OK with making m2
raise LocalJumpError
.
Ask @ko1 (Koichi Sasada) about migration.
Matz.
Updated by jeremyevans0 (Jeremy Evans) over 3 years ago
I added a pull request to fix this: https://github.com/ruby/ruby/pull/4223
Updated by ko1 (Koichi Sasada) over 3 years ago
- Status changed from Open to Closed
Applied in changeset git|ecfa8dcdbaf60cbe878389439de9ac94bc82e034.
fix return from orphan Proc in lambda
A "return" statement in a Proc in a lambda like:
lambda{ proc{ return }.call }
should return outer lambda block. However, the inner Proc can become
orphan Proc from the lambda block. This "return" escape outer-scope
like method, but this behavior was decieded as a bug.
[Bug #17105]
This patch raises LocalJumpError by checking the proc is orphan or
not from lambda blocks before escaping by "return".
Most of tests are written by Jeremy Evans
https://github.com/ruby/ruby/pull/4223