Reputation: 13297
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
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
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 Vec
s 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
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
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