Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking issue for RFC 2046, label-break-value #48594

Closed
4 tasks done
Centril opened this issue Feb 27, 2018 · 186 comments
Closed
4 tasks done

Tracking issue for RFC 2046, label-break-value #48594

Centril opened this issue Feb 27, 2018 · 186 comments
Labels
B-RFC-implemented Blocker: Approved by a merged RFC and implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. F-label_break_value `#![feature(label_break_value)]` finished-final-comment-period The final comment period is finished for this PR / Issue. relnotes Marks issues that should be documented in the release notes of the next release. S-tracking-needs-summary Status: It's hard to tell what's been done and what hasn't! Someone should do some investigation. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Milestone

Comments

@Centril
Copy link
Contributor

Centril commented Feb 27, 2018

This is a tracking issue for RFC 2046 (rust-lang/rfcs#2046).

Steps:

Unresolved questions:


Note from shepard: This is a very long thread. If you're just looking for the 2022 conversation in which this ended up stabilized, jump to the report @ #48594 (comment)

@Centril Centril added B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. T-lang Relevant to the language team, which will review and decide on the PR/issue. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. labels Feb 27, 2018
@SoniEx2
Copy link
Contributor

SoniEx2 commented Feb 28, 2018

Using "return" would have interesting implications for labeled ? (tryop questionmark operator thingy).

@est31
Copy link
Member

est31 commented Mar 1, 2018

Use return as the keyword instead of break?

@mark-i-m and @joshtriplett have already spoken out against return, but I'll join in given that it is still apparently an unresolved question.

break (and continue) are only usable with a loop.
If you can break something, you can continue it. (I don't think there's an obvious syntax to pick for continue on a block.)

In C, C++, Java, C#, JavaScript and probably more languages you are usually breaking a switch statement in order to prevent fall-through. Rust has solved this much nicer with | in patterns but people coming from those languages won't really see break as something only for for loops. Especially as Java and JavaScript expose the feature via break as well and not return.

The "rules to remember" argument works into the other direction really well however. From what I can tell it is a commonality of the mentioned languages as well as Rust that return only applies to functions and nothing else. So if you see a return, you know that a function is being left.

Labeling a block doesn't cause errors on existing unlabeled breaks

First, I think this happens very rarely as the labeled break feature is admittedly not something that will be used 10 times per 1000 lines. After all, it will only apply to unlabeled breaks that would cross the boundary of the block, not unlabeled breaks inside the block. Second, users of Rust are accustomed to complaints / error messages by the compiler, they will happily fix them! Third (this is the strongest point I think), if instead of labeling a block you wrap it into a loop, you already need to watch out for unlabeled breaks and there is no error message that conveniently lists the break statements, you've got to hunt for them yourself :).

@nikomatsakis
Copy link
Contributor

Especially as Java and JavaScript expose the feature via break as well and not return.

This to me is the killer point. break from blocks is a thing in many languages. return from blocks...not so much.

@Centril
Copy link
Contributor Author

Centril commented Mar 1, 2018

Personally, I share @joshtriplett's view on using break instead of return, but it seemed to me like the discussion hadn't been resolved on the RFC... If you believe the question is resolved in the lang team, tick the box with a note =)

@est31
Copy link
Member

est31 commented Apr 15, 2018

Just saying that I'm working on this. Don't need mentor instructions. Just to not duplicate any efforts. Expect a PR soon.

@scottmcm
Copy link
Member

scottmcm commented Apr 15, 2018

I'm still in favour of return over break, but I can agree to disagree here. Question resolved.

bors added a commit that referenced this issue May 16, 2018
Implement label break value (RFC 2046)

