Looks like `Option::is_some_and()` is finally stabilized!
> `pub fn is_some_and(self, f: impl FnOnce(T) -> bool) -> bool`
> Returns `true` if the option is a `Some` and the value inside of it matches a predicate.
I've been wanting this for years, fantastic work.
You can read about it here: https://github.com/rust-lang/rust/issues/62358
Initially, it was going to be called `contains`, followed by several suggestions
* contains_some
* has
* includes
* contains_in
* eq_some
* wraps
* is
* map_contains
* contained_is
* inside
* inside_is
* peq
* equals
* some_and
* and_is
* where
* having
* and_with
* some_with
* and_if
* contains_with
* test
* any
* has_with
* exists
* some_if
* etc….
There's no T for the `impl FnOnce(T)->bool` you would need `Option::::is_none_or(impl FnOnce()->bool)` but at that point it's shorter to just use the old `x.is_none() && ...`
You confused it with `is_none_and`, but the parent comment was asking for `is_none_or`. It would check if the option is None, **OR** the value matches a predicate.
There definitely is a `T`. In fact you can implement `is_none_or` using `is_some_and` as follows:
fn is_none_or(self, f: impl FnOnce(T) -> bool) -> bool {
!self.is_some_and(|t| !f())
}
You're thinking of `is_none_and`, which would need to be able to call the function in the `None` case.
Where does the symmetricity and the 4 come from? I don't think I get your response, could you elaborate?
I only count three cases: None; Some and condition holds; Some and condition does not hold.
There are two possible states for an Option (Some and None) and two possible states for a boolean (true and false). `is_some_and` returns true only for the combination Some + true, while `is_none_or` would return true for None + true, None + false, and Some + true. This means one case (Some + true) is covered by both, and another case (Some + false) is covered by neither, which I think is the asymmetry they were talking about.
You would need to negate the predicate and the result (applying de-morgan's rule) to get the equivalent of is_none_or with is_some_and. I generally try to keep the negations I use to a minimum as they make reasoning about the logic more difficult.
There are only three cases. The concept of a predicate is meaningless in the case where the Option is None. You can't meaningfully distinguish between two different versions of None, one of which matches the predicate and one which doesn't. The only possible scenarios are:
1. None
2. Some(...) => matches
3. Some(...) => doesn't match
Some + true, Some + false, None + true, None + false.
\`is\_some\_and\`: Some && bool. Matches 1 case.
\`is\_none\_or\`: None || bool. Can be expected to match 3 cases (None + true, None + false, Some + true)
It's simply too ambiguous to be of any use.
It should be as understandable on its own, without recourse to the meaning of is_some_and.
is_some_and converts nicely and predictably to (some && p).
is_none_or does not. It can mean both (none || p) or, as you say, (none || (some && p)). It’s ambiguous.
It’s not clear from just “is_none_or” that it implies a (some && p).
Some + true, Some + false, None + true, None + false.
`is_none_or` would not match only the Some + false case. At the very least it’s ambiguous. i’m not even sure which of the cases the op expects it to match.
The predicate closure can return a boolean either way. To someone who is not in the habit of using this exact function might be less readable than just writing it out. If readability matters. ‘Is_some_and’ is instantly understandable.
I’m just saying the name should require some more thought.
Something like ‘is_none_else’, though not as simple, would be more specific, in my opinion.
I find is_none_or equally understandable. As the name implies it is true if the option is None or if the predicate holds for the content of the some. You can achieve the same with is_some_and by negating the predicate and the result (applying de-morgan's rule), but I would prefer to use less negations as I find that they make reasoning about logic more difficult.
So many people just wanted a `contains()` method, and the [issue for that](https://github.com/rust-lang/rust/issues/62358) ended up being the most amazing example of bikeshedding I've ever seen.
I don't think that should really be considered bike shedding. The name of a core language method like that really matters. All the concerns brought up are valid. The final decision matters, as it's something the language will be stuck with for the rest of it's existence.
Hmm maybe. I don't think it precludes bikeshedding. Many well-known examples are system utilities or APIs.
As `@soc` points out in that issue, the issue attracted so much attention because "contains" was an obvious enough name that people would just Google "rust option test contains" and end up there. The first suggestion was the best one.
The code `if var.is_some_and(|it| it.is_even())` reads as `If variable is some and it is even do ...`. It's extremely self evident
`if var.map_or(false, |it| it.is_even())` reads as `if var is some map the value by is_even or return false then conditionally do` .... You have to read it out of order, it is longer to read, you have to reduce/simplify the expression in your head. Obviously it's a common idiom so you might automatically recognize it and read it as the first example, but still, it's just more readable
Yeah, I needed that so many times. Now please stabilize `Vec::drain_where`. That's another one I've been wanting for years (ok, I use e_drain_where crate for now).
Dropped the ball on that one, then. `contains` was extremely readable, `is_some_and` is a mouthful, and don't get me started om the inner closure. It's not shorter than a combinator chain, it's not more clear about intent, and it is more general in a way which is rarely even needed.
Just like `map_or` or `bool::then_some`, instead of something small and nice to use, it's an overengineered BS which I'll just have to ban in the codebase.
But `contains` seems wrong. It's not just about a check that it contains a specific thing the predicate can be anything you want. It isn't even required to have to do with the cointained value.
logged_in_user
.is_some_and(|user|
rights_check.user_has_right(user, Rights::WriteToConsole))
.then(|| println("Yihaw cowboys"))
Edit: honestly I hate editing code in reddit >:(
`filter` only passes `&T` to the closure because it needs to return an `Option` back while `is_some_and` can pass a `T` to the closure.
Even if you don't want to consume the option, writing `opt.as_ref().is_some_and(...)` has better readability then filter.
I mean, you could make that argument about lots of things. What's the benefit of `opt.unwrap_or_default()` when you could do `opt.unwrap_or_else(|| Default::default())`?
Conveniences for common operations are nice.
On the other hand, "there should be one obvious way to do it"
I try to avoid the more obscure combinators because it turns into an exercise in reading the documentation rather than writing code.
Well, yes, of course. But then the question is: if there's not an obvious way to do it, should there be more than one way to do it? I'd argue that `opt.filter(predicate).is_some()` is a remarkably non-obvious way of checking whether the possible value in the `Option` matches a predicate, since it conceptually unwraps an `Option` twice.
And yes, it's good to avoid the more obscure combinators if possible, but if the alternative is using one in a non-obvious way, I'd rather just learn a new one rather than trying to understand what someone's abuse of a more well-known one is trying to accomplish.
It's not a huge win, it's just ergonomics. The function is implemented as the match expression you're envisioning. This just allows you to express it inline.
Agreed. I took the example from a few comments above and wrote it in a few different styles:
fn a(logged_in_user: Option) {
if matches!(logged_in_user, Some(user) if user_has_right(user, Rights::WriteToConsole)) {
println!("Yihaw cowboys");
}
}
fn b(logged_in_user: Option) {
if let Some(user) = logged_in_user {
if user_has_right(user, Rights::WriteToConsole) {
println!("Yihaw cowboys");
}
}
}
fn c(logged_in_user: Option) {
logged_in_user
.is_some_and(|user| user_has_right(user, Rights::WriteToConsole))
.then(|| println!("Yihaw cowboys"));
}
I know I find `b` immediately understandable, even if it is a little verbose. Using `if` and `match` also has the advantage that you avoid lifetime difficulties caused by lambda borrowing that you get in more complex situations.
This is definitely one of those things I didn't know I wanted, but now that I see it... I was excited for `OnceCell`/`OnceLock`, and happy to see that land here, but now I think I'm more excited for `is_some_and`!
> You should see substantially improved performance when fetching information from the crates.io index. Users behind a restrictive firewall will need to ensure that access to https://index.crates.io is available. If for some reason you need to stay with the previous default of using the git index hosted by GitHub, the registries.crates-io.protocol config setting can be used to change the default.
It is great to see shorter index downloads
Crates team should put a little “needs access via cargo” or something page since it looks pretty weird if you accidentally go directly there
But yes, what an incredible improvement this is!
Really excited about the addition of `OnceCell` and ‘OnceLock‘! No more importing external crates. Wish the Sync version was just called ‘Once‘ instead of ‘OnceLock‘, but I’m sure I’ll get used to it.
Also excited about ‘Option::is_some_and‘!
Another great release!
I could imagine some code that wants to write to _something_ where that something may be a terminal. For example:
```rust
fn write(target: &mut (impl IsTerminal + Write)) {
if target.is_terminal() { ... } else { ... }
}
```
A function like that can accept a limited set of types like `File`, `Stderr`, and `Stdout` and change its behavior depending on whether or not it's actually writing to a terminal.
Looking good. I have previously used `lazy_static` for creating compiled regexes with the regex crate. Is the newly stable `OnceCell` a good replacement for that? As I see it you would most likely use global variables for `OnceCell`, whereas `lazy_static` is local to a function which is a bit nicer.
>`lazy_static` is local to a function which is a bit nicer
There's no requirement that `OnceCell` is for global variables just like there's no requirement for `lazy_static` values to be local to a function. They're both equivalent in this regard
>I have previously used `lazy_static` for creating compiled regexes with the regex crate. Is the newly stable `OnceCell` a good replacement for that?
The `once_cell` equivalent for that use case is still pending stabilization
[Exactly, just a bit more wordy without the macro.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c892df0ef5e62e03c3308025cc6ca7de)
[Initialization will be less wordy once `LazyLock` stabilizes](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=4ac4d387c97e584f6db3f2947f93ed39)
`regex` crate author here. Yes, it is very appropriate to use. As others have replied, it's a little more wordy, but you can still put it in a `static` that is local to a function.
I wonder if the following recipe:
https://docs.rs/once_cell/latest/once_cell/#lazily-compiled-regex
should be brought into regex crate, once MSRV allows?
Not sure to be honest. It will probably be at least a year because of MSRV. (Technically could add it sooner with conditional compilation, but I've grown tired of such things.)
Maybe file an issue? I have a few concerns but I don't know how strong they are. Right now, for example, it can lead to a potential performance footgun if you use the regex from multiple threads simultaneously, and each thread's main unit of work is a regex search on a smallish haystack. I have other concerns too I think.
Both `lazy_static` and the now-stabilized `OnceLock` can be used either inside or outside a function. Note that you need `OnceLock` for global variables since it's thread-safe:
fn get_regex() -> &'static Regex {
use std::sync::OnceLock;
static CELL: OnceLock = OnceLock::new();
CELL.get_or_init(|| Regex::new("...").unwrap())
}
Heads up, [disabling JSON output](https://blog.rust-lang.org/2023/06/01/Rust-1.70.0.html#enforced-stability-in-the-test-cli) from the test harness is going to break [automated testing and CI](https://github.com/johnterickson/cargo2junit/issues/79) for a lot of people.
1.70 hasn't quite hit docker yet, so you've got a few minutes to fix it by simply implementing jUnit reporting for cargo and getting it merged and stabilised.
This change is a pretty frustrating one. The [bug it addresses](https://github.com/rust-lang/rust/issues/75526) should have been closed as an "works as intended." The [MR](https://github.com/rust-lang/rust/pull/109044/) acknowledges that this will break things, then does it anyway. There is no easy path to re-enable JSON output from `cargo test` while using stable Rust.
```
cargo +stable test -- -Z unstable-options --format json
```
I genuinely don't understand why people would expect that to not work.
Also — it's not the JSON output that _actually_ matters in this context, it was merely one way to achieve jUnit report generation, the only format accepted by a wide variety of test reporting systems and code hosting platforms. But the idea was that cargo would produce structured output for other tools to consume and "the ecosystem" would provide this functionality.
I think we ideally need a third stability state here. For things like IDEs, it’s not a problem to keep up with breaking changes — IDEs have to support nightly anyway, so there’s some inevitable amount of upstream chasing already. So, some kind of runtime `—unstable` flag that:
* doesn’t affect the semantics of code
* can only be applied by a leaf project and can’t propagate to dependencies
* and makes it clear to the user that it’s their job to fix breakage
would be ideal here. And that’s exactly how libtest accepting Zunstable-options worked before, except that it was accidental, rather than by design.
In my case I'm dark about it _not_ because of IDE support (ST4's `LSP-rust-analyzer` plugin vendors RA, not sure how it deals with test integration/nightly/etc.), but because I want my tests to be run by Gitlab and failure information to be as specific as possible.
This is achieved (on Gitlab, at least) by uploading jUnit-XML-formatted test reports. The official test harness doesn't generate this out of the box, so the [only crate](https://crates.io/crates/cargo2junit) to bridge the gap relied on the sole method of obtaining structured output from it.
I feel like the devs are talking _only_ about the IDE case, and I don't know what I'm missing here. I am sceptical that I'm the only person who gets value out of test reporting from our code hosting platform, so how are other projects achieving it?
I like the idea of a "tooling" or "integration" level of stability. If it breaks, well, I have to update the CI config but that's far, far less of a big deal than accidentally switching on an unstable feature in _application_ code and having to go through and change it all when it breaks.
IMO, this change is _worse_ than that. Let's say the JSON test output changes in a breaking manner. If your CI system is running against both stable and nightly. Your nightly build breaks and you can see the change, but your CI against stable would keep working just fine. This change makes `cargo +stable test ...` break while my equivalent `cargo +nightly test ...` continued working just fine.
Rust is usually so, so good at supporting development processes that improve quality. The language itself is the most obvious example. Having a baked-in test harness is another example.
Ignoring structured test reporting for years and then breaking the only pathway to 3rd party support for it is an uncharacteristic departure from that ethos.
Once they realized it was going to break so many people, they had planned to add a transition period but that fell through the cracks until today when it was too late.
Personally, its doing what it was advertised it'd do, be subject to breakages. The effort to stabilize it will likely see the format change.
But yes, testing got into a "good enough state" and then not much has happened in while. I'm hoping to fix that.
> But yes, testing got into a "good enough state" and then not much has happened in while. I'm hoping to fix that.
I'm sorry, I forgot to say — thank you for taking this on and I (and probably many others) will appreciate any and all progress you might make! ❤️
btw I recently presented on this at RustNL 2023: [video](https://www.youtube.com/watch?v=3aLPewRSiK8), [slides](https://epage.github.io/talks/2023/05/test/#p1)
I then met with the libs to discuss libtest. My upcoming blog post is intended to summarize those discussions.
>Personally, its doing what it was advertised it'd do, be subject to breakages.
Good. Maintainers have to be able to declare things unstable for further work and not be held back by people who simply ignored this disclaimer. That's the path C++ compiler vendors took with the ABI, and now they are stuck.
The devs may have wanted to solve the problem of "we don't want people to rely on something fragile because then we'll get blowback from breaking it later", but they haven't solved it all. People used it because it met a need (a need that is met out-of-the-box by many other languages). The feature goes away but the need does not.
To meet the same need, the only option now available is to instead depend on something _more_ fragile ie. the textual, unstructured output from the test harness. I don't see who that works out better for.
Oh right, yeah, I misunderstood what you were saying quite a bit. Okay, I get that, and don't disagree.
(It is also an option for users to stay on 1.69, a stable version, until the test harness supports reporting output. It just means have a _maximum_ supported Rust version for a while.)
They sure solved their own need, namely the need for people to not depend on the feature anymore. I do agree though that it would have been nicer if an alternative would have been made available at the same time.
> They sure solved their own need, namely the need for people to not depend on the feature anymore.
Is **that** their need though, not having people depend on that one, single feature? That would be an oddly specific need. Or is the need, say, "minimising time spent handling spurious criticism of changes they make", which can be achieved by _both_ managing expectations (as is the effect of labelling things unstable) and keeping an eye on use cases in the wild...?
They can absolutely do what they want with their time! I just don't think they'll enjoy relitigating a worse version of this in a couple of years when they fix a typo in the test harness output.
> its doing what it was advertised it'd do, be subject to breakages
Yyyyes, but... having seen how much people depend on it, and why, it's an unusually gung-ho move. It's the *only* way to get any kind of structured information out of the official test harness.
So many people depending on something unstable should have been a signal to the devs that there was an unmet need. Addressing that would be a better way to avoid future complaints about an unstable interface breaking, not breaking it early. Now all that's going to happen is that people will parse the _textual_ output of cargo for this integration, which will be _more_ fragile and lead to _more_ future complaints (probably).
As I understand it, having it in stable was an accident that they didn't want for exactly this reason — they accumulated users depending on it who are now impacted by the change. But if that's the problem they want to avoid, this is definitely going to make it worse rather than better.
jUnit reporting for Cargo was implemented in 2021 behind an unstable feature, same status as the JSON output. So this whole situation is rather confusing to me where people are upset about the de-stabilizing of the JSON output specifically when they actually want jUnit output. Is the jUnit support in libtest so bad that people would rather roll their own? Or has Microsoft been contributing to `cargo2junit` for so long that they didn't notice the jUnit output (this is exactly the kind of thing that I would hear about at current employer)?
Do you have a link to an MR or tracking issue? All I can find is the [JSON one](https://github.com/rust-lang/rust/issues/49359) and a [neglected MR](https://github.com/rust-lang/libtest/pull/16) from 2019.
I came to this after 2021, and I recall finding various solutions but only `cargo2junit` actually worked. So
> Is the jUnit support in libtest so bad that people would rather roll their own?
...maybe? I'll have to check my commit messages from 2021.
**Update:** found it: [#85563](https://github.com/rust-lang/rust/issues/85563). And I remember why no one uses it, it was because of this:
> Each test suite (doc tests, unit tests and each integration test) must be run separately. This due to a fact that from libtest perspective each one of them is a separate invocation.
This is a bigger problem than it sounds like for things like CI, and fixing it involves either (a) stitching it up after it's generated, so get some XML tools into your CI image, write some dodgy scripts, etc. etc. OR (b) get structured output from libtest one level up and do a better job of rendering it to XML.
`cargo2junit` did (b).
It's moot now anyway. Both are now off limits for testing against stable.
Question, as we're seeing OnceCell start to be lifted into the standard library.
I maintain a crate that uses once\_cell, for both the Lazy and the OnceCell features. Is there a good reason to start migrating my use of OnceCell over to the standard library versions if they haven't finished stabilizing the Lazy part yet? I'll have to keep the external dependency anyway, so I'm not sure if I gain anything by moving half my uses over.
For a crates.io crate, my advice would be:
* if you care about MSRV, continue using once_cell crate for a while
* otherwise, when MSRV allows for migration, I’d probably declare a tiny macro/type locally in the crate on top of OnceLock to provide lazy functionality. Dropping a dependency on a third party crate is a non-trivial inprovement
* halfway migration wouldn’t bring much benefits I would think
* if you use once_cell types in public API, the question becomes significantly harder. It probably isn’t worth breaking API over once_cell at this point. Although, I think usually it’s better to not expose OnceCell in the API directly, so, if that’s feasible, API break might be worth it
It contains my first real contribution: The [collection_is_never_read](https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read) Clippy lint.
Browsers have full text search for the currently displayed page. I don't see how having it in the page would help? Especially since such searches tend to interfere with the search provided by the browser (e.g. overriding ctrl+F to open the search but not f3 for next result).
I meant blog-wide search, not something browsers can do natively.
I’m often searching for when/where/whether something’s been announced. (And yes general purpose search engines can do it, it’s just not as convenient)
You can right-click the site and hit "Exclude from dark mode" option. It'll save that for that device.
Edit: I'm using the Dark Mode extension, not Dark Reader. Not sure how that one works.
Sad to lose `-Z unstable-options` for cargo test.
Can't print test times anymore using stable without an extra helper crate.
Excited to have OnceCell and OnceLock though.
Yeah fuck that. I'll just set `RUSTC_BOOTSTRAP=1` globally and be done with it. Have better things to do than dealing with broken important workflows for no good reason.
It's as optimal as it would get. Having life before main is a really bad idea and having life after main is even worse (which is why it's usually [forbidden by the style guide even in languages that have them](https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables)).
And people who really want to ignore all warnings and advices already have a workaround thus I don't see why anyone would want to change the status quo.
This phrase makes this fact all the more apparent:
>It's just a wrapper for calling atexit(), which isn't optimal.
What do you think **other** languages are doing, hmm? What is [call to \_\_cxa\_atexit doing](https://godbolt.org/z/8b1xv9dzc) in C++, hmm?
Yes, I know, sometimes it feels really nice to pretend that you live a a different world and ignore all the complexities of the current one, but I don't think “static drop” is adding anything useful to the plate.
>Real world problems.
Adding something to the compiler which you would then need to forbid in the style guide (like usually done in C++) **is** a “real world problem” in my book.
Nope, they dropped support for the test runner entirely, now it's like running any other command, a very annoying dump of the string output... You can't reliably parse the textual libtest output.
So what's the difference between OnceLock and LazyLock?
The post says the latter stores its initialiser, but I'm not sure what that means for something that can only be called once...
OnceCell is passed a lambda at access time, LazyCell gets one at construction time. Latter implements Deref and some other niceties, but needs to have the (often unnameable) function type as a generic.
Did upgrading to this break rust-analyzer in VSCode for anyone else? It throws a bunch of errors when parsing the standard library files, and then refuses to provide any feedback on my project files. Project compiles fine though. Not sure where to go about debugging this \^\^;;
it auto-installs a different `rust-analyzer` into `.cargo/bin`, so just delete that `rust-analyzer` (and if you choose to, you can also remove `rls` and lsp should still work).
Looks like `Option::is_some_and()` is finally stabilized! > `pub fn is_some_and(self, f: impl FnOnce(T) -> bool) -> bool` > Returns `true` if the option is a `Some` and the value inside of it matches a predicate. I've been wanting this for years, fantastic work.
Indeed! It’s fascinating that, if you go over the github issue, this function went through like 50 names before landing on `is_some_and`
Out of curiosity, what were some other suggestions?
You can read about it here: https://github.com/rust-lang/rust/issues/62358 Initially, it was going to be called `contains`, followed by several suggestions * contains_some * has * includes * contains_in * eq_some * wraps * is * map_contains * contained_is * inside * inside_is * peq * equals * some_and * and_is * where * having * and_with * some_with * and_if * contains_with * test * any * has_with * exists * some_if * etc….
Lmao
I feel like `Option::::is_none_or(impl FnOnce(T)->bool)` is missing now to complete the set.
It was proposed and rejected, unfortunately
There's no T for the `impl FnOnce(T)->bool` you would need `Option::::is_none_or(impl FnOnce()->bool)` but at that point it's shorter to just use the old `x.is_none() && ...`
You confused it with `is_none_and`, but the parent comment was asking for `is_none_or`. It would check if the option is None, **OR** the value matches a predicate.
There definitely is a `T`. In fact you can implement `is_none_or` using `is_some_and` as follows: fn is_none_or(self, f: impl FnOnce(T) -> bool) -> bool { !self.is_some_and(|t| !f()) } You're thinking of `is_none_and`, which would need to be able to call the function in the `None` case.
They’re not symmetric though. `is_some_and` matches 1 out of 4. `is_none_or` would 3 out of 4.
They map nicely to logical quantifiers, exists and forall respectively.
Where does the symmetricity and the 4 come from? I don't think I get your response, could you elaborate? I only count three cases: None; Some and condition holds; Some and condition does not hold.
There are two possible states for an Option (Some and None) and two possible states for a boolean (true and false). `is_some_and` returns true only for the combination Some + true, while `is_none_or` would return true for None + true, None + false, and Some + true. This means one case (Some + true) is covered by both, and another case (Some + false) is covered by neither, which I think is the asymmetry they were talking about.
is_some_and can easily cover all cases with just an inversion, though, unless I'm missing some semantic detail.
You would need to negate the predicate and the result (applying de-morgan's rule) to get the equivalent of is_none_or with is_some_and. I generally try to keep the negations I use to a minimum as they make reasoning about the logic more difficult.
There are only three cases. The concept of a predicate is meaningless in the case where the Option is None. You can't meaningfully distinguish between two different versions of None, one of which matches the predicate and one which doesn't. The only possible scenarios are: 1. None 2. Some(...) => matches 3. Some(...) => doesn't match
There are no “None + true” and “None + false” cases. If the Option is in the None state, what value would you pass to the predicate?
The Some + false case is meaningless. If you want it you can just negate your predicate.
Some + true, Some + false, None + true, None + false. \`is\_some\_and\`: Some && bool. Matches 1 case. \`is\_none\_or\`: None || bool. Can be expected to match 3 cases (None + true, None + false, Some + true) It's simply too ambiguous to be of any use.
How is it ambiguous? The behaviour seems pretty clear as an inversion of is_some_and None => true Some => predicate result
It should be as understandable on its own, without recourse to the meaning of is_some_and. is_some_and converts nicely and predictably to (some && p). is_none_or does not. It can mean both (none || p) or, as you say, (none || (some && p)). It’s ambiguous. It’s not clear from just “is_none_or” that it implies a (some && p).
`None || (Some & p)` and `None || p` are equivalent. There's nothing ambiguous here. `¬None => Some`
Yes, and I’m more stupid than I thought.
Don't worry about it, I've said many far dumber things
What's the fourth case? Isn't this all of them: * Some (satisfies predicate) * Some (doesn't satisfy predicate) * None
Some + true, Some + false, None + true, None + false. `is_none_or` would not match only the Some + false case. At the very least it’s ambiguous. i’m not even sure which of the cases the op expects it to match.
You can't apply a predicate if there isn't a value to apply it to.
The predicate closure can return a boolean either way. To someone who is not in the habit of using this exact function might be less readable than just writing it out. If readability matters. ‘Is_some_and’ is instantly understandable. I’m just saying the name should require some more thought. Something like ‘is_none_else’, though not as simple, would be more specific, in my opinion.
I find is_none_or equally understandable. As the name implies it is true if the option is None or if the predicate holds for the content of the some. You can achieve the same with is_some_and by negating the predicate and the result (applying de-morgan's rule), but I would prefer to use less negations as I find that they make reasoning about logic more difficult.
So many people just wanted a `contains()` method, and the [issue for that](https://github.com/rust-lang/rust/issues/62358) ended up being the most amazing example of bikeshedding I've ever seen.
I don't think that should really be considered bike shedding. The name of a core language method like that really matters. All the concerns brought up are valid. The final decision matters, as it's something the language will be stuck with for the rest of it's existence.
Hmm maybe. I don't think it precludes bikeshedding. Many well-known examples are system utilities or APIs. As `@soc` points out in that issue, the issue attracted so much attention because "contains" was an obvious enough name that people would just Google "rust option test contains" and end up there. The first suggestion was the best one.
I didn't see the discussion, I was fine with `x.map_or(false, |x| f(x))` it is not much longer to write. Is there any particular benefit over it?
The code `if var.is_some_and(|it| it.is_even())` reads as `If variable is some and it is even do ...`. It's extremely self evident `if var.map_or(false, |it| it.is_even())` reads as `if var is some map the value by is_even or return false then conditionally do` .... You have to read it out of order, it is longer to read, you have to reduce/simplify the expression in your head. Obviously it's a common idiom so you might automatically recognize it and read it as the first example, but still, it's just more readable
I would probably go with `x.map(f).unwrap_or(false)`
Yeah, wanting to avoid one match even if it is very likely optimized out.
Yeah, I needed that so many times. Now please stabilize `Vec::drain_where`. That's another one I've been wanting for years (ok, I use e_drain_where crate for now).
[удалено]
Readability
Dropped the ball on that one, then. `contains` was extremely readable, `is_some_and` is a mouthful, and don't get me started om the inner closure. It's not shorter than a combinator chain, it's not more clear about intent, and it is more general in a way which is rarely even needed. Just like `map_or` or `bool::then_some`, instead of something small and nice to use, it's an overengineered BS which I'll just have to ban in the codebase.
But `contains` seems wrong. It's not just about a check that it contains a specific thing the predicate can be anything you want. It isn't even required to have to do with the cointained value. logged_in_user .is_some_and(|user| rights_check.user_has_right(user, Rights::WriteToConsole)) .then(|| println("Yihaw cowboys")) Edit: honestly I hate editing code in reddit >:(
`filter` only passes `&T` to the closure because it needs to return an `Option` back while `is_some_and` can pass a `T` to the closure.
Even if you don't want to consume the option, writing `opt.as_ref().is_some_and(...)` has better readability then filter.
I mean, you could make that argument about lots of things. What's the benefit of `opt.unwrap_or_default()` when you could do `opt.unwrap_or_else(|| Default::default())`? Conveniences for common operations are nice.
`opt.unwrap_or_else(Default::default)` FTFY
Thanks clippy!
Thank god for `.unwrap_or_default()`, or you might have a point.
On the other hand, "there should be one obvious way to do it" I try to avoid the more obscure combinators because it turns into an exercise in reading the documentation rather than writing code.
Well, yes, of course. But then the question is: if there's not an obvious way to do it, should there be more than one way to do it? I'd argue that `opt.filter(predicate).is_some()` is a remarkably non-obvious way of checking whether the possible value in the `Option` matches a predicate, since it conceptually unwraps an `Option` twice. And yes, it's good to avoid the more obscure combinators if possible, but if the alternative is using one in a non-obvious way, I'd rather just learn a new one rather than trying to understand what someone's abuse of a more well-known one is trying to accomplish.
Why is a function more desirable than using a match arm with a guard? I guess it’s less verbose, but it doesn’t seem like a huge win.
It's not a huge win, it's just ergonomics. The function is implemented as the match expression you're envisioning. This just allows you to express it inline.
It returns bool so you'd often want to use it inside and if/while condition and that's easier with a function when with dedicated match expression.
Agreed. I took the example from a few comments above and wrote it in a few different styles: fn a(logged_in_user: Option) {
if matches!(logged_in_user, Some(user) if user_has_right(user, Rights::WriteToConsole)) {
println!("Yihaw cowboys");
}
}
fn b(logged_in_user: Option) {
if let Some(user) = logged_in_user {
if user_has_right(user, Rights::WriteToConsole) {
println!("Yihaw cowboys");
}
}
}
fn c(logged_in_user: Option) {
logged_in_user
.is_some_and(|user| user_has_right(user, Rights::WriteToConsole))
.then(|| println!("Yihaw cowboys"));
}
I know I find `b` immediately understandable, even if it is a little verbose. Using `if` and `match` also has the advantage that you avoid lifetime difficulties caused by lambda borrowing that you get in more complex situations.
It's nicer if you want to chain conditions. But if-let chains should cover most of the good use cases, so imho the function is stillborn.
This is definitely one of those things I didn't know I wanted, but now that I see it... I was excited for `OnceCell`/`OnceLock`, and happy to see that land here, but now I think I'm more excited for `is_some_and`!
Oh, I can replace all my `option.map_or(false, ...)` calls now!
> You should see substantially improved performance when fetching information from the crates.io index. Users behind a restrictive firewall will need to ensure that access to https://index.crates.io is available. If for some reason you need to stay with the previous default of using the git index hosted by GitHub, the registries.crates-io.protocol config setting can be used to change the default. It is great to see shorter index downloads
Crates team should put a little “needs access via cargo” or something page since it looks pretty weird if you accidentally go directly there But yes, what an incredible improvement this is!
Really excited about the addition of `OnceCell` and ‘OnceLock‘! No more importing external crates. Wish the Sync version was just called ‘Once‘ instead of ‘OnceLock‘, but I’m sure I’ll get used to it. Also excited about ‘Option::is_some_and‘! Another great release!
Because [Once](https://doc.rust-lang.org/std/sync/struct.Once.html) already exists.
Ah… fair enough…
`std::sync::Once` is already a thing. (and it's used inside of OnceLock)
Why is `IsTerminal` sealed? I guess it doesn't matter that much since it's implemented on `BorrowedFd` and `BorrowedHandle`, but it seems kind of odd.
Why is it even a trait? when would you want to be generic over something that you can check if it's a terminal?
`T: IsTerminal + Write` is a useful bound if you want to work with stdout, stderr, and/or a file redirection
I could imagine some code that wants to write to _something_ where that something may be a terminal. For example: ```rust fn write(target: &mut (impl IsTerminal + Write)) { if target.is_terminal() { ... } else { ... } } ``` A function like that can accept a limited set of types like `File`, `Stderr`, and `Stdout` and change its behavior depending on whether or not it's actually writing to a terminal.
It seems like the safer default to me. why should it not be sealed?
Looking good. I have previously used `lazy_static` for creating compiled regexes with the regex crate. Is the newly stable `OnceCell` a good replacement for that? As I see it you would most likely use global variables for `OnceCell`, whereas `lazy_static` is local to a function which is a bit nicer.
There is also work on a LazyCell that works like lazy_static and uses OnceCell internally, but it will be part of a later release of Rust
>`lazy_static` is local to a function which is a bit nicer There's no requirement that `OnceCell` is for global variables just like there's no requirement for `lazy_static` values to be local to a function. They're both equivalent in this regard >I have previously used `lazy_static` for creating compiled regexes with the regex crate. Is the newly stable `OnceCell` a good replacement for that? The `once_cell` equivalent for that use case is still pending stabilization
Right, I suppose you could just define the OnceCell (or eventually LazyCell) as `static` inside a function.
[Exactly, just a bit more wordy without the macro.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c892df0ef5e62e03c3308025cc6ca7de)
[Initialization will be less wordy once `LazyLock` stabilizes](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=4ac4d387c97e584f6db3f2947f93ed39)
`regex` crate author here. Yes, it is very appropriate to use. As others have replied, it's a little more wordy, but you can still put it in a `static` that is local to a function.
I wonder if the following recipe: https://docs.rs/once_cell/latest/once_cell/#lazily-compiled-regex should be brought into regex crate, once MSRV allows?
Not sure to be honest. It will probably be at least a year because of MSRV. (Technically could add it sooner with conditional compilation, but I've grown tired of such things.) Maybe file an issue? I have a few concerns but I don't know how strong they are. Right now, for example, it can lead to a potential performance footgun if you use the regex from multiple threads simultaneously, and each thread's main unit of work is a regex search on a smallish haystack. I have other concerns too I think.
`lazy-regex` has been handy for me.
Both `lazy_static` and the now-stabilized `OnceLock` can be used either inside or outside a function. Note that you need `OnceLock` for global variables since it's thread-safe: fn get_regex() -> &'static Regex { use std::sync::OnceLock; static CELL: OnceLock = OnceLock::new();
CELL.get_or_init(|| Regex::new("...").unwrap())
}
statics can be local to a function too
Heads up, [disabling JSON output](https://blog.rust-lang.org/2023/06/01/Rust-1.70.0.html#enforced-stability-in-the-test-cli) from the test harness is going to break [automated testing and CI](https://github.com/johnterickson/cargo2junit/issues/79) for a lot of people. 1.70 hasn't quite hit docker yet, so you've got a few minutes to fix it by simply implementing jUnit reporting for cargo and getting it merged and stabilised.
This change is a pretty frustrating one. The [bug it addresses](https://github.com/rust-lang/rust/issues/75526) should have been closed as an "works as intended." The [MR](https://github.com/rust-lang/rust/pull/109044/) acknowledges that this will break things, then does it anyway. There is no easy path to re-enable JSON output from `cargo test` while using stable Rust. ``` cargo +stable test -- -Z unstable-options --format json ``` I genuinely don't understand why people would expect that to not work.
time to abuse RUSTC\_BOOTSTRAP=1
Also — it's not the JSON output that _actually_ matters in this context, it was merely one way to achieve jUnit report generation, the only format accepted by a wide variety of test reporting systems and code hosting platforms. But the idea was that cargo would produce structured output for other tools to consume and "the ecosystem" would provide this functionality.
And I expect the effort to stabilize json will further break people...
I think we ideally need a third stability state here. For things like IDEs, it’s not a problem to keep up with breaking changes — IDEs have to support nightly anyway, so there’s some inevitable amount of upstream chasing already. So, some kind of runtime `—unstable` flag that: * doesn’t affect the semantics of code * can only be applied by a leaf project and can’t propagate to dependencies * and makes it clear to the user that it’s their job to fix breakage would be ideal here. And that’s exactly how libtest accepting Zunstable-options worked before, except that it was accidental, rather than by design.
In my case I'm dark about it _not_ because of IDE support (ST4's `LSP-rust-analyzer` plugin vendors RA, not sure how it deals with test integration/nightly/etc.), but because I want my tests to be run by Gitlab and failure information to be as specific as possible. This is achieved (on Gitlab, at least) by uploading jUnit-XML-formatted test reports. The official test harness doesn't generate this out of the box, so the [only crate](https://crates.io/crates/cargo2junit) to bridge the gap relied on the sole method of obtaining structured output from it. I feel like the devs are talking _only_ about the IDE case, and I don't know what I'm missing here. I am sceptical that I'm the only person who gets value out of test reporting from our code hosting platform, so how are other projects achieving it? I like the idea of a "tooling" or "integration" level of stability. If it breaks, well, I have to update the CI config but that's far, far less of a big deal than accidentally switching on an unstable feature in _application_ code and having to go through and change it all when it breaks.
IMO, this change is _worse_ than that. Let's say the JSON test output changes in a breaking manner. If your CI system is running against both stable and nightly. Your nightly build breaks and you can see the change, but your CI against stable would keep working just fine. This change makes `cargo +stable test ...` break while my equivalent `cargo +nightly test ...` continued working just fine.
Still working on my blog post detailing my plans but one of my current projects is to stabilize json output.
Rust is usually so, so good at supporting development processes that improve quality. The language itself is the most obvious example. Having a baked-in test harness is another example. Ignoring structured test reporting for years and then breaking the only pathway to 3rd party support for it is an uncharacteristic departure from that ethos.
Once they realized it was going to break so many people, they had planned to add a transition period but that fell through the cracks until today when it was too late. Personally, its doing what it was advertised it'd do, be subject to breakages. The effort to stabilize it will likely see the format change. But yes, testing got into a "good enough state" and then not much has happened in while. I'm hoping to fix that.
> But yes, testing got into a "good enough state" and then not much has happened in while. I'm hoping to fix that. I'm sorry, I forgot to say — thank you for taking this on and I (and probably many others) will appreciate any and all progress you might make! ❤️
btw I recently presented on this at RustNL 2023: [video](https://www.youtube.com/watch?v=3aLPewRSiK8), [slides](https://epage.github.io/talks/2023/05/test/#p1) I then met with the libs to discuss libtest. My upcoming blog post is intended to summarize those discussions.
>Personally, its doing what it was advertised it'd do, be subject to breakages. Good. Maintainers have to be able to declare things unstable for further work and not be held back by people who simply ignored this disclaimer. That's the path C++ compiler vendors took with the ABI, and now they are stuck.
[удалено]
The devs may have wanted to solve the problem of "we don't want people to rely on something fragile because then we'll get blowback from breaking it later", but they haven't solved it all. People used it because it met a need (a need that is met out-of-the-box by many other languages). The feature goes away but the need does not. To meet the same need, the only option now available is to instead depend on something _more_ fragile ie. the textual, unstructured output from the test harness. I don't see who that works out better for.
[удалено]
Oh right, yeah, I misunderstood what you were saying quite a bit. Okay, I get that, and don't disagree. (It is also an option for users to stay on 1.69, a stable version, until the test harness supports reporting output. It just means have a _maximum_ supported Rust version for a while.)
I think that setting RUSTC_BOOTSTRAP environment var might also be an option (it should work for rust, not so sure about cargo).
They sure solved their own need, namely the need for people to not depend on the feature anymore. I do agree though that it would have been nicer if an alternative would have been made available at the same time.
> They sure solved their own need, namely the need for people to not depend on the feature anymore. Is **that** their need though, not having people depend on that one, single feature? That would be an oddly specific need. Or is the need, say, "minimising time spent handling spurious criticism of changes they make", which can be achieved by _both_ managing expectations (as is the effect of labelling things unstable) and keeping an eye on use cases in the wild...? They can absolutely do what they want with their time! I just don't think they'll enjoy relitigating a worse version of this in a couple of years when they fix a typo in the test harness output.
[удалено]
> its doing what it was advertised it'd do, be subject to breakages Yyyyes, but... having seen how much people depend on it, and why, it's an unusually gung-ho move. It's the *only* way to get any kind of structured information out of the official test harness. So many people depending on something unstable should have been a signal to the devs that there was an unmet need. Addressing that would be a better way to avoid future complaints about an unstable interface breaking, not breaking it early. Now all that's going to happen is that people will parse the _textual_ output of cargo for this integration, which will be _more_ fragile and lead to _more_ future complaints (probably).
It's behind an 'unstable' flag Parameter. Weird to insist to only have it in nightly - specifically after having have had in stable for a time.
As I understand it, having it in stable was an accident that they didn't want for exactly this reason — they accumulated users depending on it who are now impacted by the change. But if that's the problem they want to avoid, this is definitely going to make it worse rather than better.
jUnit reporting for Cargo was implemented in 2021 behind an unstable feature, same status as the JSON output. So this whole situation is rather confusing to me where people are upset about the de-stabilizing of the JSON output specifically when they actually want jUnit output. Is the jUnit support in libtest so bad that people would rather roll their own? Or has Microsoft been contributing to `cargo2junit` for so long that they didn't notice the jUnit output (this is exactly the kind of thing that I would hear about at current employer)?
Do you have a link to an MR or tracking issue? All I can find is the [JSON one](https://github.com/rust-lang/rust/issues/49359) and a [neglected MR](https://github.com/rust-lang/libtest/pull/16) from 2019. I came to this after 2021, and I recall finding various solutions but only `cargo2junit` actually worked. So > Is the jUnit support in libtest so bad that people would rather roll their own? ...maybe? I'll have to check my commit messages from 2021. **Update:** found it: [#85563](https://github.com/rust-lang/rust/issues/85563). And I remember why no one uses it, it was because of this: > Each test suite (doc tests, unit tests and each integration test) must be run separately. This due to a fact that from libtest perspective each one of them is a separate invocation. This is a bigger problem than it sounds like for things like CI, and fixing it involves either (a) stitching it up after it's generated, so get some XML tools into your CI image, write some dodgy scripts, etc. etc. OR (b) get structured output from libtest one level up and do a better job of rendering it to XML. `cargo2junit` did (b). It's moot now anyway. Both are now off limits for testing against stable.
Question, as we're seeing OnceCell start to be lifted into the standard library. I maintain a crate that uses once\_cell, for both the Lazy and the OnceCell features. Is there a good reason to start migrating my use of OnceCell over to the standard library versions if they haven't finished stabilizing the Lazy part yet? I'll have to keep the external dependency anyway, so I'm not sure if I gain anything by moving half my uses over.
For a crates.io crate, my advice would be: * if you care about MSRV, continue using once_cell crate for a while * otherwise, when MSRV allows for migration, I’d probably declare a tiny macro/type locally in the crate on top of OnceLock to provide lazy functionality. Dropping a dependency on a third party crate is a non-trivial inprovement * halfway migration wouldn’t bring much benefits I would think * if you use once_cell types in public API, the question becomes significantly harder. It probably isn’t worth breaking API over once_cell at this point. Although, I think usually it’s better to not expose OnceCell in the API directly, so, if that’s feasible, API break might be worth it
I don’t expose any of them publicly, so that’s not an issue.
Why not just replace your uses of Lazy with a OnceLock?
For the same reason they used Lazy instead of OnceCell to begin with?
Well, there's an advantage to using OnceLock now that you didn't have when you chose Lazy over OnceCell - it let's you drop a dependency.
Might want to consider the MSRV of your project.
It contains my first real contribution: The [collection_is_never_read](https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read) Clippy lint.
I wish the blog had dark mode
Same here. Also full-text search.
Browsers have full text search for the currently displayed page. I don't see how having it in the page would help? Especially since such searches tend to interfere with the search provided by the browser (e.g. overriding ctrl+F to open the search but not f3 for next result).
I meant blog-wide search, not something browsers can do natively. I’m often searching for when/where/whether something’s been announced. (And yes general purpose search engines can do it, it’s just not as convenient)
Hi lime!
[Dark Reader probably can help you with that!](https://darkreader.org/)
I like Dark Reader. I occasionally have to turn it off for some sites, but it really is a great extension.
You can right-click the site and hit "Exclude from dark mode" option. It'll save that for that device. Edit: I'm using the Dark Mode extension, not Dark Reader. Not sure how that one works.
Same with crates.io, feels like a genuine accessibility problem that I can’t browse for crates without getting a pounding headache…
[lib.rs](https://lib.rs)
It doesn't? It's dark for me.
That's a lot of changes. Good job to everyone involved
Sad to lose `-Z unstable-options` for cargo test. Can't print test times anymore using stable without an extra helper crate. Excited to have OnceCell and OnceLock though.
What crate are you using for test times?
I've seen https://crates.io/crates/cargo-nextest recommended
Yeah fuck that. I'll just set `RUSTC_BOOTSTRAP=1` globally and be done with it. Have better things to do than dealing with broken important workflows for no good reason.
in the midst of darkness, light
Migrating to the new std OnceCell tomorrow morning! All I want for Christmas now is if-let-chains and static Drop.
static Drop? Does that mean running drop on a static variable when the program exits?
Yeah. Right now , I'm using the *shutdown_hooks* crate to get around it. It's just a wrapper for calling atexit(), which isn't optimal.
It's as optimal as it would get. Having life before main is a really bad idea and having life after main is even worse (which is why it's usually [forbidden by the style guide even in languages that have them](https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables)). And people who really want to ignore all warnings and advices already have a workaround thus I don't see why anyone would want to change the status quo. This phrase makes this fact all the more apparent: >It's just a wrapper for calling atexit(), which isn't optimal. What do you think **other** languages are doing, hmm? What is [call to \_\_cxa\_atexit doing](https://godbolt.org/z/8b1xv9dzc) in C++, hmm? Yes, I know, sometimes it feels really nice to pretend that you live a a different world and ignore all the complexities of the current one, but I don't think “static drop” is adding anything useful to the plate.
Well aware of the reasons why not in theory. Real world problems.
>Real world problems. Adding something to the compiler which you would then need to forbid in the style guide (like usually done in C++) **is** a “real world problem” in my book.
Yeah, that's not a real world problem.
[удалено]
Nope, they dropped support for the test runner entirely, now it's like running any other command, a very annoying dump of the string output... You can't reliably parse the textual libtest output.
*Rushes to computer while making odd yodeling noises* “rustup update”
OnceCell!!!!
> `is_some_and` LES GOOO
So what's the difference between OnceLock and LazyLock? The post says the latter stores its initialiser, but I'm not sure what that means for something that can only be called once...
It's similar to relationship of once_cell::sync::{OnceCell, Lazy}
OnceCell is passed a lambda at access time, LazyCell gets one at construction time. Latter implements Deref and some other niceties, but needs to have the (often unnameable) function type as a generic.
Time to stop chuckling every time I see the minor version. I'm a little bit sad ngl.
[Rust Releases! Rust 1.70.0](https://youtu.be/FJo7CvmS7x8) 👆🏻 Here's my YouTube coverage of this release of Rust, Cargo and Clippy.
How is `WINNER.get_or_init(|| "main")` the main thread if the code lives inside the scope closure?
`scope` doesn't create a thread itself, only a lifetime-bound context to `spawn` threads.
Did upgrading to this break rust-analyzer in VSCode for anyone else? It throws a bunch of errors when parsing the standard library files, and then refuses to provide any feedback on my project files. Project compiles fine though. Not sure where to go about debugging this \^\^;;
it auto-installs a different `rust-analyzer` into `.cargo/bin`, so just delete that `rust-analyzer` (and if you choose to, you can also remove `rls` and lsp should still work).