Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does a '&&x' pattern match cause x to be copied?

In the documentation for std::iter::Iterator::filter() it explains that values are passed to the closure by reference, and since many iterators produce references, in that case the values passed are references to references. It offers some advice to improve ergonomics, by using a &x pattern to remove one level of indirection, or a &&x pattern to remove two levels of indirection.

However, I've found that this second pattern does not compile if the item being iterated does not implement Copy:

#[derive(PartialEq)]
struct Foo(i32);

fn main() {
    let a = [Foo(0), Foo(1), Foo(2)];

    // This works
    let _ = a.iter().filter(|&x| *x != Foo(1));

    // This also works
    let _ = a.iter().filter(|&x| x != &Foo(1));

    // This does not compile
    let _ = a.iter().filter(|&&x| x != Foo(1));
}

The error you get is:

error[E0507]: cannot move out of a shared reference
  --> src/main.rs:14:30
   |
14 |     let _ = a.iter().filter(|&&x| x != Foo(1));
   |                              ^^-
   |                              | |
   |                              | data moved here
   |                              | move occurs because `x` has type `Foo`, which does not implement the `Copy` trait
   |                              help: consider removing the `&`: `&x`

Does this mean that if I use the &&x destructuring pattern, and the value is Copy, Rust will silently copy every value I am iterating over? If so, why does that happen?

like image 592
harmic Avatar asked Mar 02 '23 09:03

harmic


2 Answers

In Rust, function or closure arguments are irrefutable patterns.

In the Rust reference, it says:

By default, identifier patterns bind a variable to a copy of or move from the matched value depending on whether the matched value implements Copy.

So, in this case:

let _ = a.iter().filter(|&x| *x != Foo(1));

the closure is being passed a reference to a reference to the item being iterated; therefore x is bound to a copy of a reference to the item. You can always copy a reference (it is basically a no-op) so this always succeeds.

In this case:

let _ = a.iter().filter(|&&x| x != Foo(1));

x is being bound to a copy of the item itself - which fails if the item is not Copy.

The reference also says:

This can be changed to bind to a reference by using the ref keyword, or to a mutable reference using ref mut.

That's not so useful in this case though: &&ref x results in x being a reference to the item, the same as if you had used &x.

like image 64
harmic Avatar answered Mar 12 '23 09:03

harmic


Does this mean that if I use the &&x destructuring pattern, and the value is Copy, Rust will silently copy every value I am iterating over?

Yes, that's correct, although the compiler backend might optimize the copying away.

If so, why does that happen?

Writing & before a pattern dereferences it. This copies the value it refers to, because a reference only borrows its value, so it can't move the value.

Example:

#[derive(Copy, Clone)]
struct Foo(i32);

let foo: Foo = Foo(5);   // value is owned by foo
let borrow: &Foo = &foo; // value is borrowed
let bar: Foo = *borrow;  // value is copied, the copy is owned by bar
                         // the original value is still owned by foo

Your first example is a bit special:

let _ = a.iter().filter(|&x| *x != Foo(1));

This seems at first as if it should require the Copy trait because x is dereferenced twice, first in the pattern, and then in the comparison.

However, the comparison is done by the PartialEq trait, which takes its arguments by reference. So the above is desugared to:

let _ = a.iter().filter(|&x| PartialEq::eq(&*x, &Foo(1)));

Which works because &* cancel each other out. Hence, x is only dereferenced once (in the pattern).

like image 33
Aloso Avatar answered Mar 12 '23 08:03

Aloso