How do I mutate the item in Iterator::find's closure?
Asked Answered
N

2

6

I would like to use Iterator::find on libusb::Devices object, which has a signature like so:

fn find<P>(&mut self, predicate: P) -> Option<Self::Item> 
    where Self: Sized, P: FnMut(&Self::Item) -> bool

I want to find a device with a particular vendor_id, which requires calling Device::device_descriptor on each device. However, the device_descriptor method requires a &mut to each device, and the find method only gives a &.

Does this mean that it's impossible to use mutable methods on any of the Iterator's methods (find, filter, etc.)?

Here's the example I'm trying to get working:

let mut device = context
    .devices()
    .unwrap()
    .iter()
    .find(&mut |dev: &libusb::Device| { 
         dev.device_descriptor().unwrap().vendor_id() == vendor_id
     })
    .unwrap();

Here is the error I'm getting:

error: cannot borrow immutable borrowed content `*dev` as mutable
Namedropper answered 3/6, 2016 at 17:40 Comment(0)
C
6

Does this mean that it's impossible to use mutable methods on any of the Iterator's methods (find, filter, etc.)?

In the methods that receive a parameter of type F: Fn*(&Self::Item), yes. One cannot call a method that expects a mutable reference (&mut) on a reference (&). For example:

let mut x = vec![10];
// (&x)[0] = 20; // not ok
(&mut x)[0] = 20; // ok

//(& (&x))[0] = 20; // not ok 
//(& (&mut x))[0] = 20; // not ok
(&mut (&mut x))[0] = 20; // ok

Note that this rule also applies to auto deref.

Some methods of Iterator receive a parameter of type F: Fn*(Self::Item), like map, filter_map, etc. These methods allow functions that mutate the item.


One interesting question is: Why do some methods expect Fn*(&Self::Item) and others Fn*(Self::item)?

The methods that will need to use the item, like filter (that will return the item if the filter function returns true), cannot pass Self::Item as parameter to the function, because doing that means give the ownership of the item to the function. For this reason, methods like filter pass &Self::Item, so they can use the item later.

On the other hand, methods like map and filter_map do not need the item after they are used as arguments (the items are being mapped after all), so they pass the item as Self::Item.


In general, it is possible to use filter_map to replace the use of filter in cases that the items need to be mutated. In your case, you can do this:

extern crate libusb;

fn main() {
    let mut context = libusb::Context::new().expect("context creation");

    let mut filtered: Vec<_> = context.devices()
        .expect("devices list")
        .iter()
        .filter_map(|mut r| {
            if let Ok(d) = r.device_descriptor() {
                if d.vendor_id() == 7531 {
                    return Some(r);
                }
            }
            None
        })
        .collect();

    for d in &mut filtered {
        // same as: for d in filtered.iter_mut()
        println!("{:?}", d.device_descriptor());
    }
}

The filter_map filters out None values and produces the wrapped values in Somes.

Collotype answered 3/6, 2016 at 20:45 Comment(2)
Thanks for the great response. Another option that might work in some situations is iter_mut(), but unfortunately libusb doesn't implement it.Namedropper
In general, iter_mut produces Item = &mut T, so &Item = &&mut T. That means that the filter function cannot mutate the item... See play.rust-lang.org/…Collotype
C
1

Answer migrated from: How can I apply mutating method calls to each struct when using Iter::find?

You can use Iterator::find_map instead, whose closure takes in elements by value which can then be easily mutated:

games
    .iter_mut()
    .find_map(|game| {
        let play = rng.gen_range(1..=10);
        game.play(play).then(|| game)
    })

Playground

Cutch answered 6/12, 2021 at 21:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.