简体   繁体   English

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

[英]How do I create mutable iterator over struct fields

So I am working on a little NES emulator using Rust and I am trying to be fancy with my status register.因此,我正在使用 Rust 开发一个小型 NES 模拟器,并且我正在尝试使用我的状态寄存器。 The register is a struct that holds some fields (flags) that contain a bool, the register itself is part of a CPU struct.寄存器是一个结构,它包含一些包含布尔值的字段(标志),寄存器本身是 CPU 结构的一部分。 Now, I want to loop through these fields and set the bool values based on some instruction I execute.现在,我想遍历这些字段并根据我执行的一些指令设置布尔值。 However, am not able to implement a mutable iterator, I've implemented an into_iter() function and are able to iterate through the fields to get/print a bool value but how do I mutate these values within the struct itself?但是,我无法实现可变迭代器,我已经实现了 into_iter() function 并且能够遍历字段以获取/打印 bool 值,但是如何在结构本身中改变这些值? Is this even possible?这甚至可能吗?

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();
}

Implementing an iterator over mutable references is hard in general.在可变引用上实现迭代器通常很难 It becomes unsound if the iterator ever returns references to the same element twice.如果迭代器曾经两次返回对同一元素的引用,它就会变得不健全。 That means that if you want to write one in purely safe code, you have to somehow convince the compiler that each element is only visited once.这意味着如果你想用纯安全的代码编写一个,你必须以某种方式让编译器相信每个元素只被访问一次。 That rules out simply using an index: you could always forget to increment the index or set it somewhere and the compiler wouldn't be able to reason about it.这排除了简单地使用索引:您总是可能忘记增加索引或将其设置在某处,编译器将无法推断它。


One possible way around is chaining together several std::iter::once s (one for each reference you want to iterate over).一种可能的解决方法是将几个std::iter::once链接在一起(每个要迭代的引用一个)。

For example,例如,

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) (操场)

Upsides:优点:

  • Fairly simple to implement.实施起来相当简单。
  • No allocations.没有分配。
  • No external dependencies.没有外部依赖。

Downsides:缺点:

  • The iterator has a very complicated type: std::iter::Chain<std::iter::Chain<std::iter::Once<&mut bool>, std::iter::Once<&mut bool>>, std::iter::Once<&mut bool>> .迭代器有一个非常复杂的类型: std::iter::Chain<std::iter::Chain<std::iter::Once<&mut bool>, std::iter::Once<&mut bool>>, std::iter::Once<&mut bool>>

So you if don't want to use impl Iterator<Item = &mut bool> , you'll have to have that in your code.因此,如果您不想使用impl Iterator<Item = &mut bool> ,则必须在代码中使用它。 That includes implementing IntoIterator for &mut StatusRegister , since you'd have to explicitly indicate what the IntoIter type is.这包括为&mut StatusRegister实现IntoIterator ,因为您必须明确指出IntoIter类型是什么。


Another approach is using an array or Vec to hold all the mutable references (with the correct lifetime) and then delegate to its iterator implementation to get the values.另一种方法是使用数组或Vec来保存所有可变引用(具有正确的生命周期),然后委托给它的迭代器实现以获取值。 For example,例如,

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) (操场)

Upsides:优点:

  • The type is the much more manageable std::vec::IntoIter<&mut bool> .该类型是更易于管理的std::vec::IntoIter<&mut bool>
  • Still fairly simple to implement.实现起来还是相当简单的。
  • No external dependencies.没有外部依赖。

Downsides:缺点:

  • Requires an allocation every time iter_mut is called.每次调用iter_mut时都需要分配。

I also mentioned using an array.我还提到了使用数组。 That would avoid the allocation, but it turns out that arrays don't yet implement an iterator over their values, so the above code with a [&mut bool; 3]这将避免分配,但事实证明 arrays尚未对其值实现迭代器,因此上面的代码带有[&mut bool; 3] [&mut bool; 3] instead of a Vec<&mut bool> won't work. [&mut bool; 3]而不是Vec<&mut bool>将不起作用。 However, there exist crates that implement this functionality for fixed-length arrays with limited size, eg arrayvec (or array_vec ).但是,存在为大小有限的固定长度 arrays 实现此功能的板条箱,例如arrayvec (或array_vec )。

Upsides:优点:

  • No allocation.没有分配。
  • Simple iterator type.简单的迭代器类型。
  • Simple to implement.实施简单。

Downsides:缺点:

  • External dependency.外部依赖。

The last approach I'll talk about is using unsafe .我要讨论的最后一种方法是使用unsafe Since this doesn't have many good upsides over the other approaches, I wouldn't recommend it in general.由于与其他方法相比,这没有太多好处,因此我一般不会推荐它。 This is mainly to show you how you could implement this.这主要是向您展示如何实现这一点。

Like your original code, we'll implement Iterator on our own struct.像您的原始代码一样,我们将在我们自己的结构上实现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,
}

The unsafety comes from the next method, where we'll have to (essentially) convert something of type &mut &mut T to &mut T , which is generally unsafe.不安全来自next方法,我们必须(本质上)将&mut &mut T类型的东西转换为&mut T ,这通常是不安全的。 However, as long as we ensure that next isn't allowed to alias these mutable references, we should be fine.但是,只要我们确保不允许next为这些可变引用设置别名,我们就可以了。 There may be some other subtle issues, so I won't guarantee that this is sound.可能还有其他一些微妙的问题,所以我不能保证这是正确的。 For what it's worth, MIRI doesn't find any problems with this.对于它的价值,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)
    }
}

(playground) (操场)

Upsides:优点:

  • No allocations.没有分配。
  • Simple iterator type name.简单的迭代器类型名称。
  • No external dependencies.没有外部依赖。

Downsides:缺点:

  • Complicated to implement.实施复杂。 To successfully use unsafe , you need to be very familiar with what is and isn't allowed.要成功使用unsafe ,您需要非常熟悉什么是允许的,什么是不允许的。 This part of the answer took me the longest by far to make sure I wasn't doing something wrong.到目前为止,这部分答案花了我最长的时间来确保我没有做错什么。
  • Unsafety infects the module.不安全会感染模块。 Within the module defining this iterator, I could "safely" cause unsoundness by messing with the status or index fields of StatusRegisterIterMut .在定义此迭代器的模块中,我可以通过弄乱StatusRegisterIterMutstatusindex字段来“安全地”导致不健全。 The only thing allowing encapsulation is that outside of this module, those fields aren't visible.唯一允许封装的是在这个模块之外,这些字段是不可见的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 我怎样才能使某些结构域可变? - How can I make only certain struct fields mutable? c ++如何在结构向量的一个字段上创建迭代器 - c++ how to create iterator over one field of a struct vector Julia struct中的可变字段 - mutable fields in Julia struct 如何在容器项的字段上创建迭代器? - How can I make an iterator over fields of container's items? 如何在Go中循环遍历结构切片? - How do I loop over a struct slice in Go? 不可变结构比可变结构有什么好处? - What are the benefits of an immutable struct over a mutable one? 如何确保首先解析根结构的文件,然后解析嵌入式结构的字段 - How do I make sure that the fileds of the root struct get parsed first and then the fields of embedded struct gets parsed 如何从字符串创建迭代器并_将其存储在结构中? - How to create an iterator from string _and_ store it in a struct? 如何遍历结构数组以检查字符串输入是否等于结构数组值 - How do I iterate over a struct array to check if string input is equal to a struct array value 声明要作为参数传递的临时结构-结构被覆盖。 如何在新的内存位置声明该结构? - Declaring temporary struct to pass as argument — Struct gets written over. How do I declare the struct in a new memory location?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM