Why extracting values from tuple to existing variables so hard?

After I start programming on Rust I often meet such restriction as in this code:

fn return_tuple() -> (Point, i32);

fn f(a: Option<i32>, base_point: Point) {
  let mut point = base_point;
  let mut some_val = 17;
  match a {
     Some(val) => {
        if (val > 5) {
          (point, some_val) = return_tuple();
        }
     }
     None => { /*somthing else */
  }
}

As you can see in function f I use variables point and some_val,
basically I use this variables "as is", but if several conditions match, I need
rewrite this variables with values from return_tuple.

And here is problem, I can NOT just write (point, some_val) = return_tuple(),
I have to use match, or introduce new local variables and then assign
their values to point and some_val.

Is any way to simplify extraction values from tuple?

3 Likes

It's a sufficiently common need.

1 Like

If you want to avoid new local variables you can just write s.th. like this (untested):

 let (point, some_val) = match a {
     Some(val) if (val > 5) => return_tuple()
     Some(val) => // s.th. else if val <= 5
     None => (base_point, 17)
  }

More information here: Patterns

2 Likes

The grammar for let statement looks like let pattern = expression. The grammar for assignment expression is expression = expression. What you're proposing is adding a special case – when LHS of assignment is a tuple literal, treat the whole thing differently. This also changes the whole thing from being an expression to a statement. This special case will make Rust grammar more complicated, which may be a problem for external tools and rustc itself, as parsing with that change requires arbitrary lookahead.

Lack of this feature is pointed out quite often, but it's not that annoying, as it seems – it turns out it's usually necessary only in loops (eg. fibonacci calculation can contain a line (a, b) = (b, a + b)). For example your snippet could be rewritten as:

fn f(a: Option<i32>, base_point: Point) {
  let (base_point, some_val) = match a {
     Some(val) if val > 5 => return_tuple(),
     None => (base_point, 17),
  }
}

If you feel that you really need that feature, you could always make a macro, or create an extension trait for tuples, so you could write:

assign_tuple!(base_point, val = return_tuple());
// or
return_tuple().unpack_to(&mut base_point, &mut val);
2 Likes

That's a very reasonable statement. When you are coming from Haskell it is natural to have pattern matching always be on the left side and thus in Rust equivalent might be let (a', b') = (b, a + b).

On the other hand as long as assignment expression (a = b) is not an operator with overload, I don't see why can't (a, b, _) = (b, a + b, z) (and any pattern on LHS) syntactically be expanded to { let (a', b', z') = (b, a + b, z); a = a'; b = b'; (a', b', z') }. Though I agree that there is still some weak point like assignment have order semantic that can be exposed outside of unpacking while *let statement is local. But that's something that can explicitly be documented.