首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在struct字段上创建可变迭代器

如何在struct字段上创建可变迭代器
EN

Stack Overflow用户
提问于 2020-05-23 21:41:42
回答 1查看 1.6K关注 0票数 4

所以我正在使用Rust开发一个小型NES仿真器,并且我正在尝试使用我的状态寄存器。寄存器是一个包含包含bool的字段(标志)的结构,寄存器本身是CPU结构的一部分。现在,我想循环这些字段,并根据我执行的一些指令设置bool值。但是,我无法实现可变迭代器,我已经实现了一个into_iter()函数,并且能够遍历这些字段来获得/打印bool值,但是如何在结构本身中修改这些值呢?这有可能吗?

代码语言:javascript
复制
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();
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-05-24 01:01:22

通常,在可变引用上实现迭代器是很困难的。如果迭代器两次返回对同一元素的引用,就会变得不健全。这意味着,如果您想要用纯安全的代码编写一个元素,您必须以某种方式说服编译器,每个元素只被访问一次。这排除了仅仅使用索引的可能性:您可能总是忘记在某个地方增加索引或设置索引,编译器将无法对其进行推理。

一种可能的方法是将几个std::iter::once连接在一起(每个要迭代的引用都有一个)。

例如,

代码语言:javascript
复制
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实现&mut StatusRegister,因为您必须显式地指示IntoIter类型。

另一种方法是使用数组或Vec保存所有可变引用(具有正确的生存期),然后委托其迭代器实现来获取值。例如,

代码语言:javascript
复制
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时都需要分配。

我还提到了使用数组。这将避免分配,但结果是数组还没到在其值上实现了迭代器,因此上面使用[&mut bool; 3]而不是Vec<&mut bool>的代码将无法工作。但是,有一些板条箱为有限大小的固定长度数组(如arrayvec (或array_vec) )实现了此功能。

优点:

  • 没有分配。
  • 简单迭代器类型。
  • 易于实现。

缺点:

  • 外部依赖。

我将讨论的最后一种方法是使用unsafe。由于与其他方法相比,这没有多少好的优点,所以我一般不推荐它。这主要是为了向您展示如何实现这一点。

与您的原始代码一样,我们将在自己的结构上实现Iterator

代码语言:javascript
复制
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没有发现任何问题。

代码语言:javascript
复制
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,您需要非常熟悉什么是允许的,什么是不允许的。这部分的答案花了我最长的时间来确保我没有做错什么事。
  • 不安全会感染模块。在定义此迭代器的模块中,我可以“安全”地处理statusindex字段的StatusRegisterIterMut。唯一允许封装的是,在这个模块之外,这些字段是不可见的。
票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61978903

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档