Implement label-break-value (#48594).
@topecongiro
Copy link
Contributor

Currently (with rustc 1.28.0-nightly (a1d4a9503 2018-05-20)) rustc does not allow unsafe on a labeled block. Is this expected?

@scottmcm
Copy link
Member

@topecongiro Yes, I believe it's intentional that this is currently allowed only on plain blocks. It might change in future, but given that this is such a low-level and unusual feature, I'm inclined towards that being a feature rather than a restriction. (On the extreme, I certainly don't want else 'a: {.)

@mark-i-m
Copy link
Member

mark-i-m commented May 21, 2018

Definitely agree. Unsafe + unusual control flow sounds like something to discourage.

In a pinch, though, you could use:

'a: {
unsafe {...}
}

Right?

@SoniEx2
Copy link
Contributor

SoniEx2 commented May 22, 2018

Actually, altho else does create a new lexical scope, it's not a block. The whole if-else is a block (kinda). So no, you wouldn't get else 'a: { you'd get 'a: if ... else {.

@eddyb
Copy link
Member

eddyb commented May 22, 2018

else contains a block (expression). there is no "new lexical scope" without blocks.
An even worse surface syntax position than else would be 'a: while foo 'b: {...}.
(interestingly enough, continue 'a is break 'b, we might want to rely on that at least internally)

@Centril
Copy link
Contributor Author

Centril commented May 22, 2018

(interestingly enough, continue 'a is break 'b, we might want to rely on that at least internally)

That's a great observation!

@SoniEx2
Copy link
Contributor

SoniEx2 commented May 22, 2018

I think labels should be part of block-containing expressions, not blocks themselves. We already have precedent for this with loop. (As it happens, a plain block itself is also a block-containing expression. But things like if and loop are block-containing expressions without being blocks I guess.)

(Things like while or for shouldn't support label-break-value, because they could or could not return a value based on whether they complete normally or with a break.)

@mark-i-m
Copy link
Member

@eddyb

(interestingly enough, continue 'a is break 'b, we might want to rely on that at least internally)

Only if break 'b re-checks the loop condition...

@eddyb
Copy link
Member

eddyb commented May 23, 2018

@mark-i-m It's equivalent to 'a: while foo {'b: {...}}, the break wouldn't check the loop condition, the loop itself would, because the loop condition is checked before each iteration of the body block.

@mark-i-m
Copy link
Member

Woah, I find that highly unintuitive. I expect break 'b to be basically goto 'b, meaning we never exit the loop body and the condition is not checked again...

@mark-i-m
Copy link
Member

Oh 🤦‍♂️ I see...

@mark-i-m
Copy link
Member

This is why I don't like labeled break/continue :/

@rpjohnst
Copy link
Contributor

Well, we specifically don't have the ability to label these weird inner blocks, so I don't see the problem. break always means "leave this block" and, given the above restriction, there's no way for that to mean anything other than "goto the spot after the associated closing brace."

@mark-i-m
Copy link
Member

My confusion was not specific to weird inner blocks, but I don't really want to reopen the discussion. That already happened and the community decided to add it.

@SoniEx2
Copy link
Contributor

SoniEx2 commented May 24, 2018

Okay, I understand accessibility is a big issue with programming languages... however, labeled break is extremely useful if you write code like me.

So, how can we make labeled break more accessible?

@mark-i-m
Copy link
Member

So, how can we make labeled break more accessible?

That's a great question. Some ideas I had:

  • We should collect some samples of how people use this in the wild. We can look for undesirable patterns or lazy habits.
  • We should have opinionated style around when it is considered poor practice to use labeled break/continue.
  • We may be able to add lints for some patterns that could be turned into loops/iterator combinators mechanically (I can't think of any such patterns off the top of my head, though).

As a first (admittedly biased) sample, my last (and first) encounter with labeled break in real code was not stellar: https://github.com/rust-lang/rust/pull/48456/files#diff-3ac60df36be32d72842bf5351fc2bb1dL51. I respectfully suggest that if the original author had put in slightly more effort they could have avoided using labeled break in that case altogether... This is an example of the sort of practice I would like to discourage if possible.

@rpjohnst
Copy link
Contributor

That's... not labeled break?

@rfcbot rfcbot added to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Aug 24, 2022
@jyn514
Copy link
Member

jyn514 commented Aug 24, 2022

Should the 'b: have been on the {} in _ => {}, or is match just not usable as an example here?

@eddyb you're right, I misread https://doc.rust-lang.org/nightly/reference/expressions/match-expr.html#match-expressions. match is not usable as an example here because match arms allow any ExpressionWithBlock (which eventually allows LoopExpression). Here's a correct example of it being disallowed on BlockExpression: https://doc.rust-lang.org/nightly/reference/expressions/if-expr.html#if-expressions

fn labeled_match() {
    if true 'b: {  //~ ERROR block label not supported here

jyn514 added a commit to jyn514/rust that referenced this issue Aug 24, 2022
 # Stabilization proposal

The feature was implemented in rust-lang#50045 by est31 and has been in nightly since 2018-05-16 (over 4 years now).
There are [no open issues][issue-label] other than the tracking issue. There is a strong consensus that `break` is the right keyword and we should not use `return`.

There have been several concerns raised about this feature on the tracking issue (other than the one about tests, which has been fixed, and an interaction with try blocks, which has been fixed).
1. nrc's original comment about cost-benefit analysis: rust-lang#48594 (comment)
2. joshtriplett's comments about seeing use cases: rust-lang#48594 (comment)
3. withoutboats's comments that Rust does not need more control flow constructs: rust-lang#48594 (comment)

Many different examples of code that's simpler using this feature have been provided:
- A lexer by rpjohnst which must repeat code without label-break-value: rust-lang#48594 (comment)
- A snippet by SergioBenitez which avoids using a new function and adding several new return points to a function: rust-lang#48594 (comment). This particular case would also work if `try` blocks were stabilized (at the cost of making the code harder to optimize).
- Several examples by JohnBSmith: rust-lang#48594 (comment)
- Several examples by Centril: rust-lang#48594 (comment)
- An example by petrochenkov where this is used in the compiler itself to avoid duplicating error checking code: rust-lang#48594 (comment)
- Amanieu recently provided another example related to complex conditions, where try blocks would not have helped: rust-lang#48594 (comment)

Additionally, petrochenkov notes that this is strictly more powerful than labelled loops due to macros which accidentally exit a loop instead of being consumed by the macro matchers: rust-lang#48594 (comment)

nrc later resolved their concern, mostly because of the aforementioned macro problems.
joshtriplett suggested that macros could be able to generate IR directly
(rust-lang#48594 (comment)) but there are no open RFCs,
and the design space seems rather speculative.

joshtriplett later resolved his concerns, due to a symmetry between this feature and existing labelled break: rust-lang#48594 (comment)

withoutboats has regrettably left the language team.

joshtriplett later posted that the lang team would consider starting an FCP given a stabilization report: rust-lang#48594 (comment)

[issue-label]: https://github.com/rust-lang/rust/issues?q=is%3Aissue+is%3Aopen+label%3AF-label_break_value+

 ## Report

+ Feature gate:
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/src/test/ui/feature-gates/feature-gate-label_break_value.rs
+ Diagnostics:
    - https://github.com/rust-lang/rust/blob/6b2d3d5f3cd1e553d87b5496632132565b6779d3/compiler/rustc_parse/src/parser/diagnostics.rs#L2629
    - https://github.com/rust-lang/rust/blob/f65bf0b2bb1a99f73095c01a118f3c37d3ee614c/compiler/rustc_resolve/src/diagnostics.rs#L749
    - https://github.com/rust-lang/rust/blob/f65bf0b2bb1a99f73095c01a118f3c37d3ee614c/compiler/rustc_resolve/src/diagnostics.rs#L1001
    - https://github.com/rust-lang/rust/blob/111df9e6eda1d752233482c1309d00d20a4bbf98/compiler/rustc_passes/src/loops.rs#L254
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/compiler/rustc_parse/src/parser/expr.rs#L2079
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/compiler/rustc_parse/src/parser/expr.rs#L1569
+ Tests:
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_continue.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_unlabeled_break.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_illegal_uses.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/lint/unused_labels.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/run-pass/for-loop-while/label_break_value.rs

 ## Interactions with other features

Labels follow the hygiene of local variables.

label-break-value is permitted within `try` blocks:
```rust
let _: Result<(), ()> = try {
    'foo: {
        Err(())?;
        break 'foo;
    }
};
```

label-break-value is disallowed within closures, generators, and async blocks:
```rust
'a: {
    || break 'a
    //~^ ERROR use of unreachable label `'a`
    //~| ERROR `break` inside of a closure
}
```

label-break-value is disallowed on [_BlockExpression_]; it can only occur as a [_LoopExpression_]:
```rust
fn labeled_match() {
    match false 'b: { //~ ERROR block label not supported here
        _ => {}
    }
}

macro_rules! m {
    ($b:block) => {
        'lab: $b; //~ ERROR cannot use a `block` macro fragment here
        unsafe $b; //~ ERROR cannot use a `block` macro fragment here
        |x: u8| -> () $b; //~ ERROR cannot use a `block` macro fragment here
    }
}

fn foo() {
    m!({});
}
```

[_BlockExpression_]: https://doc.rust-lang.org/nightly/reference/expressions/block-expr.html
[_LoopExpression_]: https://doc.rust-lang.org/nightly/reference/expressions/loop-expr.html
JohnTitor added a commit to JohnTitor/rust that referenced this issue Aug 24, 2022
… r=petrochenkov

Stabilize `#![feature(label_break_value)]`

See the stabilization report in rust-lang#48594 (comment).
JohnTitor added a commit to JohnTitor/rust that referenced this issue Aug 24, 2022
… r=petrochenkov

Stabilize `#![feature(label_break_value)]`

See the stabilization report in rust-lang#48594 (comment).
Jarcho pushed a commit to Jarcho/rust-clippy that referenced this issue Aug 28, 2022
 # Stabilization proposal

The feature was implemented in rust-lang/rust#50045 by est31 and has been in nightly since 2018-05-16 (over 4 years now).
There are [no open issues][issue-label] other than the tracking issue. There is a strong consensus that `break` is the right keyword and we should not use `return`.

There have been several concerns raised about this feature on the tracking issue (other than the one about tests, which has been fixed, and an interaction with try blocks, which has been fixed).
1. nrc's original comment about cost-benefit analysis: rust-lang/rust#48594 (comment)
2. joshtriplett's comments about seeing use cases: rust-lang/rust#48594 (comment)
3. withoutboats's comments that Rust does not need more control flow constructs: rust-lang/rust#48594 (comment)

Many different examples of code that's simpler using this feature have been provided:
- A lexer by rpjohnst which must repeat code without label-break-value: rust-lang/rust#48594 (comment)
- A snippet by SergioBenitez which avoids using a new function and adding several new return points to a function: rust-lang/rust#48594 (comment). This particular case would also work if `try` blocks were stabilized (at the cost of making the code harder to optimize).
- Several examples by JohnBSmith: rust-lang/rust#48594 (comment)
- Several examples by Centril: rust-lang/rust#48594 (comment)
- An example by petrochenkov where this is used in the compiler itself to avoid duplicating error checking code: rust-lang/rust#48594 (comment)
- Amanieu recently provided another example related to complex conditions, where try blocks would not have helped: rust-lang/rust#48594 (comment)

Additionally, petrochenkov notes that this is strictly more powerful than labelled loops due to macros which accidentally exit a loop instead of being consumed by the macro matchers: rust-lang/rust#48594 (comment)

nrc later resolved their concern, mostly because of the aforementioned macro problems.
joshtriplett suggested that macros could be able to generate IR directly
(rust-lang/rust#48594 (comment)) but there are no open RFCs,
and the design space seems rather speculative.

joshtriplett later resolved his concerns, due to a symmetry between this feature and existing labelled break: rust-lang/rust#48594 (comment)

withoutboats has regrettably left the language team.

joshtriplett later posted that the lang team would consider starting an FCP given a stabilization report: rust-lang/rust#48594 (comment)

[issue-label]: https://github.com/rust-lang/rust/issues?q=is%3Aissue+is%3Aopen+label%3AF-label_break_value+

 ## Report

+ Feature gate:
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/src/test/ui/feature-gates/feature-gate-label_break_value.rs
+ Diagnostics:
    - https://github.com/rust-lang/rust/blob/6b2d3d5f3cd1e553d87b5496632132565b6779d3/compiler/rustc_parse/src/parser/diagnostics.rs#L2629
    - https://github.com/rust-lang/rust/blob/f65bf0b2bb1a99f73095c01a118f3c37d3ee614c/compiler/rustc_resolve/src/diagnostics.rs#L749
    - https://github.com/rust-lang/rust/blob/f65bf0b2bb1a99f73095c01a118f3c37d3ee614c/compiler/rustc_resolve/src/diagnostics.rs#L1001
    - https://github.com/rust-lang/rust/blob/111df9e6eda1d752233482c1309d00d20a4bbf98/compiler/rustc_passes/src/loops.rs#L254
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/compiler/rustc_parse/src/parser/expr.rs#L2079
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/compiler/rustc_parse/src/parser/expr.rs#L1569
+ Tests:
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_continue.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_unlabeled_break.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_illegal_uses.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/lint/unused_labels.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/run-pass/for-loop-while/label_break_value.rs

 ## Interactions with other features

Labels follow the hygiene of local variables.

label-break-value is permitted within `try` blocks:
```rust
let _: Result<(), ()> = try {
    'foo: {
        Err(())?;
        break 'foo;
    }
};
```

label-break-value is disallowed within closures, generators, and async blocks:
```rust
'a: {
    || break 'a
    //~^ ERROR use of unreachable label `'a`
    //~| ERROR `break` inside of a closure
}
```

label-break-value is disallowed on [_BlockExpression_]; it can only occur as a [_LoopExpression_]:
```rust
fn labeled_match() {
    match false 'b: { //~ ERROR block label not supported here
        _ => {}
    }
}

macro_rules! m {
    ($b:block) => {
        'lab: $b; //~ ERROR cannot use a `block` macro fragment here
        unsafe $b; //~ ERROR cannot use a `block` macro fragment here
        |x: u8| -> () $b; //~ ERROR cannot use a `block` macro fragment here
    }
}

fn foo() {
    m!({});
}
```

[_BlockExpression_]: https://doc.rust-lang.org/nightly/reference/expressions/block-expr.html
[_LoopExpression_]: https://doc.rust-lang.org/nightly/reference/expressions/loop-expr.html
Jarcho pushed a commit to Jarcho/rust-clippy that referenced this issue Aug 28, 2022
…henkov

Stabilize `#![feature(label_break_value)]`

See the stabilization report in rust-lang/rust#48594 (comment).
@scottmcm
Copy link
Member

Looks like this was merged for 1.65 in #99332 !

@scottmcm scottmcm added this to the 1.65.0 milestone Aug 31, 2022
@scottmcm scottmcm added the relnotes Marks issues that should be documented in the release notes of the next release. label Aug 31, 2022
@matklad
Copy link
Member

matklad commented Sep 5, 2022

Hm, is it intentional that async blocks use return while sync blocks use break?

fn _f() {
    let f = 'b: { break 'b 92 };
    assert_eq!(f, 92);
}

async fn _g() {
    let f = async { return 92 };
    assert_eq!(f.await, 92);
}    

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=db815319f8eb5cee0f7be84854b4bd5e

@est31
Copy link
Member

est31 commented Sep 5, 2022

Yeah it seems a bit weird at first impression, but IMO they are closer to closures than to sync blocks.

Like closures, async blocks are decoupled from the function's normal control flow, they can't interact with it. In other words, async blocks, like closures, are not ran when they are being declared, but when they are being evaluated.

This will print bar and then foo:

#[tokio::main]
async fn main() {
    let f = async { println!("foo"); return 92; };
    println!("bar");
    assert_eq!(f.await, 92);
}

This evaluation difference also shows up in other ways. You can't break out of an outer loop inside an async block for example, while you can in a sync block. You can't even return from the function that declared the block, as the async block can be evaluated long after the function that declared it terminated.

A return statement as to return the function makes no sense for an async block. On the other hand, for sync blocks, being able to return something from the function is highly useful. If the keyword were changed to return instead of break, you would have to introduce lifetimes for return statements, something that would be a new concept.

@RalfJung
Copy link
Member

RalfJung commented Sep 5, 2022

Hm, is it intentional that async blocks use return while sync blocks use break?

Those are completely different pieces of code? The async one already works on stable since forever. I don't see how that even has anything to do with break.

@jyn514
Copy link
Member

jyn514 commented Sep 5, 2022

break vs return has already been discussed at length. Let's please not bikeshed this further unless new information comes to light.

@matklad
Copy link
Member

matklad commented Sep 5, 2022

It does seem to me that interaction between async blocks and labeled blocks is novel information. This isn't covered in stabilization report. There is a bit of discussion here which culminates in

If nothing else, it seems like it should be updated to take async blocks into account

which update didn't seem to happen. Certainly, this wasn't discussed during break/return bikeshed which predates async blocks.


Hence, my question of "did we actually thought about this properly, or is this design by accident?". I don't necessary want to re-open bikescheding here, but I do want to make sure our language design process is capable of noticing feature interactions and surfacing them as documented explicit decisions.

I guess, one can say that these are two completely different snippets of code and there's no actual feature interactions here. But, as a naive user who doesn't look much beyond syntax, it sure does feel weird that we use "different" syntax to mean the "same" thing.

@afetisov
Copy link

afetisov commented Sep 5, 2022

it sure does feel weird that we use "different" syntax to mean the "same" thing.

It's not more weird than the fact that we have separate return and break operators to begin with, even if both "mean the same thing" of returning from a block. We just have a special syntax for the toplevel block, because it's so common and so familiar to programmers.

The break on blocks is chosen by analogy with break from loop. return from an async block is chosen by symmetry with return from async fn, since an async function is a sugar for a function returning an async block.

However, I'm not sure whether there was much discussion of the differences between break from block and return from async block, since the async RFC was proposed after label-break-value was accepted and the break syntax was agreed on. Personally I didn't find any explicit comparison between those.

@scottmcm
Copy link
Member

scottmcm commented Sep 6, 2022

return from async { } is because they're really closures, not blocks -- it produces something that will run later, not something that runs immediately like normal blocks or loops do.

You're better thinking of async { BODY } as (async || BODY)() than as a block at all.

it sure does feel weird that we use "different" syntax to mean the "same" thing.

I think the important distinguisher is that block-break only works with a label. There's no 'a: async { break 'a }, nor is there ever return 'a.

(And if you attempt 'a: { async { break 'a } }, then you get an error, just like if you attempt 'a: { || break 'a }.)

@ehuss
Copy link
Contributor

ehuss commented Sep 6, 2022

This seems to have landed without a documentation PR being open. Usually there should be documentation ready before the feature is stabilized. Was there something that I missed? The documentation checkbox was marked, but I don't see where that documentation was posted.

Can someone please follow up on that?

@RalfJung
Copy link
Member

RalfJung commented Sep 6, 2022

It does seem to me that interaction between async blocks and labeled blocks is novel information.

I don't agree, they are entirely orthogonal. However, you need to think of async blocks as "closures without arguments", or "thunks" in PL speak. A thunk is a suspended computation, i.e., a computation that can be continued later. Calling them async "blocks" is indeed confusing since it makes them sound like regular blocks, which they are not:

fn main() {
  let _x = { println!("hello from block") };
  let _x = async { println!("hello from async block") };
}

The 2nd println is never executed.

Once you use an adequate mental model of async blocks thunks, I don't think there is any new information in their interaction with break 'label -- they interact exactly the same way closures do:

fn _f() {
    let f = 'b: { break 'b 92 };
    assert_eq!(f, 92);
}

fn _g() {
    let f = || { return 92 };
    assert_eq!(f(), 92);
}    

You're better thinking of async { BODY } as (async || BODY)() than as a block at all.

More like async || BODY; the closure is not actually invoked yet.

