在 Rust 中,我如何创建一个可变迭代器?

10

我在尝试创建一个安全Rust的可变迭代器时,遇到了生命周期的困难。

以下是我将问题简化后得到的内容:

struct DataStruct<T> {
    inner: Box<[T]>,
}

pub struct IterMut<'a, T> {
    obj: &'a mut DataStruct<T>,
    cursor: usize,
}

impl<T> DataStruct<T> {
    fn iter_mut(&mut self) -> IterMut<T> {
        IterMut { obj: self, cursor: 0 }
    }
}

impl<'a, T> Iterator for IterMut<'a, T> {
    type Item = &'a mut T;

    fn next(&mut self) -> Option<Self::Item> {
        let i = f(self.cursor);
        self.cursor += 1;
        self.obj.inner.get_mut(i)
    }
}

fn f(i: usize) -> usize {
   // some permutation of i
}

我的数据结构的结构不会改变,但我需要能够修改其中存储的元素内容。例如,
let mut ds = DataStruct{ inner: vec![1,2,3].into_boxed_slice() };
for x in ds {
  *x += 1;
}

编译器提示我返回的引用存在生命周期冲突错误。它发现的生命周期不符合我预期的是next(&mut self)函数的作用域。
如果我试图在next()上注释生命周期,则编译器会告诉我未满足迭代器特性。在Safe Rust中是否可以解决这个问题?
以下是错误信息:
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/iter_mut.rs:25:24
   |
25 |         self.obj.inner.get_mut(i)
   |                        ^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 22:5...
  --> src/iter_mut.rs:22:5
   |
22 | /     fn next(&mut self) -> Option<Self::Item> {
23 | |         let i = self.cursor;
24 | |         self.cursor += 1;
25 | |         self.obj.inner.get_mut(i)
26 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/iter_mut.rs:25:9
   |
25 |         self.obj.inner.get_mut(i)
   |         ^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 19:6...
  --> src/iter_mut.rs:19:6
   |
19 | impl<'a, T> Iterator for IterMut<'a, T> {
   |      ^^
note: ...so that the types are compatible
  --> src/iter_mut.rs:22:46
   |
22 |       fn next(&mut self) -> Option<Self::Item> {
   |  ______________________________________________^
23 | |         let i = self.cursor;
24 | |         self.cursor += 1;
25 | |         self.obj.inner.get_mut(i)
26 | |     }
   | |_____^
   = note: expected  `std::iter::Iterator`
              found  `std::iter::Iterator`

编辑:

  • 更改了next()的实现方式,使得迭代顺序是原始序列的置换。

slice::IterMut之所以使用unsafe实现,正是出于这个原因。如果可以的话,我鼓励你创建一个别名或包装slice::IterMut,而不是编写自己的代码。 - trent
2
这只是我实际问题的简化,以便在StackOverflow上作为问题更容易理解。我希望有一种方法可以在不使用unsafe的情况下完成这个问题。 - Barnaby Dalton
你如何保证 self.cursor 永远不会是相同的值?如果答案是“除了调用 next() 之外没有改变 cursor 的方法”,那么为什么不能只是包装 slice::IterMut 呢? - trent
也许我简化得太多了。我的用例相当于 let i = f(self.cursor),其中 f 是索引的置换。 - Barnaby Dalton
1
所以你依赖于属性(fself.cursor[T] :: index你知道但编译器不知道,如果它们被破坏了,你的代码就会有未定义的行为,这正是你需要使用unsafe的时候。 - trent
1个回答

18

借用检查器无法证明后续调用next()不会访问相同的数据。这是个问题的原因是因为借用的生命周期与迭代器的生命周期持续一样长,所以它无法证明在同一时间内不会有两个可变引用指向相同的数据。

实际上没有办法解决这个问题而不使用不安全代码-或者更改您的数据结构。你可以做与slice::split_at_mut等价的事情,但是,鉴于你不能改变原始数据,你必须在不安全的代码中实现该方法。一个不安全的实现可能如下所示:

impl<'a, T> Iterator for IterMut<'a, T> {
    type Item = &'a mut T;

    fn next(&mut self) -> Option<Self::Item> {
        let i = self.cursor;
        self.cursor += 1;
        if i < self.obj.inner.len() {
            let ptr = self.obj.inner.as_mut_ptr();
            unsafe {
                Some(&mut *ptr.add(i))
            }
        } else {
            None
        }
    }
}

2
在得到这个答案之前,我对我违反的未定义行为感到困惑。这解释了问题以及解决方案。谢谢。 - Barnaby Dalton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接