If let chaining not works in Rust edition 2021

Hi everyone !
I want to use smth like:

if let Some(mut receiver) = receiver && let Some(mut sender) = sender {

in my code. But this shows me an error:

`let` expressions in this position are unstable [E0658]

So as a workaround I use such code:

if let (Some(mut receiver), Some(mut sender)) = (receiver, sender) {

Could somebody explain what can I do to make first option also possible ?

try switching to nightly?

nope. Switching to nightly means I will use unstable version, right ? If so, I need stable

1 Like

so you can't use it
in nightly you can just add #![feature(let_chains)], but as you said its unstable.

2 Likes

I'm curious. The syntax you are asking for seems more obscure than what you already have. Why the need to change to that?

1 Like

For me this

if let Some(mut receiver) = receiver && let Some(mut sender) = sender {

seems to be more natural than:

if let (Some(mut receiver), Some(mut sender)) = (receiver, sender) {

So I tried the first option first.

Fair enough.

It's just that as I look at it I see that the former solution has:
Two let´s instead of one.
Two =´s instead of one.
And a && instead of none.

So I'm seeing that latter as being more straightforward.

3 Likes

You could try the if_chain crate to see if it's worth for your usecase, otherwise you'll have to be satisfied with the workaround, since you can't use unstable features on stable.

Honestly the first seems more natural to me. In the first example an expression must match a pattern and another expression must match another pattern, while in the second example you introduce a tuple and mix the two patterns just to get the same result. The fact that the first syntax was added to the language (despite still being unstable) is a kind of proof that people want it (though it should be noted that it is also more powerful than what was shown here, so there are other reasons other than being more natural).

1 Like

If you want to track the feature, this looks like its tracking issue. Tracking issues won't always tell you when stabilization will happen, nor should any mention of future stabilizations be read as promises, but it'll let you get an idea about what progress is being made toward stabilization.

2 Likes

As a C user coming to Rust I would have guessed that the fact that the second syntax was added to the language is a kin of proof that people want it. So why the need for the first ?

Because you couldn't do:

if let Some(receiver) = receiver && let Some(property) = receiver.property {
   …
}

with tuples trick.

P.S. It's funny how everyone ignore the question in the title: why do we need Rust 2024 for that to work :upside_down_face:. It's because of subtle change in Rust 2024. I wonder why editions guide seems to be silent about that change, though.

3 Likes

What about this variant?

if let (Some(mut receiver), Some(mut sender)) = (receiver, sender) {

In my opinion, this communicates more clearly the intend.

Well… it combines two unrelated things into one, then pulls it apart again… sure, optimizer is clever enough to understand what goes on here… and with Rust 2015/18/21 that's the only choice… but why?

In spite of subtle issues related to the implementation if let chaining is extremely natural… so natural that topicstarter started his message not with question “when would that be available”, but more of “why the heck this still doesn't work”…

Different people have different opinions, ultimately, but I wouldn't prefer tuple hack in a language where it's not needed.

The first "syntax" was not made for this, it's just a consequence of the fact you can create and pattern match tuples.

In the title there 2021. Just a default edition I have for each my project (created by IDE). There no 2024 mentioned in my question.

Thanks everyone for the answers. I believe the easiest way for now is my workaround.

The more general solution is to nest if statements.

if let Some(mut receiver) = receiver {
    if let Some(mut sender) = sender {
        ...
    }
}

I'm sure you don't want that, but note that your solution only works in certain cases. What I run into more frequently is the need to:

  • Destructure with if let
  • Check a condition using the destructured value.
  • Then do something with the destructured value.

For example, I would love to use let chaining like this:

if let Some(val) = &opt_val && cond(val) {
    val.xxx();
}

But for now I have to use nested if statements:

if let Some(val) = &opt_val {
    if cond(val) {
        val.xxx();
    }
}

I don't use Option::is_some_and since I would have to destructure afterwards to get the value. The only Option combinators that work are iter/filter/for_each:

opt_val.iter().filter(|val| cond(val).for_each(|val| val.xxx());

I think that's less readable than the if statements. You can use a for "loop" also. But that's also not as readable (and very weird) IMO:

for val in opt_val.iter().filter(|val| cond(val)) {
    val.xxx();
}

Perhaps some people prefer the combinators.

Anyway, I don't consider nested if statements a "workaround". I can live without chained lets and I can understand why other things are more important to change in the language. Nesting is fine and I try not to let it bother me.

1 Like

It's in the nightly edition.

https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html

3 Likes

And also (importantly) on beta

https://doc.rust-lang.org/beta/edition-guide/rust-2024/temporary-if-let-scope.html

which is going to be the version (1.85) where the 2024 edition stabilizes (in about 11 days).

I think "channel" is the correct term.

2 Likes

I totally agree the chain of if let is more pleasant to use in this situation, I just want to point out you can do it without chaining, though not with tuples, and is very verbose in syntax:

struct Receiver {
    property: Option<i32>,
    property2: u32,
}

fn foo(receiver: Option<Receiver>) {
    if let Some(
        ref receiver @ Receiver {
            property: Some(ref property),
            ..
        },
    ) = receiver
    {
        todo!()
    }
}

this is also good example of chaining, but in the case of Option specifically, you can use the Option::filter() method for this purpose:

if let Some(ref val) = opt_val.filter(cond) {
    val.xxx();
}
1 Like