it sure does feel weird that we use "different" syntax to mean the "same" thing.

Your example is exactly like this one:

fn uwu() -> i32 {
    return 0;
}

fn foo() -> i32 { 'a: {
    break 'a 0;
}}

That's also "two different syntax to mean the same thing". break is strictly more general than return, so indeed we could remove return from the language now that we have break on labeled blocks -- but the return case is so common that it is worth having a separate keyword.

@jyn514
Copy link
Member

jyn514 commented Sep 6, 2022

@ehuss rust-lang/reference#1263

@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Sep 8, 2022
workingjubilee pushed a commit to tcdi/postgrestd that referenced this issue Sep 15, 2022
 # Stabilization proposal

The feature was implemented in rust-lang/rust#50045 by est31 and has been in nightly since 2018-05-16 (over 4 years now).
There are [no open issues][issue-label] other than the tracking issue. There is a strong consensus that `break` is the right keyword and we should not use `return`.

There have been several concerns raised about this feature on the tracking issue (other than the one about tests, which has been fixed, and an interaction with try blocks, which has been fixed).
1. nrc's original comment about cost-benefit analysis: rust-lang/rust#48594 (comment)
2. joshtriplett's comments about seeing use cases: rust-lang/rust#48594 (comment)
3. withoutboats's comments that Rust does not need more control flow constructs: rust-lang/rust#48594 (comment)

