[PRE-RFC] generalized return/escape/continue from scopes


#1

Okay, so this is obviously pretty wild and one may argue that it reduces clarity but I propose deprecating continue, return and break and replacing them with the far more general escape combined with labeled scopes.

Like labelled loops introducing labeled scopes as a generalization:

'label:{ /* ... */ }

fn factorial ( n : u32 ) -> u32 'label:{
	/* ... */
}

if 1 > 2 'label:{ /* ... */ }

As you might imagine escape 'label:$expr escapes to the nearest enclosing label that matches it like in loops and also returns the value which becomes the return value of the scope.

This can obviously be used to implement return but also continue and break:

for i in [0,1,2] 'continue:{
	/* ... */
	escape 'continue:();
	/* ... */
};

'break:{ for i in [0,1,2] {
	/* ... */
	escape 'break:();
	/* ... */
}};

Also implementing returning values from loops.

Indeed Rust could just treat an unlabeled function outer scope as if it assigned the label 'return, similarly assigning 'continue and 'break labels by default for loops and treating return $expr as syntactic sugar for escape 'return:$expr andsoforth.

Since introducing a new keyword is obviously painful and since the return $expr syntax does not conflict with return 'label:$expr the return keyword can instead be used for escape. This would however require that return $expr does not return out of its nearest enclosing scope but defaults back to return 'return:#expr with the master scope of any function automatically named so.


#2

You do know that you can use labels for break and continue already, right?

As for return, there were sort of similar ideas in this thread:


#3

Yes, my post references that and borrows the label syntax. These labels however do not return values and you can’t return from any ordinary scope.[quote=“cuviper, post:2, topic:5173”] As for return, there were sort of similar ideas in this thread: pre-RFC: Allow return, continue, and break to affect the captured environment language design [/quote]

Not at all,in fact my idea excludes that. That’s a dynamic return that goes up and down a stack. Mine is completely lexical. Under my proposal a closure that has such a labelled return would be statically disqualified from ever leaving labelled scope it returns from.

You can in fact return from any scope with this such as:

let x = 'label:{
	// complex code
	if condition {
		escape 'label:value; // premature exit from scope
	}
	// complex code
};

#4

Calling a closure is still a function call on the stack, at least before inlining is applied.

So you wouldn’t allow passing such closures as parameters at all? e.g. to Iterator::map, or in the captured environment of a neighboring closure, etc.


#5

Indeed, that would no longer make it lexically clear where it returns to.

You can in fact argue that for clarity’s sake inside of a closure it shouldn’t be able to escape to outside of the closure at all which keeps the reasoning simple. There are some valid cases where it is not objectionable inside of a closure but those can easily just be implemented with a macro instead of a closure.


#6

So that drops return semantics from this proposal entirely, right? Then what you’re left with is basically a generalized break from any block type, and break with values is RFC 1624.


#7

No? I don’t see how? this would be perfectly legal:

'outer:{
	let y = 5;
	let add_y = |x| 'inner:{ escape 'inner: (x + y) };
	escape  'outer: add_y(3);
	// dead code
}

this however would not be:

'outer:{
	let y = 5;
	let add_y = |x| 'inner:{ escape 'outer: (x + y) }; //closure wants to escape directly to a scope that lies outside of the closure
	escape  'outer: add_y(3);
	// dead code
}

Note that a normal return does not escape to outside the function but to the last scope that still lies inside of it.


#8

Sure, but that’s not really a return, more like a break-with-value for a block within the closure. And a true return is still possible in the closure when that’s what you need.

So I think you get everything you want by paring this down to allowing 'label on any block and break to such a label – possibly with values following RFC 1624. Is there an example that doesn’t fit that?

I don’t understand this note. A normal return “escapes” the current calling scope, whether that’s a function or closure.


#9

That’s pretty much how I defined it.

The current calling scope is inside of the function, not outside of it is what I mean. The highest continuation you can escape to is the continuation of the function call itself.


#10

Maybe we’ve just been refining my understanding then. :slight_smile:


#11

Well let’s put it like this, you can already hack it in with super inconvenient syntax. Imagine replacing a scope with:

{ let mut escape_value; 'label: for _ in once(()) { ... }; escape_value }

And a escape 'label: value; with:

escape_value = value; break 'label;

#12

So are we reinventing goto ?


#13

I don’t see how. If you see 'label:{ ... } you know you can only “goto” to that label from any part lexically inside of the scope. This is in no way more powerful than “return” or “break” or “continue” and as I said you can in theory in super inconvenient syntax implement this in terms of a macro on top of loops with labeled breaks that run only once and assign a value.

Some people have arguments against return, break and continue arguing that they suffer from a lighter version of the goto problem and that they should not be allowed at all. I can see those arguments and a lot of languages don’t have them as a consequence but if you do have them I don’t see how this is in any way more objectionable. Far from it, it makes code a lot more readable and it also makes working with Results and carriers cleaner. You often see people define an inner function or closure just as a hack to control the point to which ? aborts. That’s no longer needed with this.


#14

I see no logical issues with this proposition. It’s basically just a way to shortcut the evaluation of block expressions.

Now, does it carry its weight in terms of control flow complexity? Would correctness and understandability of algorithms become easier or more difficult to achieve with this in the mix? This is my question because I value these over elegance or expressiveness.

It would be useful to see some motivating examples.

I admit, I’ve always had a bad feeling about unnamed breaks and continues. One copy-paste or refactoring gone wrong and the landing site is different than the one intended. On the other hand, labels can cause visual clutter by themselves, I suppose.


#15

This is more or less the “break from any block” or “functional goto” (cc @Ericson2314) idea that has been bandied about since RFC 243, though I don’t believe there has been any actual RFC (or it seems, even RFC issue) for it.


#16

My WIP draft is https://github.com/Ericson2314/rust-rfcs/blob/goto/text/0000-goto.md


#17

I hope that this is just a joke…


#18

Err, it’s not a joke. Dijkstra’s crticisms don’t really apply to this, for example.


#19

This feel like something that can be done with a procedural macro. No need to be a language feature.


#20

Nope. see http://jamey.thesharps.us/2016/11/go-to-statement-considered-harmless.html?m=1 . Goto can do arbitrary graphs, like the MIR. But c-style control flow cannot without some sort of indirection (e.g. pattern match on enum in loop).