如何在结构域上创建可变的迭代器

问题描述 投票:0回答:1

因此,我正在使用Rust开发一个小型NES模拟器,我想着迷于我的状态寄存器。寄存器是一个结构,其中包含一些包含布尔的字段(标志),该寄存器本身是CPU结构的一部分。现在,我想遍历这些字段并根据我执行的某些指令设置布尔值。但是,不能实现可变的迭代器,我实现了into_iter()函数,并且可以遍历字段以获取/打印布尔值,但是如何在结构本身内对这些值进行突变?这甚至可能吗?

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();
}
struct rust reference iterator mutable
1个回答
0
投票

在可变引用上实现迭代器通常是[[hard”。如果迭代器两次对同一元素的引用返回两次,它将变得不合理。这意味着,如果您想用纯安全的代码编写代码,则必须以某种方式使编译器相信每个元素只能被访问一次。仅使用索引就可以排除这种情况:您总是会忘记增加索引或将其设置在某个位置,而编译器将无法对此进行推理。


一种可能的解决方法是将几个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)) } }

(playground)

优势:

    相当容易实现。
  • 没有分配。
  • 没有外部依赖性。
  • 缺点:
    • 迭代器的类型非常复杂: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实现&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() } }

(playground)

优势:

    类型是更易于管理的std::vec::IntoIter<&mut bool>
  • 仍然相当容易实现。
  • 没有外部依赖性。
  • 缺点:
    • [每次调用iter_mut时需要分配。
  • 我也提到使用数组。这样可以避免分配,但是事实证明数组don't yet在其值上实现了一个迭代器,因此上述带有[&mut bool; 3]而不是Vec<&mut bool>的代码将不起作用。但是,有一些板条箱可用于具有有限大小的定长阵列的这种功能,例如arrayvec(或arrayvec)。
  • 优势:

    未分配。
  • 简单的迭代器类型。
  • 易于实现。
  • 缺点:
    • 外部依赖性。

  • 我要讨论的最后一种方法是使用array_vec。由于与其他方法相比,这没有很多好处,因此一般而言,我不建议这样做。这主要是为了向您展示您如何[[可以
  • 实现此目标。

[就像您的原始代码一样,我们将在自己的结构上实现array_vecunsafe

不安全性来自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为这些可变引用加上别名,就可以了。可能还有其他一些细微的问题,所以我不能保证这是正确的。对于它的价值,MIRI对此没有发现任何问题。

&mut T

next

优势:

没有分配。

    简单迭代器类型名称。
  • 没有外部依赖性。
  • 缺点:
  • 实施起来很复杂。要成功使用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) } } ,您需要非常了解允许和不允许的内容。答案的这一部分花了我最长的时间,以确保我没有做错任何事情。

    不安全会感染模块。在定义此迭代器的模块中,我可以通过弄乱(playground)unsafestatus字段来“安全”地导致声音不佳。唯一允许封装的是,在此模块之外,这些字段不可见。
© www.soinside.com 2019 - 2024. All rights reserved.