Many different examples of code that's simpler using this feature have been provided:
- A lexer by rpjohnst which must repeat code without label-break-value: rust-lang/rust#48594 (comment)
- A snippet by SergioBenitez which avoids using a new function and adding several new return points to a function: rust-lang/rust#48594 (comment). This particular case would also work if `try` blocks were stabilized (at the cost of making the code harder to optimize).
- Several examples by JohnBSmith: rust-lang/rust#48594 (comment)
- Several examples by Centril: rust-lang/rust#48594 (comment)
- An example by petrochenkov where this is used in the compiler itself to avoid duplicating error checking code: rust-lang/rust#48594 (comment)
- Amanieu recently provided another example related to complex conditions, where try blocks would not have helped: rust-lang/rust#48594 (comment)

Additionally, petrochenkov notes that this is strictly more powerful than labelled loops due to macros which accidentally exit a loop instead of being consumed by the macro matchers: rust-lang/rust#48594 (comment)

nrc later resolved their concern, mostly because of the aforementioned macro problems.
joshtriplett suggested that macros could be able to generate IR directly
(rust-lang/rust#48594 (comment)) but there are no open RFCs,
and the design space seems rather speculative.

joshtriplett later resolved his concerns, due to a symmetry between this feature and existing labelled break: rust-lang/rust#48594 (comment)

withoutboats has regrettably left the language team.

joshtriplett later posted that the lang team would consider starting an FCP given a stabilization report: rust-lang/rust#48594 (comment)

[issue-label]: https://github.com/rust-lang/rust/issues?q=is%3Aissue+is%3Aopen+label%3AF-label_break_value+

 ## Report

+ Feature gate:
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/src/test/ui/feature-gates/feature-gate-label_break_value.rs
+ Diagnostics:
    - https://github.com/rust-lang/rust/blob/da920d15830d2376be914c68767c4b7dca846959/compiler/rustc_parse/src/parser/diagnostics.rs#L2629
    - https://github.com/rust-lang/rust/blob/f65bf0b2bb1a99f73095c01a118f3c37d3ee614c/compiler/rustc_resolve/src/diagnostics.rs#L749
    - https://github.com/rust-lang/rust/blob/f65bf0b2bb1a99f73095c01a118f3c37d3ee614c/compiler/rustc_resolve/src/diagnostics.rs#L1001
    - https://github.com/rust-lang/rust/blob/111df9e6eda1d752233482c1309d00d20a4bbf98/compiler/rustc_passes/src/loops.rs#L254
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/compiler/rustc_parse/src/parser/expr.rs#L2079
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/compiler/rustc_parse/src/parser/expr.rs#L1569
+ Tests:
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_continue.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_unlabeled_break.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_illegal_uses.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/lint/unused_labels.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/run-pass/for-loop-while/label_break_value.rs

 ## Interactions with other features

Labels follow the hygiene of local variables.

label-break-value is permitted within `try` blocks:
```rust
let _: Result<(), ()> = try {
    'foo: {
        Err(())?;
        break 'foo;
    }
};
```

label-break-value is disallowed within closures, generators, and async blocks:
```rust
'a: {
    || break 'a
    //~^ ERROR use of unreachable label `'a`
    //~| ERROR `break` inside of a closure
}
```

label-break-value is disallowed on [_BlockExpression_]; it can only occur as a [_LoopExpression_]:
```rust
fn labeled_match() {
    match false 'b: { //~ ERROR block label not supported here
        _ => {}
    }
}

