如何在结构字段上创建可变迭代器
How do I create mutable iterator over struct fields
所以我正在使用 Rust 开发一个小型 NES 模拟器,并且我正在尝试使用我的状态寄存器。寄存器是一个结构,其中包含一些包含布尔值的字段(标志),寄存器本身是 CPU 结构的一部分。现在,我想遍历这些字段并根据我执行的一些指令设置 bool 值。但是,我无法实现可变迭代器,我已经实现了一个 into_iter() 函数并且能够遍历字段到 get/print 一个 bool 值但是我如何在结构本身?这可能吗?
pub struct StatusRegister {
CarryFlag: bool,
ZeroFlag: bool,
OverflowFlag: bool,
}
impl StatusRegister {
fn new() -> Self {
StatusRegister {
CarryFlag: true,
ZeroFlag: false,
OverflowFlag: true,
}
}
}
impl<'a> IntoIterator for &'a StatusRegister {
type Item = bool;
type IntoIter = StatusRegisterIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
StatusRegisterIterator {
status: self,
index: 0,
}
}
}
pub struct StatusRegisterIterator<'a> {
status: &'a StatusRegister,
index: usize,
}
impl<'a> Iterator for StatusRegisterIterator<'a> {
type Item = bool;
fn next(&mut self) -> Option<bool> {
let result = match self.index {
0 => self.status.CarryFlag,
1 => self.status.ZeroFlag,
2 => self.status.OverflowFlag,
_ => return None,
};
self.index += 1;
Some(result)
}
}
pub struct CPU {
pub memory: [u8; 0xffff],
pub status: StatusRegister,
}
impl CPU {
pub fn new() -> CPU {
let memory = [0; 0xFFFF];
CPU {
memory,
status: StatusRegister::new(),
}
}
fn execute(&mut self) {
let mut shifter = 0b1000_0000;
for status in self.status.into_iter() {
//mute status here!
println!("{}", status);
shifter <<= 1;
}
}
}
fn main() {
let mut cpu = CPU::new();
cpu.execute();
}
在可变引用上实现迭代器通常是困难。如果迭代器 returns 两次引用同一个元素,它就会变得不可靠。这意味着如果你想用纯粹安全的代码编写一个,你必须以某种方式让编译器相信每个元素只被访问一次。这排除了简单地使用索引的可能性:您总是可能忘记增加索引或将其设置在某处,而编译器将无法对其进行推理。
一种可能的解决方法是将多个 std::iter::once
链接在一起(一个用于您要迭代的每个引用)。
例如,
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))
}
}
优点:
- 实现起来相当简单。
- 没有分配。
- 没有外部依赖。
缺点:
- 迭代器有一个非常复杂的类型:
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>
,则必须在您的代码中包含它。这包括为 &mut StatusRegister
实施 IntoIterator
,因为您必须明确指出 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()
}
}
优点:
- 类型更易于管理
std::vec::IntoIter<&mut bool>
。
- 实施起来仍然相当简单。
- 没有外部依赖。
缺点:
- 每次调用
iter_mut
时都需要分配。
我还提到了使用数组。这将避免分配,但事实证明数组 don't yet implement an iterator over their values, so the above code with a [&mut bool; 3]
instead of a Vec<&mut bool>
won't work. However, there exist crates that implement this functionality for fixed-length arrays with limited size, e.g. arrayvec
(or array_vec
).
优点:
- 没有分配。
- 简单迭代器类型。
- 易于实施。
缺点:
- 外部依赖。
我要谈的最后一种方法是使用 unsafe
。由于与其他方法相比,这没有太多好处,所以我一般不会推荐它。这主要是为了向您展示如何可以实现它。
像您的原始代码一样,我们将在我们自己的结构上实现 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
类型的内容转换为 &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)
}
}
优点:
- 没有分配。
- 简单迭代器类型名称。
- 没有外部依赖。
缺点:
- 实施起来很复杂。要成功使用
unsafe
,您需要非常熟悉什么是允许的,什么是不允许的。到目前为止,这部分答案花了我最长的时间来确保我没有做错什么。
- 不安全因素感染模块。在定义这个迭代器的模块中,我可以通过弄乱
StatusRegisterIterMut
的 status
或 index
字段来 "safely" 导致不健全。唯一允许封装的是在这个模块之外,那些字段是不可见的。
所以我正在使用 Rust 开发一个小型 NES 模拟器,并且我正在尝试使用我的状态寄存器。寄存器是一个结构,其中包含一些包含布尔值的字段(标志),寄存器本身是 CPU 结构的一部分。现在,我想遍历这些字段并根据我执行的一些指令设置 bool 值。但是,我无法实现可变迭代器,我已经实现了一个 into_iter() 函数并且能够遍历字段到 get/print 一个 bool 值但是我如何在结构本身?这可能吗?
pub struct StatusRegister {
CarryFlag: bool,
ZeroFlag: bool,
OverflowFlag: bool,
}
impl StatusRegister {
fn new() -> Self {
StatusRegister {
CarryFlag: true,
ZeroFlag: false,
OverflowFlag: true,
}
}
}
impl<'a> IntoIterator for &'a StatusRegister {
type Item = bool;
type IntoIter = StatusRegisterIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
StatusRegisterIterator {
status: self,
index: 0,
}
}
}
pub struct StatusRegisterIterator<'a> {
status: &'a StatusRegister,
index: usize,
}
impl<'a> Iterator for StatusRegisterIterator<'a> {
type Item = bool;
fn next(&mut self) -> Option<bool> {
let result = match self.index {
0 => self.status.CarryFlag,
1 => self.status.ZeroFlag,
2 => self.status.OverflowFlag,
_ => return None,
};
self.index += 1;
Some(result)
}
}
pub struct CPU {
pub memory: [u8; 0xffff],
pub status: StatusRegister,
}
impl CPU {
pub fn new() -> CPU {
let memory = [0; 0xFFFF];
CPU {
memory,
status: StatusRegister::new(),
}
}
fn execute(&mut self) {
let mut shifter = 0b1000_0000;
for status in self.status.into_iter() {
//mute status here!
println!("{}", status);
shifter <<= 1;
}
}
}
fn main() {
let mut cpu = CPU::new();
cpu.execute();
}
在可变引用上实现迭代器通常是困难。如果迭代器 returns 两次引用同一个元素,它就会变得不可靠。这意味着如果你想用纯粹安全的代码编写一个,你必须以某种方式让编译器相信每个元素只被访问一次。这排除了简单地使用索引的可能性:您总是可能忘记增加索引或将其设置在某处,而编译器将无法对其进行推理。
一种可能的解决方法是将多个 std::iter::once
链接在一起(一个用于您要迭代的每个引用)。
例如,
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))
}
}
优点:
- 实现起来相当简单。
- 没有分配。
- 没有外部依赖。
缺点:
- 迭代器有一个非常复杂的类型:
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>
,则必须在您的代码中包含它。这包括为 &mut StatusRegister
实施 IntoIterator
,因为您必须明确指出 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()
}
}
优点:
- 类型更易于管理
std::vec::IntoIter<&mut bool>
。 - 实施起来仍然相当简单。
- 没有外部依赖。
缺点:
- 每次调用
iter_mut
时都需要分配。
我还提到了使用数组。这将避免分配,但事实证明数组 don't yet implement an iterator over their values, so the above code with a [&mut bool; 3]
instead of a Vec<&mut bool>
won't work. However, there exist crates that implement this functionality for fixed-length arrays with limited size, e.g. arrayvec
(or array_vec
).
优点:
- 没有分配。
- 简单迭代器类型。
- 易于实施。
缺点:
- 外部依赖。
我要谈的最后一种方法是使用 unsafe
。由于与其他方法相比,这没有太多好处,所以我一般不会推荐它。这主要是为了向您展示如何可以实现它。
像您的原始代码一样,我们将在我们自己的结构上实现 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
类型的内容转换为 &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)
}
}
优点:
- 没有分配。
- 简单迭代器类型名称。
- 没有外部依赖。
缺点:
- 实施起来很复杂。要成功使用
unsafe
,您需要非常熟悉什么是允许的,什么是不允许的。到目前为止,这部分答案花了我最长的时间来确保我没有做错什么。 - 不安全因素感染模块。在定义这个迭代器的模块中,我可以通过弄乱
StatusRegisterIterMut
的status
或index
字段来 "safely" 导致不健全。唯一允许封装的是在这个模块之外,那些字段是不可见的。