在Rust结构中使用自引用封装顺序初始化状态

Dan*_* S. 11 rust

我正在尝试定义一个结构,它可以充当一个Vec在以下内容中保存的迭代器RefCell:

use std::slice::Iter;
use std::cell::Ref;
use std::cell::RefCell;

struct HoldsVecInRefCell {
    vec_in_refcell: RefCell<Vec<i32>>,
}

// TODO: struct HoldsVecInRefCellIter implementing Iterator ...

impl HoldsVecInRefCell {
    fn new() -> HoldsVecInRefCell {
        HoldsVecInRefCell { vec_in_refcell: RefCell::new(Vec::new()) }
    }

    fn add_int(&self, i: i32) {
        self.vec_in_refcell.borrow_mut().push(i);
    }

    fn iter(&self) -> HoldsVecInRefCellIter {
        // TODO ...
    }
}

fn main() {
    let holds_vec = HoldsVecInRefCell::new();
    holds_vec.add_int(1);
    holds_vec.add_int(2);
    holds_vec.add_int(3);

    let mut vec_iter = holds_vec.iter();  // Under the hood: run-time borrow check

    for i in vec_iter {
        println!("{}", i);
    }
}
Run Code Online (Sandbox Code Playgroud)

相比之下,vec_iter可以main()按如下方式在线初始化(故意冗长):

// Elided: lifetime parameter of Ref
let vec_ref: Ref<Vec<i32>> = holds_vec.vec_in_refcell.borrow();
// Elided: lifetime parameter of Iter
let mut vec_iter: Iter<i32> = vec_ref.iter();
Run Code Online (Sandbox Code Playgroud)

有没有办法定义一个实现的结构Iterator,它既包含Ref(保持不可变的RefCell借用值)和Iter(保持迭代器状态next(),而不是滚动我自己的迭代器Vec或其他任何容器),当第二个派生自(并持有从第一个获得的参考?

我已经尝试了几种方法来实现这一点,并且所有方法都与借用检查器发生冲突.如果我将这两个状态作为裸结构成员,就像

struct HoldsVecInRefCellIter<'a> {
    vec_ref: Ref<'a, Vec<i32>>,
    vec_iter: Iter<'a, i32>,
}
Run Code Online (Sandbox Code Playgroud)

那么我不能用HoldsVecInRefCellIter { ... }语法一次初始化两个字段(参见例如,Rust是否具有使用早期字段初始化struct字段的语法?).如果我尝试用类似的结构分流顺序初始化

struct HoldsVecInRefCellIter<'a> {
    vec_ref: Ref<'a, Vec<i32>>,
    vec_iter: Option<Iter<'a, i32>>,
}

// ...

impl HoldsVecInRefCell {
    // ...

    fn iter(&self) -> HoldsVecInRefCellIter {
        let mut new_iter = HoldsVecInRefCellIter { vec_ref: self.vec_in_refcell.borrow(), vec_iter: None };
        new_iter.vec_iter = new_iter.vec_ref.iter();
        new_iter
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我招致一个可变的自我借用的结构,阻止从它返回iter().如果您尝试在结构本身中存储对结构的一部分的引用,那么结构的自行借用也会发生(为什么我不能在同一结构中存储值和对该值的引用?),会阻止安全地移动结构的实例.相比之下,它看起来像一个结构HoldsVecInRefCellIter,如果你可以完成初始化,会在移动时做正确的事情,因为内部的所有引用都是在其他地方的数据超过了这个结构.

有一些技巧可以避免使用Rc(参见https://internals.rust-lang.org/t/self-referencing-structs/418/3中的示例)来创建自引用,但我不知道如何应用这些如果你想存储一个现有的Iterator结构,它实现为保存对底层容器的直接引用,而不是Rc.

作为一个来自C++的Rust新手,这感觉就像一个经常出现的问题("我在一个代码块中有一些复杂的状态初始化逻辑,我想抽象出那个逻辑并将结果状态保存在一个结构中使用").

相关问题: 在RefCell中返回Vec的迭代器

Fra*_*gné 6

我们不得不欺骗和欺骗生命.

use std::mem;

struct HoldsVecInRefCellIter<'a> {
    vec_ref: Ref<'a, Vec<i32>>,
    vec_iter: Iter<'a, i32>, // 'a is a lie!
}

impl HoldsVecInRefCell {
    fn iter(&self) -> HoldsVecInRefCellIter {
        unsafe {
            let vec_ref = self.vec_in_refcell.borrow();
            // transmute changes the lifetime parameter on the Iter
            let vec_iter = mem::transmute(vec_ref.iter());
            HoldsVecInRefCellIter { vec_ref: vec_ref, vec_iter: vec_iter }
        }
    }
}

impl<'a> Iterator for HoldsVecInRefCellIter<'a> {
    type Item = i32;

    fn next(&mut self) -> Option<Self::Item> {
        self.vec_iter.next().cloned()
    }
}
Run Code Online (Sandbox Code Playgroud)

这只能起作用,因为Iter它不是通过移动Ref,作为Ref指向的点Vec,并Iter指向Vec存储而不是Ref自身的无效.

但是,这也使你能够vec_iter走出HoldsVecInRefCellIter; 如果你提取vec_iter和删除vec_ref,那么借用将被释放,并且Iter可以在没有Rust给出编译器错误('aRefCell生命周期)的情况下无效.通过适当的封装,您可以保持struct的内容私有,并避免用户执行此不安全的操作.

顺便说一句,我们也可以定义迭代器来返回引用:

impl<'a> Iterator for HoldsVecInRefCellIter<'a> {
    type Item = &'a i32;

    fn next(&mut self) -> Option<Self::Item> {
        self.vec_iter.next()
    }
}
Run Code Online (Sandbox Code Playgroud)