macro_rules! m {
    ($b:block) => {
        'lab: $b; //~ ERROR cannot use a `block` macro fragment here
        unsafe $b; //~ ERROR cannot use a `block` macro fragment here
        |x: u8| -> () $b; //~ ERROR cannot use a `block` macro fragment here
    }
}

fn foo() {
    m!({});
}
```

[_BlockExpression_]: https://doc.rust-lang.org/nightly/reference/expressions/block-expr.html
[_LoopExpression_]: https://doc.rust-lang.org/nightly/reference/expressions/loop-expr.html
workingjubilee pushed a commit to tcdi/postgrestd that referenced this issue Sep 15, 2022
…henkov

Stabilize `#![feature(label_break_value)]`

See the stabilization report in rust-lang/rust#48594 (comment).
calebcartwright pushed a commit to calebcartwright/rustfmt that referenced this issue Jan 24, 2023
 # Stabilization proposal

The feature was implemented in rust-lang/rust#50045 by est31 and has been in nightly since 2018-05-16 (over 4 years now).
There are [no open issues][issue-label] other than the tracking issue. There is a strong consensus that `break` is the right keyword and we should not use `return`.

There have been several concerns raised about this feature on the tracking issue (other than the one about tests, which has been fixed, and an interaction with try blocks, which has been fixed).
1. nrc's original comment about cost-benefit analysis: rust-lang/rust#48594 (comment)
2. joshtriplett's comments about seeing use cases: rust-lang/rust#48594 (comment)
3. withoutboats's comments that Rust does not need more control flow constructs: rust-lang/rust#48594 (comment)

