Here is a snippet of code that I think should work just fine, but doesn't. The idea is to step through a hashmap and pull out the largest number contained in the the value fields. The hashmap is defined by:
let mut loft: HashMap<String, i32> = HashMap::new();
Here's the snippet:
let mut bigval = std::i32::MIN; // Set to lowest i32 value possible.
for (_key, val) in loft.iter() {
if val > bigval {
bigval = val;
};
}
To me this code seems quite straightforward and easy to follow, however, the compiler rejects this code, insisting I need to use &bigval and *val in the loop. Here's what works:
for (_key, val) in loft.iter() {
if val > &bigval {
bigval = *val;
};
}
Obviously there's something about for-loops, references, & dereferences that I'm not understanding. Could someone set me straight, please? Thanks.
iter() creates a by-reference iterator over the contents of the HashMap. If you want to iterate over the values in the map, don't call that method.
let mut loft: HashMap<String, i32> = HashMap::new();
let mut bigval = std::i32::MIN; // Set to lowest i32 value possible.
for (_key, val) in loft {
if val > bigval {
bigval = val;
};
}
However this consumes the map, so you won't be able to use it again afterwards.
Slightly tangential, but you can get the same effect as iter() by just putting a reference in the for loop
for (_key, val) in &loft {
if *val > bigval {
bigval = *val;
};
}
So, my problem was using iter(). I thought I had to use that method in order to step through the hashmap. And that brings us to a bigger problem -- what exactly does iter() do for us and, even bigger, what do "iteration" and "iterator" mean in rustarese? I think I'm missing that concept.
for item in collection.iter() and for item in &collection are equivalent, and they produce iterators over references to items. You use them if/when you want references, without consuming the container.
If you don't want references, but you want to consume the container, then you don't need/shouldn't use .iter(). You can just do for item in collection (or the equivalent but unnecessarily verbose for item in collection.into_iter()), then.
The third option is for item in &mut collection or the equivalent for item in collection.iter_mut(). These produce mutable references to the container's elements.
All three pairs of methods have different use cases. Just because you happen to need a by-value iterator in this case doesn't mean that the by-reference iteration methods are useless.
std::i32::MAX is deprecated. Use i32::MAX instead.
If you want the map to remain usable, but iterate over values, either put a reference pattern in the first part of the for loop (as in for (&key, &value) in loft) or use Iterator::copied after calling .iter().
Generally, when you want to obtain one item from an iterator, you should not use a for loop. The procedural style of writing this more is error-prone (e.g. if the iterator contains i32::MAX, you can't differentiate it from an empty iterator; and you can make errors more easily) and a lot less concise.
Thus is it considered more idiomatic to do these kinds of tasks in a functional style. As finding the maximum of an iterator is a common task, there is even a specific method for this purpose: Iterator::max (note that you use HashMap::values for an iterator over the map's values). For more general problems you could try fold or reduce.
I have a feeling that you misunderstand references rather than iterators. Iterators in Rust are fairly straightforward, they are mostly the same as in Java or Python. You have a trait Iterator with a method next which returns Option<Iterator::Item>. Values are converted into iterators using the IntoIterator trait, which is trivially implemented for all iterators (but not all types are iterators on their own, though many can be converted into iterator). The conversion is always applied to the iterated object in the for-loop.
But references in Rust are entirely unlike references in C++, or object references in Java. In those languages a reference is basically a transparent alias for an existing object, and can be used everywhere the object can. In Rust, a reference &T is an entirely separate type which is a priori unrelated to T itself. A reference is basically a pointer (in C sense) plus certain compiler-checked validity guarantees. References cannot be used interchangeably with their references types (although stuff like method call syntax includes some syntax sugar which hides this distinction). &T can have entirely different methods and trait implementations from T itself.
HashMap::iter gives you an iterator of references. More specifically, it gives you an implementation of Iterator<Item = (&Key, &Value)>. This means that in your example val: &Value, while bigval: Value. The types are different, so you need to take care which way you use the values. Something like val + bigval works, because there is an impl Add<&i32> for i32. bigval = val doesn't work, because you're trying to assign a value of a different type, &i32 instead of i32. val > bigval doesn't work, because there is noimpl PartialOrd<&i32> for i32.
fn main() {
// These are all equivalent if `v` is a `Vec`:
let v = vec![1, 2];
for x in v.iter() {
println!("{x}")
}
for x in (&v).iter() {
println!("{x}")
}
for x in &v {
println!("{x}")
}
// These are equivalent if `r` is a `&Vec`:
let r = &v;
for x in r.iter() {
println!("{x}")
}
for x in r {
println!("{x}")
}
// `&Vec` implements `Copy`, so the previous loop didn't consume `r`:
for x in r {
println!("{x}")
}
// But we need an explicit `.iter()` sometimes:
//for x in r.map(|&x| x * 10) { // won't work
//for x in v.map(|x| x * 10) { // won't work either
for x in r.iter().map(|&x| x * 10) {
println!("{x}")
}
// Note that this consumes the underlying `Vec`:
for x in v {
println!("{x}")
}
// I.e. we can't repeat it:
//for x in v {
// println!("{x}")
//}
}