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 bool_to_option #80967

Closed
3 tasks done
KodrAus opened this issue Jan 13, 2021 · 44 comments
Closed
3 tasks done

Tracking Issue for bool_to_option #80967

KodrAus opened this issue Jan 13, 2021 · 44 comments
Labels
C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@KodrAus
Copy link
Contributor

KodrAus commented Jan 13, 2021

Feature gate: #![feature(bool_to_option)]

This is a tracking issue for bool::then_some, a value-receiver version of bool::then, split off from #64260.

Public API

impl bool {
    pub fn then_some<T>(self, t: T) -> Option<T>;
}

Steps / History

Unresolved Questions

@KodrAus KodrAus added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC labels Jan 13, 2021
@iago-lito
Copy link
Contributor

iago-lito commented Jan 13, 2021

I personally like bool.then_some(value) better than bool.then(|| value) because it's less circuit in my head (althought I do expect both to produce same machine code). I would also favour it if it's more consistent with the rest of std.

Otherwise, bool.then(|| value) is fine and I could definitely live with that, so the stakes aren't high IMO. What's the overhead of having both methods in std?

@slerpyyy
Copy link

I'm in favor of deprecating then_some for the following reasons:

  • I still don't like the name
    • Since it is the conceptually simpler version of then, I think it should have the shorter/simpler name of the two functions, similar to and vs and_then, or vs or_else and unwrap_or vs unwrap_or_else and because then is already one word (and we don't want to call it th) this won't be possible.
    • The std crate uses the word then (and else) in function names almost exclusively to imply lazy evaluation (see again and vs and_then). then_some would only be the second function in the entire std crate to break this pattern.
    • The word some makes it sound like there is a then_none or then_ok counterpart to then_some, which does not exist. For the same reason, I believe being exposed to then_some first makes the name of the then function less intuitive and harder to find.
  • then can already do everything then_some can do (and more!)
    • Every use of the then_some function can be trivially translated to use then instead, by adding || in front of the argument.
    • then_some can't even be called shorthand for then, because b.then(|| 5) is 2 bytes shorter b.then_some(5).
    • There is no decrease in performance to using then instead of then_some, because the compiler is smart enough to optimize closures away if they're unnecessary (see this comment in the original issue). Depending on how heavy the computation of the argument is, then might even give you a performance benefit, because the computation is skipped entirely if the bool evaluates to false.

@nagisa
Copy link
Member

nagisa commented Jan 13, 2021

Every use of the then_some function can be trivially translated to use then instead, by adding || in front of the argument.

That wouldn't necessarily have the same semantics. then evaluates the argument, thus executing all of the code necessary to construct the T regardless of the bool's value.

@Boscop
Copy link

Boscop commented Jan 15, 2021

So instead of cond.then_some(foo()) you'd have to write cond.then({ let v = foo(); move || v })
or write it in multiple lines :/

@iago-lito
Copy link
Contributor

The following two versions are not strictly identical indeed:

#![feature(bool_to_option)]

fn create_value() -> u32 {
    println!("creating");
    16
}

fn main() {
    let cond = false;
    println!("{:?}", cond.then_some(create_value()));
    println!("----");
    println!("{:?}", cond.then(|| create_value())); // or cond.then(create_value)
}

prints

creating
None
----
None

So it's not just a matter of syntax.

Not sure whether this particular behaviour of then_some is useful though.

@x87
Copy link

x87 commented Jan 20, 2021

With .then_some I can do the following:

    fn f() -> Option<usize> {
        let x: Option<Vec<u32>> = Some(vec![]);
        true.then_some(x?.len())
    }

how do I rewrite this example using .then?

    fn f() -> Option<usize> {
        let x: Option<Vec<u32>> = Some(vec![]);
        true.then(|| x?.len())
    }

gives an error that a closure should return either a Result or Option.

I can think of an extracting the closure expression into a variable:

    fn f() -> Option<usize> {
        let x: Option<Vec<u32>> = Some(vec![]);
        let len = x?.len();
        true.then(|| len)
    }

But in my opinion the first example with .then_some looks more concise and does not require a block {...}

@jplatte
Copy link
Contributor

jplatte commented Jan 20, 2021

how do I rewrite this example using .then? [...]

If you use ? on your Option<Vec<u32>> anyways, might as well do it before assigning to a local:

fn f() -> Option<usize> {
    let x: Vec<u32> = Some(vec![])?;
    true.then(|| x.len())
}

@slerpyyy
Copy link

If you insist on eagerly evaluating the value inside the option, you can also make use of Option::filter.

@iago-lito

#![feature(bool_to_option)]

fn create_value() -> u32 {
    println!("creating");
    16
}

fn main() {
    let cond = false;
    println!("{:?}", cond.then_some(create_value()));
    println!("----");
    println!("{:?}", Some(create_value()).filter(|_| cond));
}

@x87

fn f() -> Option<usize> {
    let x: Option<Vec<u32>> = Some(vec![]);
    Some(x?.len()).filter(|_| true)
}

@iago-lito
Copy link
Contributor

@slerpyyy Well, this does work, but it becomes really hard to read IMO. I would argue that this being the only workaround to "eager evaluation" would be a good reason to actually include then_some ;)