Many different examples of code that's simpler using this feature have been provided:
- A lexer by rpjohnst which must repeat code without label-break-value: rust-lang/rust#48594 (comment)
- A snippet by SergioBenitez which avoids using a new function and adding several new return points to a function: rust-lang/rust#48594 (comment). This particular case would also work if `try` blocks were stabilized (at the cost of making the code harder to optimize).
- Several examples by JohnBSmith: rust-lang/rust#48594 (comment)
- Several examples by Centril: rust-lang/rust#48594 (comment)
- An example by petrochenkov where this is used in the compiler itself to avoid duplicating error checking code: rust-lang/rust#48594 (comment)
- Amanieu recently provided another example related to complex conditions, where try blocks would not have helped: rust-lang/rust#48594 (comment)

Additionally, petrochenkov notes that this is strictly more powerful than labelled loops due to macros which accidentally exit a loop instead of being consumed by the macro matchers: rust-lang/rust#48594 (comment)

nrc later resolved their concern, mostly because of the aforementioned macro problems.
joshtriplett suggested that macros could be able to generate IR directly
(rust-lang/rust#48594 (comment)) but there are no open RFCs,
and the design space seems rather speculative.

joshtriplett later resolved his concerns, due to a symmetry between this feature and existing labelled break: rust-lang/rust#48594 (comment)

withoutboats has regrettably left the language team.

joshtriplett later posted that the lang team would consider starting an FCP given a stabilization report: rust-lang/rust#48594 (comment)

[issue-label]: https://github.com/rust-lang/rust/issues?q=is%3Aissue+is%3Aopen+label%3AF-label_break_value+

 ## Report

+ Feature gate:
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/src/test/ui/feature-gates/feature-gate-label_break_value.rs
+ Diagnostics:
    - https://github.com/rust-lang/rust/blob/6b2d3d5f3cd1e553d87b5496632132565b6779d3/compiler/rustc_parse/src/parser/diagnostics.rs#L2629
    - https://github.com/rust-lang/rust/blob/f65bf0b2bb1a99f73095c01a118f3c37d3ee614c/compiler/rustc_resolve/src/diagnostics.rs#L749
    - https://github.com/rust-lang/rust/blob/f65bf0b2bb1a99f73095c01a118f3c37d3ee614c/compiler/rustc_resolve/src/diagnostics.rs#L1001
    - https://github.com/rust-lang/rust/blob/111df9e6eda1d752233482c1309d00d20a4bbf98/compiler/rustc_passes/src/loops.rs#L254
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/compiler/rustc_parse/src/parser/expr.rs#L2079
    - https://github.com/rust-lang/rust/blob/d695a497bbf4b20d2580b75075faa80230d41667/compiler/rustc_parse/src/parser/expr.rs#L1569
+ Tests:
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_continue.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_unlabeled_break.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_illegal_uses.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/lint/unused_labels.rs
    - https://github.com/rust-lang/rust/blob/master/src/test/ui/run-pass/for-loop-while/label_break_value.rs

 ## Interactions with other features

Labels follow the hygiene of local variables.

label-break-value is permitted within `try` blocks:
```rust
let _: Result<(), ()> = try {
    'foo: {
        Err(())?;
        break 'foo;
    }
};
```

label-break-value is disallowed within closures, generators, and async blocks:
```rust
'a: {
    || break 'a
    //~^ ERROR use of unreachable label `'a`
    //~| ERROR `break` inside of a closure
}
```

label-break-value is disallowed on [_BlockExpression_]; it can only occur as a [_LoopExpression_]:
```rust
fn labeled_match() {
    match false 'b: { //~ ERROR block label not supported here
        _ => {}
    }
}

macro_rules! m {
    ($b:block) => {
        'lab: $b; //~ ERROR cannot use a `block` macro fragment here
        unsafe $b; //~ ERROR cannot use a `block` macro fragment here
        |x: u8| -> () $b; //~ ERROR cannot use a `block` macro fragment here
    }
}

fn foo() {
    m!({});
}
```

[_BlockExpression_]: https://doc.rust-lang.org/nightly/reference/expressions/block-expr.html
[_LoopExpression_]: https://doc.rust-lang.org/nightly/reference/expressions/loop-expr.html
calebcartwright pushed a commit to calebcartwright/rustfmt that referenced this issue Jan 24, 2023
…henkov

Stabilize `#![feature(label_break_value)]`

See the stabilization report in rust-lang/rust#48594 (comment).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B-RFC-implemented Blocker: Approved by a merged RFC and implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. F-label_break_value `#![feature(label_break_value)]` finished-final-comment-period The final comment period is finished for this PR / Issue. relnotes Marks issues that should be documented in the release notes of the next release. S-tracking-needs-summary Status: It's hard to tell what's been done and what hasn't! Someone should do some investigation. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests