在可变引用上实现迭代器是hard一般来说。如果迭代器两次返回对同一元素的引用,那么它就会变得不合理。这意味着,如果您想用纯粹安全的代码编写一个元素,则必须以某种方式说服编译器每个元素仅被访问一次。这就排除了简单地使用索引的情况:您可能总是忘记增加索引或将其设置在某处,而编译器将无法对其进行推理。
一种可能的解决方法是将多个链接在一起std::iter::once
s(一个对应于您想要迭代的每个引用)。
例如,
impl StatusRegister {
fn iter_mut(&mut self) -> impl Iterator<Item = &mut bool> {
use std::iter::once;
once(&mut self.CarryFlag)
.chain(once(&mut self.ZeroFlag))
.chain(once(&mut self.OverflowFlag))
}
}
Upsides:
缺点:
- 迭代器有一个非常复杂的类型:
std::iter::Chain<std::iter::Chain<std::iter::Once<&mut bool>, std::iter::Once<&mut bool>>, std::iter::Once<&mut bool>>
.
所以你如果不想使用impl Iterator<Item = &mut bool>
,你必须在你的代码中包含它。这包括实施IntoIterator
for &mut StatusRegister
,因为你必须明确指出IntoIter
类型是。
另一种方法是使用数组或Vec
保存所有可变引用(具有正确的生命周期),然后委托给其迭代器实现来获取值。例如,
impl StatusRegister {
fn iter_mut(&mut self) -> std::vec::IntoIter<&mut bool> {
vec![
&mut self.CarryFlag,
&mut self.ZeroFlag,
&mut self.OverflowFlag,
]
.into_iter()
}
}
Upsides:
- 该类型更易于管理
std::vec::IntoIter<&mut bool>
.
- 实施起来仍然相当简单。
- 没有外部依赖。
缺点:
我还提到使用数组。这将避免分配,但事实证明数组还没有 https://github.com/rust-lang/rust/pull/65819对它们的值实现迭代器,因此上面的代码带有[&mut bool; 3]
代替Vec<&mut bool>
行不通的。然而,存在一些板条箱可以为大小有限的固定长度数组实现此功能,例如arrayvec https://docs.rs/arrayvec/0.5.1/arrayvec/ (or array_vec https://docs.rs/array-vec/0.1.3/array_vec/).
Upsides:
缺点:
我要讨论的最后一种方法是使用unsafe
。由于与其他方法相比,这种方法没有太多优点,因此我一般不推荐它。这主要是为了向您展示如何could实施这一点。
就像您的原始代码一样,我们将实现Iterator
在我们自己的结构上。
impl<'a> IntoIterator for &'a mut StatusRegister {
type IntoIter = StatusRegisterIterMut<'a>;
type Item = &'a mut bool;
fn into_iter(self) -> Self::IntoIter {
StatusRegisterIterMut {
status: self,
index: 0,
}
}
}
pub struct StatusRegisterIterMut<'a> {
status: &'a mut StatusRegister,
index: usize,
}
不安全感来自于next
方法,我们必须(本质上)转换某种类型&mut &mut T
to &mut T
,这通常是不安全的。但是,只要我们保证next
不允许给这些可变引用起别名,我们应该没问题。可能还有一些其他微妙的问题,所以我不能保证这是正确的。无论如何,MIRI 并没有发现这方面有任何问题。
impl<'a> Iterator for StatusRegisterIterMut<'a> {
type Item = &'a mut bool;
// Invariant to keep: index is 0, 1, 2 or 3
// Every call, this increments by one, capped at 3
// index should never be 0 on two different calls
// and similarly for 1 and 2.
fn next(&mut self) -> Option<Self::Item> {
let result = unsafe {
match self.index {
// Safety: Since each of these three branches are
// executed exactly once, we hand out no more than one mutable reference
// to each part of self.status
// Since self.status is valid for 'a
// Each partial borrow is also valid for 'a
0 => &mut *(&mut self.status.CarryFlag as *mut _),
1 => &mut *(&mut self.status.ZeroFlag as *mut _),
2 => &mut *(&mut self.status.OverflowFlag as *mut _),
_ => return None
}
};
// If self.index isn't 0, 1 or 2, we'll have already returned
// So this bumps us up to 1, 2 or 3.
self.index += 1;
Some(result)
}
}
Upsides:
缺点:
- 实施起来很复杂。才能成功使用
unsafe
,您需要非常熟悉什么是允许的,什么是不允许的。到目前为止,这部分答案花了我最长的时间来确保我没有做错什么。
- 不安全会影响模块。在定义此迭代器的模块中,我可以通过弄乱
status
or index
的领域StatusRegisterIterMut
。唯一允许封装的是,在该模块之外,这些字段不可见。