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 ControlFlow enum, for use with try_fold and in Try #75744

Closed
5 of 9 tasks
NoraCodes opened this issue Aug 20, 2020 · 43 comments · Fixed by #130518
Closed
5 of 9 tasks

Tracking issue for ControlFlow enum, for use with try_fold and in Try #75744

NoraCodes opened this issue Aug 20, 2020 · 43 comments · Fixed by #130518
Assignees
Labels
A-control-flow Area: Control flow A-iterators Area: Iterators C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. Libs-Tracked Libs issues that are tracked on the team's project board. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@NoraCodes
Copy link
Contributor

NoraCodes commented Aug 20, 2020

(edited to turn this into a tracking issue, as it's referenced by the unstable attributes)

This is a tracking issue for the std::ops::ControlFlow type.
The feature gate for the issue is #![feature(control_flow_enum)].

About tracking issues

Tracking issues are used to record the overall progress of implementation.
They are also uses as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved Questions

Implementation history

Initial PR that added LoopState as an implementation detail: #45595
PR that exposed as unstable and renamed to ControlFlow: #76204
Added BREAK/CONTINUE associated constants: #76318
Changed type parameter order and defaulted C = (): #76614
Add is_break and is_continue methods: #78200


I work with an organization that has a large amount of Rust graph traversal code as part of its core business. We used to use itertools's fold_while for short-circuiting functional-style loops over slices of our graphs, which is now deprecated in favor of try_fold.

try_fold is great, but the lack of a standard library provided Try implementation that makes the loop semantics clear is confusing. We created our own, which is fine, but I think it would make a lot of sense to expose LoopState and provide an example in the docs.

Originally related to: rust-itertools/itertools#469

@ecstatic-morse
Copy link
Contributor

ecstatic-morse commented Aug 22, 2020

Big +1 for this. A Break/Continue enum is also the correct abstraction for the Try trait instead of Result IMO, so I think this is worthy inclusion in the standard library (although in that case a name change would be in order). FWIW, I usually call this enum ControlFlow.

@scottmcm
Copy link
Member

scottmcm commented Aug 24, 2020

As the one who added LoopState initially, I'm not at all tied to that name, so morse's rename sounds good to me -- in fact I've also been calling it ControlFlow in the Try discussions.

@NoraCodes Want to take on making a PR here? There's probably a few parts:

  • Renaming from LoopState to ControlFlow
  • Making it pub and having appropriate #[unstable] markers on it
  • Probably moving it out of core::iter; maybe to core::ops instead?
  • Reexporting from std
  • Derive a few more things on it now that it's intended to be a real type and not just something internal -- probably at least Clone, Copy, Debug (like morse's example has) in addition to its current PartialEq. (I would personally avoid PartialOrd and Ord for now -- if we stay order-agnostic for now we can change the discriminants later to make interconversion with result as cheap as possible without breaking people.)

Feel free to ping me (here, in a draft PR, on community Discord, or on Zulip) if you have questions.

Its methods are probably imperfect right now, but if it's unstable that's ok. People can start using it on nightly as we'll find out what needs fixing before stabilization that way.

@scottmcm scottmcm added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. C-enhancement Category: An issue proposing an enhancement or a PR with one. C-feature-request Category: A feature request, i.e: not implemented / a PR. and removed C-enhancement Category: An issue proposing an enhancement or a PR with one. labels Aug 24, 2020
@NoraCodes
Copy link
Contributor Author

I would be happy to do this. I may ask for some help getting a development environment set up as I have yet to contribute to the core libraries.

@scottmcm

This comment has been minimized.

@scottmcm scottmcm added C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. and removed C-feature-request Category: A feature request, i.e: not implemented / a PR. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. labels Sep 3, 2020
@scottmcm scottmcm changed the title Expose LoopState to make try_fold closures easier to write and more idiomatic Tracking issue for ControlFlow enum, for use with try_fold and in Try Sep 3, 2020
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this issue Sep 3, 2020
…=scottmcm

Rename and expose LoopState as ControlFlow

Basic PR for rust-lang#75744. Addresses everything there except for documentation; lots of examples are probably a good idea.
@scottmcm
Copy link
Member

scottmcm commented Sep 3, 2020

Congrats on your first core libraries PR, @NoraCodes!

