-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Allow negation of if let
#2616
Comments
I don't understand what's "awkward" in a block. Furthermore, I don't see any good syntax for realizing this. Certainly, both proposed ones ( |
We've seen numerous proposals for this, so maybe review them before attempting to push any serious discussion. If the variants contain data then you normally require said data, but you cannot bind with a non-match, so
Also, one can always do very fancy comparisons like:
I believe the only "missing" syntax around bindings is tightly related to refinement types, which require a major type system extension. In my opinion, we should first understand more what design by contract and formal verification might look like in Rust because formal verification is currently the major use case for refinement types. Assuming no problems, there is a lovely syntax in which some
In this |
I looked around for past proposals regarding expanding the @burdges Thanks for sharing that info, I have some reading to do. But with regards to
No, those are exactly what I would like, except they're not available for enum variants, i.e. for One problem with the existing |
Oh, I think that can be fixed quite easily. They could be |
I'd suggest
That said, if you have more than a couple variants then In fact, you'd often want the or/
|
I made a PoC implementation of the "generate |
@H2CO3 This already exists on crates.io: https://crates.io/crates/derive_is_enum_variant and there's likely more complex variants too involving prisms/lenses and whatnot. It seems to me however that generating a bunch of Fortunately, given or-patterns (rust-lang/rust#54883) let chains (rust-lang/rust#53667), and a trivial macro defined like so: macro_rules! is { ($(x:tt)+) => { if $(x)+ { true } else { false } } } we can write: is!(let Some(E::Bar(x)) && x.some_condition() && let Other::Stuff = foo(x)); and the simpler version of this is: is!(let E::Bar(..) = something)
// instead of: something.is_bar() |
@Centril I'm glad it already exists in a production-ready version. Then OP can just use it without needing to wait for an implementation.
What do you exactly mean by this? AFAIK there aren't many kinds of enum variants in Rust. There are only unit, tuple, and struct variants. I'm unaware of special prism and/or lens support in Rust that would lead to the existence of other kinds of variants.
I beg to differ. Since they are generated automatically, there's not much the user has to do… moreover the generated functions are trivial, there are
Those are very nice, and seem fairly powerful. I would be glad if we could indeed reuse these two new mechanisms plus macros in order to avoid growing the language even more. |
🎉
I found this a while back: https://docs.rs/refraction/0.1.2/refraction/ and it seemed like an interesting experiment; other solutions might be to generate
In a small crate I don't think it would pay off to use a derive macro like this; just compiling
Sure; this is indeed nice; but imo, it seems natural and useful to think of |
I'm for this. It arguably reduces the semantic complexity of the language, and improves consistency, especially with let-chaining coming in. Has @rust-lang/libs thought of bundling the |
See also #1303. |
Opposed. Not a big enough use-case to warrant extension at language level. Similar to there not being both |
It is respectfully nothing like the while vs do while situation. A while loop is one thing, and do while is another. An if statement already exists in the language with clearly defined semantics and permutations. This syntax reuses the if statement in name only, masquerading as a block of code while not actually sharing any of its features apart from reusing the same if keyword, with no reason why that can’t be fixed. |
I regard (And definitely also not a guard-let or whatever Swift has. It has too many of these.) |
Which is why I think |
This comment has been minimized.
This comment has been minimized.
In Ruby exists
|
I’ve always disliked |
Hi, I've landed here looking for
Which I think today, is best expressed as:
|
@mark-i-m I dislike them when they're just sugar for |
I feel like that would be a bit unexpected for most people. There is conceptually no reason |
See #1303 |
AFAIK nobody is confused about guard statements in Swift. |
The usage of a pattern in
Guard blocks as in #1303 don't have this problem. |
@HeroicKatora I think you linked to the wrong RFC (I don't think you mean to link to supporting comments in rustdoc) |
No, But I agree that the ergonomics are not great with |
Could you provide any example of |
if let Some(b) = buf_b.as_ref() {
let b = b.clone();
drop(buf_b);
observer(ctx, &func.call_mut(ctx, &(a.clone(), b)));
} here |
how is that different of match buf_b.as_ref() {
Some(b) => {
let b = b.clone();
drop(buf_b);
observer(ctx, &func.call_mut(ctx, &(a.clone(), b)));
},
_ => {}
} ? Especially if we add an |
@Pzixel If you read the original RFC for |
I agree that |
Do you mean |
The critical thing that almost any approach using If you're performing validation on some type that is four nested layers deep, for instance, with if let Some(a) = foo() {
if let Some(b) = a.b {
if let Some(c) = b.c {
return Ok(c);
} else {
return Err("no c");
}
} else {
return Err("no b");
}
} else {
return Err("no a");
} This requires both a reader and writer of the code to keep track of the various levels of nesting and what condition was checked at each stage. It is quantifiable more difficult to work with code that has deep nested logic like this; it requires keeping more state in your head as you read and it will slow development down. With enough layers of nesting (and anyone who doesn't believe that they can get this deep should look at rustc source), you make more readable code by ignoring the ergonomic convenience of pattern destructuring: let f = foo();
if f.is_none() {
return Err("no a");
}
let a = f.unwrap();
if a.b.is_none() {
return Err("no b");
}
let c = a.b.unwrap();
// etc. This code is far easier to work with. But better still would be: unless let Some(a) = foo() {
return Err("no a");
}
unless let Some(b) = a.b {
return Err("no b");
}
// etc. I used However, I realized today, and the reason I'm poking in, is that I think it's maybe doable if you allow the use of the never type as a way to statically assert that a block can never return control flow normally. Specifically, a macro where Unfortunately it requires a proc macro to get the extra names to bind to, but otherwise I think it could work pretty well. |
I'm not sure if we should force "never return". We could support, for instance let Some(a) = foo() else {
a = 1234;
}
// meaning: let a = foo().unwrap_or(1234);
use_(a); If we don't diverge or assign |
The biggest problem is how do you determine whether an identifier is a name or a constant. #[cfg(wut)]
const B: u64 = 1u64;
let (Some(A), B) = foo() else { return };
dbg!(A, B); |
For the first comment, doing alternative bindings like that is definitely
cool, but keeping it simple to start with is good, I think.
If we want to go further down the rabbit hole, I could even imagine, say,
`^` being a pattern metacharacter that binds into an outer scope:
```rust
if !let Some(^a) = ... {
} else {
let ^a = ...;
}
```
No reason this couldn't work if you were doing bindings outside the `if
let`, too.
And this isn't even its final form:
```rust
'label: {
if foo {
if bar {
let ^'label a = 1;
}
//...
}
}
```
For the second, I have no answer :(. The macro could accept a metatoken to
denote a constant, perhaps, to allow the user to disambiguate.
|
Well we do have let (Some(a), const { None }) = ... |
We have I implemented this over the weekend as a proof-of-concept, requiring the use of Meanwhile, an alternative syntactic suggestion from @strega-nil, that I quite like: let Some(a) = opt else {
return err;
} I like this one in that it works naturally with chaining, too: let Some(a) = opt &&
Some(b) = a.field else {
return err;
} |
@alercah Here's the RFC that contains const in patterns: https://github.com/ecstatic-morse/rfcs/blob/inline-const/text/0000-inline-const.md#patterns |
can we close this now, given #3137? |
That seems reasonable. The use case likely still exists, but is drastically reduced at this point. |
The usecases outlined here get even worse with |
You can trivially implement them as a macro by example using let..else (although the syntax I was able to get |
@goose121 Yes, this would work, but this is anything but "trivial". I'd rather rewrite the whole loop and the code around it. Using a macro makes such code a lot less readable and doesn't benefit from language orthogonality. If I have a feature that negates |
came here, suprised this does not work^^ I actually need |
Another option is |
Isn't it generally considered a bad code style to negate the condition of |
I would say this really depends on the use case. I e.g. did not want a plain if but a condition in a while loop. My use-case is polling a popen-object until it returns a Some() (indicating the process has terminated). |
Is the object being polled an Option? |
I tried to play around with your use case a bit and I ran into a few "challenges". My code and what I tried is below. The challenge that I had was that if I used a while loop I couldn't find a way to get the Here is the code I used to play with the idea (let me know if I misunderstood). fn poll(step:u8) -> Option<u8>{
if step > 5{
Some(0)
} else {
None
}
}
fn main() {
let mut step = 1;
loop{
if let Some(exit_code) = poll(step){
println!("Exiting with exit code: {exit_code}");
break;
}
println!("Step: {step}");
step += 1;
}
println!("Done");
} First off I can see why you would want to put it in a while loop but I also couldn't find a way to do it, that allowed me to still find out what the exit code was. If you didn't need the exit code you could just do the following: fn poll(step:u8) -> Option<u8>{
if step > 5{
Some(0)
} else {
None
}
}
fn main() {
let mut step = 1;
while poll(step).is_none(){
println!("Step: {step}");
step += 1;
}
println!("Done");
} |
We kinda have this as a combination of |
The RFC for
if let
was accepted with the rationale that it lowers the boilerplate and improves ergonomics over the equivalentmatch
statement in certain cases, and I wholeheartedly agree.However, some cases still remain exceedingly awkward; an example of which is attempting to match the "class" of a record
enum
variant, e.g. given the following enum:and an instance
foo: Foo
, with behavior predicated onfoo
not beingBar
and a goal of minimizing nesting/brace creep (e.g. for purposes of an early return), the only choice is to type out something like this:or the equally awkward empty
match
block:It would be great if this were allowed:
or perhaps a variation on that with slightly less accurate mathematical connotation but far clearer in its intent (you can't miss the
!
this time):(although perhaps it is a better idea to tackle this from an entirely different perspective with the goal of greatly increasing overall ergonomics with some form of
is
operator, e.g.if self.integrity_policy is Foo::Bar ...
, but that is certainly a much more contentious proposition.)The text was updated successfully, but these errors were encountered: