如何在结构字段上创建可变迭代器

2024-04-09

所以我正在使用 Rust 开发一个小型 NES 模拟器,并且我正在尝试使用我的状态寄存器。寄存器是一个结构体,其中包含一些包含布尔值的字段(标志),寄存器本身是 CPU 结构体的一部分。现在,我想循环这些字段并根据我执行的某些指令设置布尔值。但是,我无法实现可变迭代器,我已经实现了 into_iter() 函数,并且能够迭代字段以获取/打印 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();
}

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


一种可能的解决方法是将多个链接在一起std::iter::onces(一个对应于您想要迭代的每个引用)。

例如,

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>.
  • 实施起来仍然相当简单。
  • 没有外部依赖。

缺点:

  • 每次都需要分配iter_mut叫做。

我还提到使用数组。这将避免分配,但事实证明数组还没有 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。唯一允许封装的是,在该模块之外,这些字段不可见。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在结构字段上创建可变迭代器 的相关文章

随机推荐

  • SharedPreferences.edit() 没有相应的 commit() 或 apply() 调用

    我正在将 SharedPreferences 用于我的应用程序的介绍滑块 但是 我在这一行收到错误 class PrefManager private SharedPreferences pref private SharedPrefere
  • Jquery fadeIn 导致滚动顶部,我该如何解决?

    我对 jQuery fadeIn 或 fadeOut 方法有疑问 我建立了一个文章旋转器 一切正常 但是当页面滚动到底部并且文章旋转时出现问题 fadeIn 或fadeOut 方法导致滚动到文章位置 我认为这些方法改变了body的css t
  • 如何在后台线程中创建和使用WebBrowser?

    如何在后台 STA 线程中创建 System Windows Forms WebBrowser 我尝试使用这样的代码 var tr new Thread wbThread tr SetApartmentState ApartmentStat
  • 在控制台上用Java打印金字塔

    我怎样才能像这样在Java中打印金字塔 1 23 456 78910 我当前的代码如下所示 public class T public static void main String args int i j num 1 int n Int
  • 使用spring构建Web项目时如何修复MojoFailureException

    最近我使用spring STS和roo 1 2 0 M1来构建一个web项目 我设置了 jpa 并创建了一个具有某些字段的实体 并为该实体创建了一个存储库和一个服务层 然后当我执行测试时 它给出了以下错误 roo gt perform te
  • MongoDB 查询速度过慢,即使查询很简单并且与索引对齐

    我正在运行一个 MongoDB 服务器 这实际上就是它运行的全部内容 该服务器拥有 64GB RAM 和 16 个内核 以及 2TB 硬盘空间 文档结构 数据库有一个集合domains拥有大约 2000 万份文档 每个文档中都有相当多的数据
  • SQL JOIN 查询编写

    我正在尝试编写一个涉及两个表的简单查询 人 表有其独特之处person id and a name 朋友 表有一个person id and a friend id这是一个 FK 到person id在人员表中 person
  • StringBuilder 真的比连接十几个字符串慢吗?

    StringBuilder 真的比连接十几个字符串慢吗 编译器如何优化字符串连接 使得使用 连接十几个字符串会比StringBuilder更好 从一本 本 沃森写的 书中说 字符串连接 用于简单连接已知的 在编译时 time 字符串数量 只
  • 在AngularJS控制器中转换日期格式

    我编写以下代码来打印当前日期时间 scope date new Date 然后我使用 console log 打印相同的内容 console log scope date 它工作正常 2017 年 1 月 24 日星期二 16 36 06
  • 带点运算符的常数 (VBA)

    我想要一个常量材料的目录 这样我就可以使用如下所示的代码 Dim MyDensity MySymbol MyDensity ALUMINUM Density MySymbol ALUMINUM Symbol 显然 铝的密度和符号预计不会改变
  • WP - 检查 wp_insert_post 之前是否存在帖子

    如何检查 wp insert post 之前的帖子是否存在以避免重复 如果我删除 if 语句 post exists 并继续重新插入产生多个重复的帖子 下面的代码就可以工作 我用 post exists 编写了 if 语句 只是为了开始实现
  • 如何禁用 phpmailer 在浏览器上显示配置详细信息

    我不知道为什么电子邮件配置详细信息和状态在成功发送电子邮件后显示在我的页面上 如何禁用它 消息 服务器 gt 客户端 220 mx google com ESMTP ss3sm18138445pab 43 gsmtp客户端 gt 服务器 E
  • 如何识别用户空间和内核空间之间的特定套接字?

    我在用户空间中有一个库 可以拦截套接字层调用 例如socket connect accept 等等 我只处理 TCP 套接字 在内核空间中 我有一个网络内核模块 它处理所有 TCP 连接 我需要能够在驱动程序中识别哪些套接字被用户空间库拦截
  • VIM 高亮匹配开始/结束

    我正在尝试找到一个插件 它将突出显示与 Verilog 匹配的开始 结束语句 VIM 可以使用花括号 方括号 但不能使用它的开始 结束 我希望 VIM 突出显示正确的开始到正确的结束 在我看来 最好的选择是使用 matchit 该脚本是 v
  • 如何在 docker exec bash 命令中转义引号

    我正在尝试在 docker 中执行 bash 命令 但我无法弄清楚如何转义表名周围的单引号user mappings在我的指挥下 我尝试过反斜杠 双单引号和双引号单引号 我知道如果我不将命令包装起来我可以绕过它bash c 但我需要它 以便
  • 使用 CA 文件从 Azure Database for MySQL 进行 SSL 连接

    跟随新 MySQL 服务的 Azure 文档 https learn microsoft com en us azure mysql howto configure ssl 看来您可以使用他们的受信任的 CA 证书非常简单地设置 SSL 按
  • Pod 更新后出现许多构建错误

    我的项目中安装了很多 pod 自从我上次更新 Pod 以来 一切都运行良好 上次 pod 更新后 我遇到了 28 个以 无法构建模块 xxx 开头的构建错误 这是我的 Podfile target projectXXX do use fra
  • Node.js 监听 MongoDB 变化

    Node js 有没有办法监听 MongoDB 集合中特定数据的更改 并在发生更改时触发事件 嗯 这是一个老问题 但我也在为同样的事情而苦苦挣扎 我发现了一些花絮帮助我构建了一个解决方案 并且我已将其作为库发布 https github c
  • document.head v. document.getElementsByTagName("head")[0]

    使用有什么区别document head并使用document getElementsByTagName head 0 我运行的测试表明它们都需要大约一毫秒 我也见过 document head document getElementsBy
  • 如何在结构字段上创建可变迭代器

    所以我正在使用 Rust 开发一个小型 NES 模拟器 并且我正在尝试使用我的状态寄存器 寄存器是一个结构体 其中包含一些包含布尔值的字段 标志 寄存器本身是 CPU 结构体的一部分 现在 我想循环这些字段并根据我执行的某些指令设置布尔值