如何实现迭代器产生可变引用

时间:2020-02-05 09:17:30

标签: rust

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

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(&'a mut self) -> ..,但这不再是Iterator。

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

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

感谢您的帮助

2 个答案:

答案 0 :(得分:3)

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

要使这种方法正常工作,对于每个返回的Item,您将需要不同的生存期,这样就不会对同一切片包含2个可变引用。您暂时无法执行此操作,因为它需要通用关联类型:

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

一种解决方案是检查索引是否唯一,并在'a块中将引用项的生存期重新绑定到unsafe。这是安全的,因为所有索引都是唯一的,因此用户不能持有对同一项目的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]) }
            })
        }
    }
}

请注意,如果一个索引超出范围,您的代码将崩溃。

答案 1 :(得分: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进行排序,则可以在不分配的情况下执行检查。

相关问题