【问题标题】:How to implement Iterator yielding mutable references [duplicate]如何实现产生可变引用的迭代器[重复]
【发布时间】:2020-05-21 04:09:08
【问题描述】:

我正在尝试实现一个简单的查找迭代器:

pub struct LookupIterMut<'a, D> {
    data : &'a mut [D],
    indices : &'a [usize],
    i: usize
}

impl<'a, D> Iterator for LookupIterMut<'a, D> {
    type Item = &'a mut D;

    fn next(&mut self) -> Option<Self::Item> {
        if self.i >= self.indices.len() {
            None
        } else {
            let index = self.indices[self.i] as usize;
            self.i += 1;
            Some(&mut self.data[index]) // error here
        }
    }
}

这个想法是允许调用者连续可变地访问内部存储。但是我收到了错误cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements

据我了解,我必须将函数签名更改为 next(&amp;'a mut self) -&gt; ..,但这不再是迭代器了。

我还发现我可以简单地使用原始指针,但我不确定这是否合适:

// ...
type Item = *mut D;
// ...

感谢您的帮助

【问题讨论】:

    标签: rust


    【解决方案1】:

    您的代码无效,因为您尝试将多个可变引用返回到具有相同生命周期的同一切片 'a

    要使这样的事情起作用,您需要为每个返回的Item 设置不同的生命周期,这样您就不会持有对同一切片的 2 个可变引用。你现在不能这样做,因为它需要通用关联类型:

    type Item<'item> = &'item mut D; // Does not work today
    

    一种解决方案是检查索引是否唯一,并在unsafe 块中将引用项的生命周期重新绑定到'a。这是安全的,因为所有索引都是唯一的,因此用户不能持有对同一项目的 2 个可变引用。

    不要忘记将整个代码封装在一个模块中,这样如果没有new中的检查就无法构建结构:

    mod my_mod {
        pub struct LookupIterMut<'a, D> {
            data: &'a mut [D],
            indices: &'a [usize],
            i: usize,
        }
    
        impl<'a, D> LookupIterMut<'a, D> {
            pub fn new(data: &'a mut [D], indices: &'a [usize]) -> Result<Self, ()> {
                let mut uniq = std::collections::HashSet::new();
                let all_distinct = indices.iter().all(move |&x| uniq.insert(x));
    
                if all_distinct {
                    Ok(LookupIterMut {
                        data,
                        indices,
                        i: 0,
                    })
                } else {
                    Err(())
                }
            }
        }
    
        impl<'a, D> Iterator for LookupIterMut<'a, D> {
            type Item = &'a mut D;
    
            fn next(&mut self) -> Option<Self::Item> {
                self.indices.get(self.i).map(|&index| {
                    self.i += 1;
    
                    unsafe { std::mem::transmute(&mut self.data[index]) }
                })
            }
        }
    }
    

    请注意,如果一个索引超出范围,您的代码会出现恐慌。

    【讨论】:

    • 我还简化了您的next 方法。
    【解决方案2】:

    使用unsafe

    提醒:在任何时候拥有两个对同一基础值的可访问的可变引用是不合理的。

    问题的症结在于语言不能保证代码遵守上述规则,如果indices 包含任何重复项,那么实现的迭代器将允许同时获得对切片中同一项目的两个可变引用,这是不合理的。

    当语言无法自行做出保证时,您要么需要寻找替代方法,要么需要尽职尽责,然后使用unsafe

    在这种情况下,在Playground

    impl<'a, D> LookupIterMut<'a, D> {
        pub fn new(data: &'a mut [D], indices: &'a [usize]) -> Self {
            let set: HashSet<usize> = indices.iter().copied().collect();
            assert!(indices.len() == set.len(), "Duplicate indices!");
    
            Self { data, indices, i: 0 }
        }
    }
    
    impl<'a, D> Iterator for LookupIterMut<'a, D> {
        type Item = &'a mut D;
    
        fn next(&mut self) -> Option<Self::Item> {
            if self.i >= self.indices.len() {
                None
            } else {
                let index = self.indices[self.i];
                assert!(index < self.data.len());
    
                self.i += 1;
    
                //  Safety:
                //  -   index is guaranteed to be within bounds.
                //  -   indices is guaranteed not to contain duplicates.
                Some(unsafe { &mut *self.data.as_mut_ptr().offset(index as isize) })
            }
        }
    }
    

    就性能而言,构造函数中HashSet 的构造相当不令人满意,但实际上无法避免一般。例如,如果保证indices 已排序,则可以在不分配的情况下执行检查。

    【讨论】:

      猜你喜欢
      • 2017-07-03
      • 1970-01-01
      • 1970-01-01
      • 2021-10-06
      • 2020-06-20
      • 2017-12-06
      • 2013-06-30
      • 2023-03-20
      • 2015-07-23
      相关资源
      最近更新 更多