Cornstalks
Cornstalks

Reputation: 38218

Can I destructure a tuple without binding the result to a new variable in a let/match/for statement?

I'd like to destructure a tuple and assign part of the result to a new variable and assign another part of the result to an existing.

The following code illustrates the intent (it's a dumb example which results in an infinite loop printing [0]):

fn main() {
    let mut list = &[0, 1, 2, 3][..];
    while !list.is_empty() {
        let (head, list) = list.split_at(1);
        // An obvious workaround here is to introduce a new variable in the above
        // let statement, and then just assign it to list.
        println!("{:?}", head);
    }
}

This code creates a new variable list instead of reassigning it.

If I change the code to the following (to avoid the let that introduces the new list variable), it doesn't compile:

fn main() {
    let mut list = &[0, 1, 2, 3][..];
    while !list.is_empty() {
        let head;
        (head, list) = list.split_at(1);
        println!("{:?}", head);
    }
}

Compilation error:

error[E0070]: invalid left-hand side of assignment
 --> src/main.rs:5:22
  |
5 |         (head, list) = list.split_at(1);
  |         ------------ ^
  |         |
  |         cannot assign to this expression
  |

Is there a way to do this, or can destructuring only be used in let, match, and for statements?

Upvotes: 34

Views: 14290

Answers (3)

McGrady
McGrady

Reputation: 11477

Yes.

The Rust team has published a new version of Rust 1.59.0 in Feb. 24, 2022, you can now use tuple, slice, and struct patterns as the left-hand side of an assignment.

Announcing Rust 1.59.0

Destructuring assignments

You can now use tuple, slice, and struct patterns as the left-hand side of an assignment.

let (a, b, c, d, e);

(a, b) = (1, 2); [c, .., d, _] = [1, 2, 3, 4, 5]; Struct { e, .. } =
Struct { e: 5, f: 3 };

assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]);

This makes assignment more consistent with let bindings, which have long supported the same thing. Note that destructuring assignments with operators such as += are not allowed.

Before 1.59.0, you can only destructure it in Nightly version with #![feature(destructuring_assignment)].

Now you can do this trick in stable version and remove the feature line.

See more details from rust-lang/rust/issues/71126 and rust-lang/rust/pull/90521.

Upvotes: 25

Shepmaster
Shepmaster

Reputation: 430673

Nightly Rust has feature(destructuring_assignment), which allows your original attempt to compile as-is:

#![feature(destructuring_assignment)]

fn main() {
    let mut list = &[0, 1, 2, 3][..];
    while !list.is_empty() {
        let head;
        (head, list) = list.split_at(1);
        println!("{:?}", head);
    }
}
[0]
[1]
[2]
[3]

However, I'd solve this using stable features like slice pattern matching, which avoids the need for the double check in split_at and is_empty:

fn main() {
    let mut list = &[0, 1, 2, 3][..];

    while let [head, rest @ ..] = list {
        println!("{:?}", head);
        list = rest;
    }
}

See also:

Upvotes: 4

DK.
DK.

Reputation: 58995

No.

Destructuring is something you can only do with patterns; the left-hand side of an assignment is not a pattern, hence you can't destructure-and-assign.

See proto-RFC 372 (Destructuring assignment) which discusses the possibility of adding this feature.

Upvotes: 19

Related Questions