Note that I'm still unsure whether "eager evaluation" is useful anyway, has anyone ever needed this?

To summarize my understanding of the advantage of then_some against then so far:

  • permits early evaluation (but I'm not sure whether it's useful, is it?)
  • permits early return (but @jplatte offered a convincing workaround IMO)
  • .. anything else?

@varkor
Copy link
Member

varkor commented Jan 22, 2021

I prefer to use then_some than then when possible: needlessly creating a closure feels inelegant, even if in practice it makes no difference to generated code. It is also more consistent with existing APIs. I agree the name is unfortunate, but I don't view this as a reason not to have both. There's no way to make the names consistent with existing APIs now, so we could always go with a shorter name like some that is unrelated to then, which would at least address the length concern. I don't think the two names have to be related: it helps a little with memorisation, but if they're both useful (which in my experience, and others', they are), then it doesn't matter so much.

@slerpyyy
Copy link

You're right, this is a function that users want and there is no reason not to add it to the std. I'm still annoyed that after more than a year of discussion about what we want to call these two functions in the original issue, we went with the only combination of names I actively dislike. Yet, I see how having this function with a somewhat unfortunate name is still better than not having this functionality at all.

@coolshaurya
Copy link

coolshaurya commented Feb 12, 2021

With regards to naming, I personally think then/then_do would have been ideal, but that ship has sailed.

then_some sounds a bit awkward, but I can't think of something better ¯\_(ツ)_/¯

@varkor
Copy link
Member

varkor commented Feb 12, 2021

I think it's fair to say that few people are happy with the names as they stand, but there was no general consensus, and we'd rather have the feature than not :)

@slerpyyy
Copy link

slerpyyy commented Feb 12, 2021

With regards to naming, I personally think then/then_do would have been ideal, but that ship has sailed.

I would have preferred (written as value / closure)

  • then/then_with
  • and/and_then
  • some/then_some
  • and_some/and_then_some
  • to_option/to_option_with
  • on_true/on_true_then
  • if_true/if_true_then

but we went with

  • then_some/then

@SoniEx2
Copy link
Contributor

SoniEx2 commented Mar 8, 2021

reap/then? (or sail/then)

@slerpyyy
Copy link

@SoniEx2 How does reap/sail relate to turning a (bool, T) into an Option<T>?

@SoniEx2
Copy link
Contributor

SoniEx2 commented Mar 12, 2021

nah it's just a reference to that ship having sailed ^^

@SoniEx2
Copy link
Contributor

SoniEx2 commented Mar 12, 2021

