如何改变 Iterator::find 的闭包中的项目?

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

我想在 libusb::Devices 对象上使用 Iterator::find,它的签名如下:

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

我想查找具有特定 vendor_id 的设备,这需要在每个设备上调用 Device::device_descriptor。但是device_descriptor方法需要给每个设备一个&mut,而find方法只给一个&

这是否意味着不可能在 Iterator 的任何方法(findfilter 等)上使用可变方法?

这是我正在努力工作的示例:

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

这是我遇到的错误:

error: cannot borrow immutable borrowed content `*dev` as mutable

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

在接收F: Fn*(&Self::Item)类型参数的方法中,是的。不能调用在引用 (&) 上期望可变引用 (&mut) 的方法。例如:

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

请注意,此规则也适用于自动取消引用。

Iterator 的某些方法接收 F: Fn*(Self::Item) 类型的参数,如 mapfilter_map 等。这些方法允许函数改变项目。


一个有趣的问题是:为什么有些方法期望 Fn*(&Self::Item) 而其他方法期望 Fn*(Self::item)

需要使用该项目的方法,如filter(如果过滤器函数returns true,将return该项目),不能通过Self::Item 作为函数的参数,因为这样做意味着将项目的所有权赋予函数。出于这个原因,像 filter 这样的方法通过 &Self::Item,所以他们可以在以后使用该项目。

另一方面,像mapfilter_map这样的方法在被用作参数后不需要项目(项目毕竟是被映射的),所以他们将项目作为Self::Item.


一般情况下,在项目需要变异的情况下,可以使用filter_map代替filter的使用。对于您的情况,您可以这样做:

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());
    }
}

filter_map 过滤掉 None 值并在 Some 秒内生成包装值。

答案迁移自:

您可以改用 Iterator::find_map,它的闭包通过 value 接收元素,然后可以很容易地对其进行变异:

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

Playground