如何实现支持可变迭代器的容器?
How do I implement a container with support for a mutable iterator?
我想设计一个支持可变迭代器的玩具容器 class,但我在整理迭代器的生命周期及其对容器的引用时遇到问题。
我尝试创建一个最小的非编译示例:
struct Payload {
value: i32,
}
struct Container {
val: Payload,
}
struct IterMut<'a> {
cont: &'a mut Container,
cnt: i32,
}
impl<'a> Container {
fn new() -> Container {
Container { val: Payload { value: 42 } }
}
fn iter_mut(&'a mut self) -> IterMut<'a> {
IterMut {
cont: self,
cnt: 10,
}
}
}
impl<'a> Iterator for IterMut<'a> {
type Item = &'a mut Payload;
fn next<'b>(&'b mut self) -> Option<Self::Item> {
self.cnt -= 1;
if self.cnt < 0 {
return None;
} else {
Some(&mut self.cont.val)
}
}
}
fn main() {
let mut cont = Container::new();
let mut it = cont.iter_mut();
it.next();
}
上面的内容旨在实现一个真正的愚蠢容器,当使用 iter_mut()
迭代时,相同的项目 returns 10 次。
我不知道如何实施 Iterator::next
。
我确实设法编写了一个常规函数,它实现了与我想要的语义相同的语义 next
:
fn manual_next<'a, 'b>(i: &'a mut IterMut<'b>) -> Option<&'a mut Payload> {
i.cnt -= 1;
if i.cnt < 0 {
return None;
} else {
Some(&mut i.cont.val)
}
}
这没有用,因为我无法调整它来实现 Iterator::next
,并且如果不实现 Iterator
,我的容器就无法在 for 循环中迭代,我想要的。
不可能按原样实现迭代器,因为它允许您获得对同一项目的多个可变引用,这违反了 Rust 的 aliasing/borrowing 规则。幸好借用检查员发现了错误! :-)
例如,扩展您的 main
示例:
fn main() {
let mut cont = Container::new();
let mut it = cont.iter_mut();
let alias_1 = it.next();
let alias_2 = it.next();
// alias_1 and alias_2 both would have mutable references to cont.val!
}
其他iter_mut
迭代器(例如vectors/slices上的一个迭代器)return在每一步引用不同的项目,所以没有这个问题。
如果你真的需要迭代逻辑上可变的东西,你可以不可变地迭代,但通过 RefCell
or Cell
.
使用内部可变性
manual_next
函数编译的原因是你不受 Iterator::next
签名的限制,实际上调用一次是完全安全的(如果你不保持结果)。但是,如果您尝试保存结果,它会保留可变借用的 IterMut
并且您无法再次调用它:
let mut cont = Container::new();
let mut it = cont.iter_mut();
let x = manual_next(&mut it);
manual_next(&mut it); // Error: `it` is still borrowed mutably
相比之下,Iterator::next
有一种类型可以使 collect
进入向量成为可能。
我想设计一个支持可变迭代器的玩具容器 class,但我在整理迭代器的生命周期及其对容器的引用时遇到问题。
我尝试创建一个最小的非编译示例:
struct Payload {
value: i32,
}
struct Container {
val: Payload,
}
struct IterMut<'a> {
cont: &'a mut Container,
cnt: i32,
}
impl<'a> Container {
fn new() -> Container {
Container { val: Payload { value: 42 } }
}
fn iter_mut(&'a mut self) -> IterMut<'a> {
IterMut {
cont: self,
cnt: 10,
}
}
}
impl<'a> Iterator for IterMut<'a> {
type Item = &'a mut Payload;
fn next<'b>(&'b mut self) -> Option<Self::Item> {
self.cnt -= 1;
if self.cnt < 0 {
return None;
} else {
Some(&mut self.cont.val)
}
}
}
fn main() {
let mut cont = Container::new();
let mut it = cont.iter_mut();
it.next();
}
上面的内容旨在实现一个真正的愚蠢容器,当使用 iter_mut()
迭代时,相同的项目 returns 10 次。
我不知道如何实施 Iterator::next
。
我确实设法编写了一个常规函数,它实现了与我想要的语义相同的语义 next
:
fn manual_next<'a, 'b>(i: &'a mut IterMut<'b>) -> Option<&'a mut Payload> {
i.cnt -= 1;
if i.cnt < 0 {
return None;
} else {
Some(&mut i.cont.val)
}
}
这没有用,因为我无法调整它来实现 Iterator::next
,并且如果不实现 Iterator
,我的容器就无法在 for 循环中迭代,我想要的。
不可能按原样实现迭代器,因为它允许您获得对同一项目的多个可变引用,这违反了 Rust 的 aliasing/borrowing 规则。幸好借用检查员发现了错误! :-)
例如,扩展您的 main
示例:
fn main() {
let mut cont = Container::new();
let mut it = cont.iter_mut();
let alias_1 = it.next();
let alias_2 = it.next();
// alias_1 and alias_2 both would have mutable references to cont.val!
}
其他iter_mut
迭代器(例如vectors/slices上的一个迭代器)return在每一步引用不同的项目,所以没有这个问题。
如果你真的需要迭代逻辑上可变的东西,你可以不可变地迭代,但通过 RefCell
or Cell
.
manual_next
函数编译的原因是你不受 Iterator::next
签名的限制,实际上调用一次是完全安全的(如果你不保持结果)。但是,如果您尝试保存结果,它会保留可变借用的 IterMut
并且您无法再次调用它:
let mut cont = Container::new();
let mut it = cont.iter_mut();
let x = manual_next(&mut it);
manual_next(&mut it); // Error: `it` is still borrowed mutably
相比之下,Iterator::next
有一种类型可以使 collect
进入向量成为可能。