Can Rust support non-local return?

For example:

fn foo() -> ... {
    a(|| b(|| c(|| return 'label expr)))
}

The label can be specified to return to a, b and foo functions.

There's no special syntax for it. And it's fundamentally hard to support with Drop, not to mention that it can easily make existing code unsound by skipping cleanup logic.

If you want to write code that supports this, you might be interested in ControlFlow in std::ops - Rust.

1 Like

On the language design side, it's useful to look at Swift here, since they make an important property visible here.

Rust's function (closure) arguments are by default 'stativ and what modern Swift calls @escaping (and in early Swift was the default). Such closures can be stored and executed later, after the function you're calling has returned. Obviously, non-local control flow out into the calling function cannot work in this case.

Alternatively, function arguments can be what early Swift called @nonescaping, and modern Swift has by default. These closures must be called as part of the function body they are passed to. (The equivalent in Rust would be an unbound input lifetime, which makes storing the closure to be called later a borrow error.) With this restriction, it becomes possible to do non-local (or "TCP preserving") control flow with specific compiler support.

7 Likes

It wouldn't be terrible to represent this as a lifetime bound to the lexical control flow it affects, as if it borrowed a local in that scope. But I think we would also need some explicit function trait for this, so existing code that takes Fn / FnMut / FnOnce doesn't have to worry about non-local control flow.

Here's an older thread on TCP closures:

2 Likes

Very simple request,just:

fn foo() -> ... {
    match catch_unwind(|| a(|| b(|| c(|| panic!(expr))))) {
        ...
    }
}

Panicking should only be used for abnormal conditions, and it may be configured to abort rather than unwind.

13 Likes

Previous discussion: Idea: non-local control flow

1 Like