(side note: and/and_then we'd expect to return a bool.)

@slerpyyy
Copy link

(I also proposed the names and_some/and_then_some in the original tracking issue for the same reason, but I listed those lower because the comment didn't get any reactions)

@quadruple-output
Copy link

quadruple-output commented Apr 4, 2021

I usually prefer starting a statement with the variable which is being assigned, checked, or iterated on. This can increase readability, once you get used to it.

I have to say that I even prefer
players.iter().for_each(|p| p.take_turn) (I would like to get rid of .iter(), though)
over
for p in players { p.take_turn } (here, readers have to mentally skip three symbols (for p in) until they reach the point where it is getting interessing (players...))

Therefore, the following combination of methods for bool would be my favorite solution:

  • bool.if_true( || foo() ) (alternative to, or even obsoleting then)
  • bool.if_false( || bar() )
  • and maybe: bool.if_else( || foo(), || bar() )
  • finally, this is my contribution to this issue: bool.to_option( expr )

(disclaimer: I have my roots in Smalltalk, where if_true, and if_false is the only way to write an IF-condition.)

@jhpratt
Copy link
Member

jhpratt commented Apr 5, 2021

@thedrow that wouldn't be unambiguous in all cases. What if you wanted to obtain Some(closure)?

@jhpratt
Copy link
Member

jhpratt commented Apr 6, 2021

If I understand what you said correctly, you were suggesting that bool.then(val) would be permitted via specialization, yes? If this is the case, what if val was a closure? This would be equivalent to bool.then_some(|| || ret) (note that the closure returns a closure). From my understanding of what you said, this would be replaced by bool.then(|| ret), which is not the same thing.

@ExpHP
Copy link
Contributor

ExpHP commented May 3, 2021

bool::then_some still links to the old tracking issue in the documentation...

@dhardy
Copy link
Contributor

dhardy commented Jul 18, 2021

  • Create a specialization for plain values (which would be converted to Option) and another one for callables.

I think that dictating the behavior according to the type will let us eat the cake and keep it.

What do you guys think? I think it's the most ergonomic option as of yet.
It's very common to do these type of things in C++ for example.

This requires replacing

pub fn then<T, F>(self, f: F) -> Option<T> where
    F: FnOnce() -> T, 

with

pub fn then<T, R>(self, f: R) -> Option<T> where
    R: ResolveTo<T>,

But with impls of ResolveTo<T> for both T and FnOnce() -> T this breaks type inference.

This is a breaking change and also in my opinion undesirable (explicit typing is often easier to work with).


I honestly am in favour of bool::then_some; many arguments here are more criticisms of bool::then which has already been stabilised.

@SoniEx2
Copy link
Contributor

SoniEx2 commented Jul 18, 2021

It would be possible with overlapping traits.

@dhardy
Copy link
Contributor

dhardy commented Jul 18, 2021

@SoniEx2 if you are referring to https://rust-lang.github.io/rfcs/1268-allow-overlapping-impls-on-marker-traits.html, this is not applicable since the ResolveTo used above is not a marker trait:

pub trait ResolveTo<T> {
    fn resolve(self) -> T;
}

@SoniEx2
Copy link
Contributor

SoniEx2 commented Jul 18, 2021

nah, we mean our rejected proposal for marking traits as overlapping, and having both preferred and overlapping impls of those traits. you can then explicitly choose impls in the overlapping case, or default to the preferred impls only.

you'd write

// uhh not sure how you'd make an overlapping trait "sealed".
pub overlapping trait ResolveTo<T> {
  fn resolve(self) -> T;
}

overlapping impl<T> ResolveTo<T> for T {
  fn resolve(self) -> T {
    self
  }
}

impl<T, F> ResolveTo<T> for F where F: FnOnce() -> T {
  fn resolve(self) -> T {
    self()
  }
}

and then calls would be one of these:

b.then(|| bar): Option<Bar>;
b.then::<fn() -> Bar>(|| bar): Option<Bar>;
b.then::<_: (overlapping impl<T> ResolveTo<T> for T)>(|| bar): Option<fn() -> Bar>;
b.then::<_: (overlapping impl<T> ResolveTo<T> for T)>(bar): Option<Bar>; // with future possibility of not requiring the <_: (overlapping impl)> syntax if Bar just so happens to not implement FnOnce() -> T. 

@SoniEx2
Copy link
Contributor

SoniEx2 commented Feb 9, 2022

so python has this convention of list.sorted() and sort(list). rust has option.filter(closure) how about bool.filtered(option)?

(we know this is mostly silly but, eh, might as well put it out there y'know?)

@ranile
Copy link
Contributor

ranile commented Feb 17, 2022

FWIW, I discovered this method today because I did bool.th and pulled up intellisense. At first glance, the name was confusing. then and then_some both return Some(_).

I guess it's too late to rename the methods. I can't think of a name which doesn't involve renaming then. and(Some(_))/and_then(||Some(_)) would follow the convention of a word followed by "then"/"or" which denotes that it takes a closure (similar to unwrap_or/unwrap_or_else)

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. 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 Feb 19, 2022
@rfcbot
Copy link

rfcbot commented Feb 19, 2022

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@B-Teague
Copy link

So, I ran into this issue when refactoring some code from the rust book as a learning exercise:

pub fn new(value: i32) -> Guess {
        if value >= 1 && value <= 100 {
           Guess { value }
        } else {
           panic!("Guess value must be between 1 and 100, but received {}", value)
       }
    }

pub fn new(value: i32) -> Guess {
        match value >= 1 && value <= 100 {
           true => Guess { value },
           false => panic!("Guess value must be between 1 and 100, but received {}", value),
    }

pub fn new(value: i32) -> Guess {
        (value >= 1 && value <= 100).then_some(Guess { value }).expect("Guess value must be between 1 and 100.")
    }

Only downside was expect doesn't accept multiple arguments for the panic! macro. (yes, I know I can use format!().as_str())

@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Mar 3, 2022
@SoniEx2
Copy link
Contributor

SoniEx2 commented Apr 22, 2022

We'd like to suggest renaming then_some to something that allows this code to be clear to the reader:

// previously
if x {
  y()
} else {
  y();
  z
}

// now
x.then_some({
  y() // NOTE: y() eagerly evaluated! do not use .then()!
}).unwrap_or(z)

Even if it's not named to be similar to then. Tho we're drawing a blank on what such name could/should be.

(This is actually real code! It's really useful for filtering return values of callbacks!)

Well, the real code looks more like this:

let suppress_eat = ...;
...
(!suppress_eat).then_some({
  cb(&mut ph, word)
}).unwrap_or(EAT_NONE)

so uh... maybe (!suppress_eat).with(cb(...)).unwrap_or(EAT_NONE)?

JohnTitor added a commit to JohnTitor/rust that referenced this issue May 5, 2022
…=m-ou-se

Stabilize `bool::then_some`

FCP completed in rust-lang#80967
@Xiretza
Copy link
Contributor

Xiretza commented May 14, 2022

Looks like this got stabilized in #96628, can that be added to the OP under "Stabilization PR"?

@m-ou-se m-ou-se closed this as completed May 16, 2022
workingjubilee pushed a commit to tcdi/postgrestd that referenced this issue Sep 15, 2022
RickCao-2017 added a commit to RickCao-2017/RustPython that referenced this issue Oct 16, 2022
…e library feature 'bool_to_option

I have found a compile error in my windows serve 2020. This is it's details:

error[E0658]: use of unstable library feature 'bool_to_option'
  --> common\src\refcount.rs:47:63
   |
47 |             .fetch_update(AcqRel, Acquire, |prev| (prev != 0).then_some(prev + 1))
   |                                                               ^^^^^^^^^
   |
   = note: see issue #80967 <rust-lang/rust#80967> for more information

For more information about this error, try `rustc --explain E0658`.
error: could not compile `rustpython-common` due to previous error
warning: build failed, waiting for other jobs to finish...
error: build failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests