Max Yankov
Max Yankov

Reputation: 13297

Why do I get a mismatched types error while trying to match a tuple?

Here's some self-explanatory code that I would expect to work:

type some_t = i32;

struct SomeStruct {
    pub some_tuple_vector: Vec<(some_t, some_t)>,
}

impl SomeStruct {
    fn some_method(&mut self) {
        for p in self.some_tuple_vector.iter_mut() {
            match p {
                (x, y) if x < 0 => self.some_tuple_vector.remove(p),
                (x, y) => p = (x, y - 1),
            }
        }
    }
}

fn main() {}

However, I get errors on both lines that attempt to match and destructure the tuple:

error[E0308]: mismatched types
  --> src/main.rs:12:17
   |
12 |                 (x, y) if x < 0 => self.some_tuple_vector.remove(p),
   |                 ^^^^^^ expected mutable reference, found tuple
   |
   = note: expected type `&mut (i32, i32)`
              found type `(_, _)`

I don't understand it; isn't the type &mut (i32,i32) a tuple itself?

I've realized that I can rewrite this code to be more elegant:

self.some_tuple_vector = self.some_tuple_vector
    .iter()
    .map(|(x, y)| (x - 1, y))
    .filter(|(x, y)| x > 0);

But I get the same error:

error[E0308]: mismatched types
  --> src/main.rs:12:19
   |
12 |             .map(|(x, y)| (x - 1, y))
   |                   ^^^^^^ expected &(i32, i32), found tuple
   |
   = note: expected type `&(i32, i32)`
              found type `(_, _)`

Upvotes: 2

Views: 3455

Answers (4)

Shepmaster
Shepmaster

Reputation: 430673

RFC 2005 improves the ergonomics around matching of references. This would help your immediate error by making (x, y) able to be pattern matched against a &mut (T, T).

In addition, the recently-added Vec::drain_filter method allows for exactly the Vec transformation you need.

Unfortunately, both of these are unstable features:

#![feature(drain_filter)]
#![feature(match_default_bindings)]

#[derive(Debug)]
struct SomeStruct {
    some_tuple_vector: Vec<(i32, i32)>,
}

impl SomeStruct {
    fn some_method(&mut self) {
        self.some_tuple_vector.drain_filter(|(x, y)| {
            if *x < 0 {
                true // remove
            } else {
                *y -= 1;
                false // keep
            }
        });
    }
}

fn main() {
    let mut ss = SomeStruct {
        some_tuple_vector: vec![(-1, -1), (0, 0), (1, 1)],
    };
    ss.some_method();
    println!("{:?}", ss);
}

This prints:

SomeStruct { some_tuple_vector: [(0, -1), (1, 0)] }

Upvotes: 0

huon
huon

Reputation: 102066

Vec::iter_mut returns a MutItems which implements Iterator<&'a mut T>. That is, it is a sequence of mutable references pointing into the memory of the vector. These are pointers (like C/C++) rather than the values themselves.

If you want to handle the data at these memory locations (read or write), you need to dereference. This happens automatically when calling methods or accessing fields with the . operator, but it does not happen while pattern-matching or while assigning. That is, p = ... is trying to set the local variable p to a new value, it is not assigning data to the memory to which it points. The latter is done via *p = ....

Hence, a rewrite that has a better chance of working is:

for p in self.some_tuple_vector.iter_mut() {
    match *p {
        (x, y) if x < 0 => self.some_tuple_vector.remove(p),
        (x, y) => *p = (x, y - 1),
    }
}

However, it is still wrong, due to remove taking an index, not a value. The remove is better done by calling retain and separately looping over the vector to decrement y, as A.B. suggests.


Your alternate solution just needs to pattern match. Pattern matching through a reference is done with &: destructuring mirrors constructing.

self.some_tuple_vector = self.some_tuple_vector
    .iter()
    .map(|&(x, y)| (x - 1, y))
    .filter(|&(x, y)| x > 0);

However, that leads to this rather long error message:

   = note: expected type `std::vec::Vec<(i32, i32)>`
              found type `std::iter::Filter<std::iter::Map<std::slice::Iter<'_, (i32, i32)>, [closure@src/main.rs:11:18: 11:38]>, [closure@src/main.rs:12:21: 12:36]>`

The issue is the iterator adaptors like map and filter return lazy iterator objects, they do not eagerly evaluate to Vecs of the result. To collect the values of a sequence into a concrete type like Vec, you just need to call the collect method:

self.some_tuple_vector =
    self.some_tuple_vector.iter()
        .map(|&(x,y)| (x-1,y))
        .filter(|&(x,y)| x > 0)
        .collect();

collect is generic and works with many collection types (anything that implements FromIterator), but in this case the compiler can infer that the desired type is Vec<(some_t, some_t)> since it is being assigned to a field with that type.

However, this is allocating a whole new vector and just discarding the old one, so is likely to be slower than the retain + iter_mut solution, except if most of the elements will be removed by the filtering.

Upvotes: 1

A.B.
A.B.

Reputation: 16630

I don't quite understand it. Isn't the type &mut (i32, i32) a tuple itself?

No, it is a mutable reference to a tuple.

You also can't remove elements from a vector while iterating over it as this could lead to dangling pointers. remove also removes an element at index. You'll have to do things differently:

type some_t = i32;

struct SomeStruct {
    pub some_tuple_vector: Vec<(some_t, some_t)>,
}

impl SomeStruct {
    fn some_method(&mut self) {
        self.some_tuple_vector.retain(|tuple| tuple.0 >= 0);
        for tuple in &mut self.some_tuple_vector {
            tuple.1 -= 1;
        }
    }
}

fn main() {}

This could also be done in a loop in one pass, but this is probably good enough.

Upvotes: 3

Manishearth
Manishearth

Reputation: 16188

This should work:

type some_t = i32;

struct SomeStruct {
    pub some_tuple_vector: Vec<(some_t,some_t)>
}

impl SomeStruct {
    fn some_method(&mut self) {
        for p in self.some_tuple_vector.iter_mut() {
            match *p {
                (x,y) if x < 0 => self.some_tuple_vector.remove(p),
                (x,y) => p = (x, y-1)
            }
        }
    }
}

While Rust does autoderef references when accessing member methods or data, it won't autoderef while checking equality/matching and you have to explicitly use the * operator.

&mut (a,b) is not a tuple, it is a reference to a tuple.

Upvotes: 0

Related Questions