The standard term for the specific thing you are looking for is non-local return. This is a language feature where some sort of code block can be given to another function or method, while retaining the ability to return from its lexically-enclosing method when invoked.
There are several languages that have this as a core feature. Ruby is a common one today:
def f(&x)
x.call
end
def g
f { return 1 }
puts "This doesn't run"
return 0
end
g // => 1; no output
This comes out of the Smalltalk tradition, and virtually all Smalltalk-likes have this feature as well, including standard Smalltalk, Self, and Pharo. There are a number of others as well; for example, R has this as part of its general lazy evaluation.
I worked on an academic educational programming language called Grace that leans on this very heavily, precisely to allow building control structures of the kind you're looking for:
method foo(target) {
for (1..5) do { i ->
if (i == target) then {
return "found"
}
}
"not found"
}
...
method if(cond : Boolean) then (blk : Action) { // in the standard library somewhere
cond.ifTrue(blk)
}
Here both the for-do and if-then control structures are ordinary in-language methods, and the braced blocks are ordinary object arguments to them. The return statement in the middle returns from the lexically-enclosing method, regardless of how far down the stack the block is applied. The idea was that this would allow user-defined control structures on a par with the standard ones. There is a lot of subsequent academic literature exploring uses of these features in Grace, and of course all the work on Smalltalk is using a language that has them, although they don't come up much except as an implementation challenge. When looking at Smalltalk, note that "return" is spelled ↑ or ^.
Implementing this in the language is a challenge, and I have done it a few times with different approaches in different Grace implementations. The techniques from Jörg's answer are all implementation strategies that could back this as a language feature. setjmp/longjmp are viable approaches in C; throwing an exception to indicate the return, and catching it in the relevant method works in C#, Java, JavaScript, ...; in a continuation-passing interpreter this amounts to just saving and using the original return continuation. They were all a little fiddly, but workable, although there are both code complexity and performance tradeoffs involved.
If you're building this out within a language with one of those base features, you can simulate it yourself without explicit language support. I would be cautious of doing that without a very good reason, but for example implementing a small internal DSL with the semantics required for a common task might be reasonable.
This ability also raises some other questions: in particular, what if I save that block and invoke it after the original method has already returned? Most systems make this a dynamic error, tracking whether the stack frame has already been popped. Some would proceed with the program from the return site again, in effect looping - in continuation-passing style, this is essentially the default - which allows some interesting constructions while also probably causing confusion. Interaction with other language constructs can also be complex: what does a non-local return during a finally block do?
This can be a good feature when the language is built around it, and I think the overall approach used in Grace was reasonable. There is some degree of "action at a distance" that arises out of being able to jump back up the stack like this, and it's much less clear that it's worthwhile in a language that isn't constructed around the feature: when it's a corner case users will forget to account for it, and the costs of the implementation may not be worthwhile for something that is rarely important.
allin python, which will return as soon as the callback returns a Truthy value.longjmpcould jump out of a nested call and thenlongjmpback in? That would be undefined behaviour; you can onlylongjmpto a jump buffer saved in a parent function of the one that callslongjmp. Or at least it's UB if the function that calledsetjmphas returned before youlongjmp. I assume longjmp out of it would also count. (man7.org/linux/man-pages/man3/setjmp.3.html although the POSIX man page doesn't seem to mention that restriction, at least not as explicitly: man7.org/linux/man-pages/man3/longjmp.3p.html)