Search code examples
rustiteratormax

Is the idiom to find the maximum value in an iterator, and using it, by dereferencing only (Rust)?


I have gone through a handful of posts on this topic, in Stackoverflow. I am confirming if I have got the essence, correct.

I want to find out the maximum value in a Vector, using Iterator. Because of the way, iterators are implemented in Rust (discussed here, and here), I understand that I will always receive a reference to the element (if it exists) and not its value.

This doesn't compile.

pub struct WorkCard {
    pub worker_id: u32,
    pub largest_sack_moved_by_worker: u128,
    pub sacks_moved: Vec<u128> // collection of duration of each call to API
}

impl WorkCard {
    pub fn new(worker_id: u32, sacks_moved: Vec<u128>) -> WorkCard {
        let mx = sacks_moved.iter().max().unwrap_or(0);
        WorkCard { worker_id, largest_sack_moved_by_worker: mx, sacks_moved }
    }
}
fn main() {
    

    let sacks: Vec<u128> = vec![5,2,9,8,1];
    
    let _worker1 = WorkCard::new(1 /* worker_id */, sacks /* sacks_moved */);
    
    println("Worker {}, moved even a sack of {} KG!", 
            _worker1.worker_id, 
            _worker1.largest_sack_moved_by_worker
    );
    
    
}

The compiler clearly indicates what the problem is:

Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/main.rs:9:53
  |
9 |         let mx = sacks_moved.iter().max().unwrap_or(0);
  |                                           --------- ^
  |                                           |         |
  |                                           |         expected `&u128`, found integer
  |                                           |         help: consider borrowing here: `&0`
  |                                           arguments to this method are incorrect
  |
help: the return type of this call is `{integer}` due to the type of the argument passed
 --> src/main.rs:9:18
  |
9 |         let mx = sacks_moved.iter().max().unwrap_or(0);
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^
  |                                                     |
  |                                                     this argument influences the return type of `unwrap_or`

Of course, this works!

impl WorkCard {
    pub fn new(worker_id: u32, sacks_moved: Vec<u128>) -> WorkCard {
    
        let mx = sacks_moved.iter().max().unwrap_or(&(0 as u128)); // <-- forcing a numeric constant's reference
        WorkCard { worker_id, largest_sack_moved_by_worker: *mx, sacks_moved } // <-- then, dereferencing !
    }
}

My question is if I should take it that this is an idiomatic way to grab the maximum (or minimum) value off an iterator. If there is a more pleasant way, I would like to learn that.

Playground

NB: Thanks for everybody who has taken time to comment and guide me.

I think I should point out why I had been looking for an idiomatic use for such a common construct.I had been in serious 'C' world for the first decade and a half of my career, and there - as you all know - dereferencing using *-operator was (and is) a part of daily ritual. However, as I learn Rust, I realize that using *-operator to get a value is not really the norm. In fact, safer and cleaner operators/techniques/idioms exist to bypass that completely, in a overwhelming number of Use-Cases. Thus, was my question, if such a use (use of '*' operator) was OK, with the Rust experts. I had got my job done, yet I inquired because my own solution didn't seem right to me. :-P

I am very aware of the power and the inherent danger of using '*' operator. So, I am cautious.


Solution

  • Because of the way, iterators are implemented in Rust (discussed here, and here), I understand that I will always receive a reference to the element (if it exists) and not its value.

    That is not quite true, you receive a reference not because of the way "iterators are implemented in Rust" but because you're using a borrowing iterator (slice::Iter, as returned by slice::iter).

    Of course, this works!

    It's not clear to me why you bother with (0 as u128), &0 would work fine. Even if it did not, 0u128 will provide a concretely-typed constant.

    My question is if I should take it that this is an idiomatic way to grab the maximum (or minimum) value off an iterator. If there is a more pleasant way, I would like to learn that.

    Pleasantness is in the eye of the beholder, defaulting to a reference looks fine to me, but you could use an adapter to convert the reference to an owned value, either Iterator::copied:

    let mx = sacks_moved.iter().copied().max().unwrap_or(0);
    

    or Option::copied:

    let mx = sacks_moved.iter().max().copied().unwrap_or(0);