Want to take on the generic parameter reordering (that morse brought up in #76204 (comment)) next?

@NoraCodes
Copy link
Contributor Author

NoraCodes commented Sep 3, 2020 via email

@KodrAus KodrAus added the Libs-Tracked Libs issues that are tracked on the team's project board. label Sep 10, 2020
@KodrAus KodrAus added A-control-flow Area: Control flow A-iterators Area: Iterators labels Sep 22, 2020
@scottmcm
Copy link
Member

Another example of a type that's pretty close to this: the Transition type in https://deislabs.io/posts/a-fistful-of-states/, which is essentially ControlFlow<Result<()>, Box<dyn State>> -- either execution is done (possibly with an error), or it continues (in the specified state).

@scottmcm
Copy link
Member

scottmcm commented Oct 21, 2020

An MCP to use this more often in the compiler: rust-lang/compiler-team#374

Conveniently that also suggests that ControlFlow<BreakType> would be better than the current ControlFlow<(), BreakType>.

EDIT: A great comment on the PR that implements the MCP (#78182 (comment)):

This PR is awesome (and exposes so many issues of different sublety)

Nice to see confirmation of the value 🙂

@NoraCodes
Copy link
Contributor Author

NoraCodes commented Oct 21, 2020

Apologies for taking forever on this. I set up a Rust dev env on my new PC and fixed the nits in that PR so we should be good to go. I'll move forward with the other work, and documentation.

JohnTitor added a commit to JohnTitor/rust that referenced this issue Oct 22, 2020
…, r=scottmcm

Add `ControlFlow::is_{break,continue}` methods

r? @scottmcm cc rust-lang#75744
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jul 21, 2022
…inators, r=scottmcm

Add map_continue and continue_value combinators to ControlFlow

As suggested in this comment: rust-lang#75744 (comment)

Related tracking issue: rust-lang#75744

r? `````@scottmcm`````
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jul 21, 2022
…inators, r=scottmcm

Add map_continue and continue_value combinators to ControlFlow

As suggested in this comment: rust-lang#75744 (comment)

Related tracking issue: rust-lang#75744

r? ``````@scottmcm``````
workingjubilee pushed a commit to tcdi/postgrestd that referenced this issue Sep 15, 2022
…r=scottmcm

Add map_continue and continue_value combinators to ControlFlow

As suggested in this comment: rust-lang/rust#75744 (comment)

Related tracking issue: rust-lang/rust#75744

r? ``````@scottmcm``````
@ModProg
Copy link
Contributor

ModProg commented Dec 10, 2022

I found a usecase where maybe ControlFlow could be used as well, having an early exit from a try_fold for speed up using the same value as the aggregator.

In case this should be in a new issue, let me know.

Something like

impl<T> ControlFlow<T, T> {
    pub fn value(self) -> T {
        match self {
            ControlFlow::Continue(value) | ControlFlow::Break(value) => value,
        }
    }
}

allowing to do

let value = smth.try_fold(Vec::new(), |aggr, curr| {
  // Do something 
  if todo!("Some condition that checks weather aggr is ready for early exit e.g. full") {
    ControlFlow::Break(aggr)
  } else {
    ControlFlow::Continue(aggr)
  }
}).value();

@yoshuawuyts
Copy link
Member

yoshuawuyts commented Mar 10, 2023

I've been wondering about whether we could move some of the shared operations between Option/Result/ControlFlow/Poll to the Try trait. The most obvious one probably being Try::map, but probably methods such as Try::inspect and Try::filter could make sense too.

I realized that ControlFlow doesn't have a singular operation named map; it has both map_continue and map_break. Because ControlFlow maps Try::Output to the value contained in the ControlFlow::Continue, it might be more consistent to rename the map_continue operation to just map. This would match the Result::map / Result::map_err scheme too.

Proposed Method Naming

  • ControlFlow::map_continue -> ControlFlow::map
  • ControlFlow::map_break -> ControlFlow::map_break (no change)

@schuelermine
Copy link
Contributor

@ModProg I have also run into a case where such a method would be desirable

@LeoniePhiline
Copy link

The naming of ControlFlow::break_value and ControlFlow::continue_value is inconsistent with Result::err and Result::break.

Should the two methods be renamed before stabilization?

Proposed Method Naming

Current name New name Consistent with
ControlFlow::break_value ControlFlow::break Result::err
ControlFlow::continue_value ControlFlow::continue Result::ok

@schuelermine
Copy link
Contributor

schuelermine commented Sep 27, 2023

They cannot have those names because they are strict keywords.

@SmnTin
Copy link

SmnTin commented Jul 12, 2024

Can we stabilize map_break and map_continue?

@scottmcm
Copy link
Member

Hello libs-api folks! Inspired by the previous comment, I'm nominating this to get your thoughts on the methods currently tracked by this issue:

impl<B, C> ControlFlow<B, C> {
    pub fn break_value(self) -> Option<B>;
    pub fn map_break<T, F>(self, f: F) -> ControlFlow<T, C>
        where F: FnOnce(B) -> T;
    pub fn continue_value(self) -> Option<C>;
    pub fn map_continue<T, F>(self, f: F) -> ControlFlow<B, T>
        where F: FnOnce(C) -> T;
}

Methods like this were intentionally not part of the RFC that added ControlFlow (https://rust-lang.github.io/rfcs/3058-try-trait-v2.html#methods-on-controlflow) so there hasn't been any previous checkboxes about them.

These are basically like Result::ok and Result::map_err, but because break and continue are keywords, ControlFlow::break doesn't work. It'd need to be ControlFlow::r#break, which I assume we don't want. How do you feel about the break_value and continue_value as the solution to that?

The other naming question from the thread is how to name the map methods, #75744 (comment). I added them as map_break and map_continue, thinking of this type as not prioritizing one side over the other, which personally I think I still like. But map_continue is the try { f(x?) } version, so one could argue it should be named just map like Option::map and Result::map -- though of course there's no Option::map_none so you could also say that that's why the Option one is just map.

@scottmcm scottmcm added the I-libs-api-nominated Nominated for discussion during a libs-api team meeting. label Jul 12, 2024
@SmnTin
Copy link

SmnTin commented Jul 12, 2024

@scottmcm If we add these methods, unwrap_break and unwrap_continue might also be good additions. Or their utility is not as obvious as Result::unwrap and Result::unwrap_err?

@m-ou-se
Copy link
Member

m-ou-se commented Jul 16, 2024

@scottmcm We discussed your comment in the libs-api meeting. We're happy with the current names (with _value). We agree with what you said about map_break and map_continue, to not prioritize one over the other.

@m-ou-se m-ou-se removed the I-libs-api-nominated Nominated for discussion during a libs-api team meeting. label Jul 16, 2024
@dhedey
Copy link

dhedey commented Sep 18, 2024

I'm sorry if this is the wrong place to mention this; but I've just been using ControlFlow for a visitor pattern (well a validator + visitor), and had a couple of notes that I think would be very beneficial to other users, and hopefully easy tweaks:

  • I got bitten by the fact ControlFlow is not #[must_use], and I missed a ? to propagate the control flow in one location, and failed to abort / return an error. Could we consider adding this? Must like with result, I think the default expectation is that control flows should be propagated. As a workaround / sanity-check for now, I'm manually adding #[must_use] manually to every method returning a ControlFlow.
  • On the interface of the interpreter (which calls the visitor), I wanted to turn the ControlFlow<E, ()> into a Result<(), E> but found it weird that there was no obvious built-in conversion between ControlFlow and Result. Obviously this is a simple match, but it felt like a bit of an omission. Would we consider one of:
    • Methods continue_result which maps Continue to Ok and break_result which maps Break to Ok - I can see some reasonable use cases for both, depending on the algorithm / expectation.
    • Possibly an implementation of impl<C, B> From<ControlFlow<B, C>> for Result<C, B>, if we felt like the more typical conversion is for Continue => Ok.

@NoraCodes
Copy link
Contributor Author

NoraCodes commented Sep 18, 2024

I would be in favor of a #[must_use] annotation on ControlFlow.

I'm concerned that implementing Into<Result< >> would be confusing, since it wouldn't be obvious or easy to remember whether it was impl<C, B> From<ControlFlow<B, C>> for Result<C, B> or impl<C, B> From<ControlFlow<B, C>> for Result<B, C>. I think the proposed continue_result and break_result is a better idea, perhaps made even clearer if called continue_ok and break_ok.

@dhedey
Copy link

dhedey commented Sep 18, 2024

Thanks @NoraCodes - and just to add I much prefer the continue_ok and break_ok names you propose!

@scottmcm
Copy link
Member

I've put up a stabilization PR for the remaining things tracked under this issue in #130518

@dhedey, can you open a new item for your request so it's more likely to be considered instead of lost? Maybe open an ACP to make the case to the libs-api folks, then if that's approved the attribute addition can be PRed?

@scottmcm
Copy link
Member

scottmcm commented Sep 18, 2024

@ModProg @schuelermine Could one of your open an ACP proposing the into_value (or whatever name) method? It sounds plausible to me, but it should have an ACP in order to add it, and then probably a different tracking issue from the existing things.

(This one's over 4 years old, so I'd like to get it closed out rather than make people expand the history and read a bunch of no-longer-relevant things.)

@dhedey
Copy link

dhedey commented Sep 18, 2024

Will do, thanks @scottmcm 👍

workingjubilee added a commit to workingjubilee/rustc that referenced this issue Oct 4, 2024
…ra, r=dtolnay

Stabilize the `map`/`value` methods on `ControlFlow`

And fix the stability attribute on the `pub use` in `core::ops`.

libs-api in rust-lang#75744 (comment) seemed reasonably happy with naming for these, so let's try for an FCP.

Summary:
```rust
impl<B, C> ControlFlow<B, C> {
    pub fn break_value(self) -> Option<B>;
    pub fn map_break<T>(self, f: impl FnOnce(B) -> T) -> ControlFlow<T, C>;
    pub fn continue_value(self) -> Option<C>;
    pub fn map_continue<T>(self, f: impl FnOnce(C) -> T) -> ControlFlow<B, T>;
}
```

Resolves rust-lang#75744

`@rustbot` label +needs-fcp +t-libs-api -t-libs

---

Aside, in case it keeps someone else from going down the same dead end: I looked at the `{break,continue}_value` methods and tried to make them `const` as part of this, but that's disallowed because of not having `const Drop`, so put it back to not even unstably-const.
@bors bors closed this as completed in 5a8fcab Oct 5, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Oct 5, 2024
Rollup merge of rust-lang#130518 - scottmcm:stabilize-controlflow-extra, r=dtolnay

Stabilize the `map`/`value` methods on `ControlFlow`

And fix the stability attribute on the `pub use` in `core::ops`.

libs-api in rust-lang#75744 (comment) seemed reasonably happy with naming for these, so let's try for an FCP.

Summary:
```rust
impl<B, C> ControlFlow<B, C> {
    pub fn break_value(self) -> Option<B>;
    pub fn map_break<T>(self, f: impl FnOnce(B) -> T) -> ControlFlow<T, C>;
    pub fn continue_value(self) -> Option<C>;
    pub fn map_continue<T>(self, f: impl FnOnce(C) -> T) -> ControlFlow<B, T>;
}
```

Resolves rust-lang#75744

``@rustbot`` label +needs-fcp +t-libs-api -t-libs

---

Aside, in case it keeps someone else from going down the same dead end: I looked at the `{break,continue}_value` methods and tried to make them `const` as part of this, but that's disallowed because of not having `const Drop`, so put it back to not even unstably-const.
@scottmcm
Copy link
Member

scottmcm commented Oct 5, 2024

Huzzah, everything tracked here is now stable!

For anyone with ControlFlow-related requests in future, please make new ACPs/bugs/etc for them as appropriate.

flip1995 pushed a commit to flip1995/rust-clippy that referenced this issue Oct 10, 2024
…lnay

Stabilize the `map`/`value` methods on `ControlFlow`

And fix the stability attribute on the `pub use` in `core::ops`.

libs-api in rust-lang/rust#75744 (comment) seemed reasonably happy with naming for these, so let's try for an FCP.

Summary:
```rust
impl<B, C> ControlFlow<B, C> {
    pub fn break_value(self) -> Option<B>;
    pub fn map_break<T>(self, f: impl FnOnce(B) -> T) -> ControlFlow<T, C>;
    pub fn continue_value(self) -> Option<C>;
    pub fn map_continue<T>(self, f: impl FnOnce(C) -> T) -> ControlFlow<B, T>;
}
```

Resolves #75744

``@rustbot`` label +needs-fcp +t-libs-api -t-libs

---

Aside, in case it keeps someone else from going down the same dead end: I looked at the `{break,continue}_value` methods and tried to make them `const` as part of this, but that's disallowed because of not having `const Drop`, so put it back to not even unstably-const.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-control-flow Area: Control flow A-iterators Area: Iterators C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. Libs-Tracked Libs issues that are tracked on the team's project board. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.