Aliasing in pattern matching


#1

I talked about it on #rust-beginners, and I was suggested to share this problem.

In OCaml you can easily do:

let min_rat pr = match pr with
   ((_,0),p2) ->  p2
 | (p1,(_,0)) ->  p1
 | (((n1,d1) as r1), ((n2,d2) as r2)) ->  
         if (n1 *  d2 ) < (n2 * d1) then r1 else r2;;

Trying to do the same in Rust results in an error:

fn min_rat(x: (i32, i32), y: (i32, i32)) -> (i32, i32) {
    match (x, y) {
        ((_, 0), p2) => p2,
        (p1, (_, 0)) => p1,
        (r1 @ (n1, d1), r2 @ (n2, d2)) => if n1 * d2 < n2 * d1 {r1} else {r2},
    }
}

gives

pattern bindings are not allowed after an `@`

(playground: https://play.rust-lang.org/?gist=61dbad9ba71473e407864e3536ec7b5d&version=stable)

Currently, the only way to implement this is to duplicate the matched part in the following way:

 ((n1, d1), (n2, d2)) => if n1 * d2 < n2 * d1 {(n1, d1)} else {(n2, d2)},

This is error-prone (you can easily forget to change the returned value while changing the match condition) and superfluous. Is there any reason why an extra name binding is not allowed here? It’s all read-only.


#2

One potential issue is that you would then have two different locations that the value will be moved to. In your example it’s ok since i32: Copy, but there would need to be a error message about “use of moved value” if similar code was written for a non-Copy type. It could also be worked around by adding ref's on all the bindings (but again there would need to be an error message for attempting to use mut ref).

Other than making sure those cases have good error messages, I don’t see any reason that this couldn’t work.


#3

See an example of the problem:

https://github.com/rust-lang/rust/issues/14587

Regarding your specific problem:

fn min_rat(x: (i32, i32), y: (i32, i32)) -> (i32, i32) {
    match (x, y) {
        ((_, 0), p2) => p2,
        (p1, (_, 0)) => p1,
        ((n1, d1), (n2, d2)) => if n1 * d2 < n2 * d1 {x} else {y},
    }
}

#4

The problem is that there’s both an immutable and a mutable reference at the same time. This should be caught by the borrow checker instead.

I see no way something could go wrong if all references/bindings were immutable.

Regarding my specific problem: in real code I sometimes destructure a Result at the same time. So what if…

fn min_rat(x: Option<((i32, i32),(i32, i32))>) -> (i32, i32) {
    match x {
        Some((_, 0), p2) => p2,
        Some(p1, (_, 0)) => p1,
        Some((n1, d1), (n2, d2)) => if n1 * d2 < n2 * d1 {x} else {y}, // whoops!
        Err(_) => /* handle the error */
    }
}

it’s where I’d love to do this as:

Some(xx @ (n1, d1), yy @ (n2, d2)) => if n1 * d2 < n2 * d1 {xx} else {yy},