【问题标题】:Encapsulating sequentially initialized state with self-references in Rust struct在 Rust 结构中使用自引用封装顺序初始化的状态
【发布时间】:2017-01-19 09:42:30
【问题描述】:

我正在尝试定义一个结构,该结构可以充当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);
    }
}

相比之下,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();

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

我尝试了几种方法来实现这一点,但都与借用检查器发生冲突。如果我将两个状态都作为裸结构成员,例如

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

然后我不能使用HoldsVecInRefCellIter { ... } 语法同时初始化两个字段(参见例如Does Rust have syntax for initializing a struct field with an earlier field?)。如果我尝试使用类似

的结构来分流顺序初始化
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
    }
}

然后我会导致结构的可变自借,阻止从iter() 返回它。如果您尝试将对结构的一部分的引用存储在结构本身 (Why can't I store a value and a reference to that value in the same struct?) 中,也会发生结构的这种自我借用,这将阻止安全移动结构的实例。相比之下,它似乎像HoldsVecInRefCellIter 这样的结构,如果您可以完成初始化,那么在移动时会做正确的事情,因为内部的所有引用都是对其他地方的数据的引用,这些数据比这个结构的寿命更长。

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

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

相关问题: Returning iterator of a Vec in a RefCell

【问题讨论】:

  • 复杂的初始化很常见——这通常由 Builder 模式解决;这不是这里的问题。切片的迭代器希望能够在切片存在的整个时间内保持对切片的引用;事实上你cannot write an iterator that yields references to itself
  • 您可以添加一个可以存储的fn borrow(&amp;self) -&gt; Ref&lt;Vec&lt;i32&gt;&gt; 方法,然后调用iter,或者您可以接受一个提供迭代器的闭包:fn iter&lt;F: Fn(Iter&lt;i32&gt;)&gt;(&amp;self, f: F)
  • 澄清一下:我的意图是让迭代器返回值,而不是引用(示例代码中的i32)。我的挑战是初始化一个迭代器结构,该结构可以通过为底层容器存储Iterator 来产生值。对于这个例子,HoldsVecInRefCellIter 可以只保存Ref&lt;Vec&lt;i32&gt;&gt; 和向量的索引,但对于其他Ref&lt;SomeContainer&lt;i32&gt;&gt;,我想你会想要容器的Iterator
  • 至于您对borrow 函数的想法,通过这样做从根本上可以实现迭代i32s 的最终结果,尽管它没有达到以下目标抽象出HoldsVecInRefCell 的实现细节。闭包的想法很有趣——它不是一个迭代器,但它确实让用户在不知道内部存储方式的情况下接收后续的i32s。
  • 我刚刚编辑了一个相关问题的链接,该问题的answer 与您想要做的非常相似。我认为您所要做的就是在该答案的into_iter() 函数中调用map(|&amp;x| x.clone()) 以获得您所追求的精确行为。

标签: rust


【解决方案1】:

我们将不得不欺骗和谎报一生。

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

这仅是因为Iter 不会通过移动Ref 而失效,因为Ref 指向Vec,而Iter 指向Vec 的存储,而不是Ref 本身。

但是,这也使您可以将vec_iter 移出HoldsVecInRefCellIter;如果你提取vec_iter 并删除vec_ref,那么借用将被释放并且Iter 可能会在Rust 不给出编译器错误的情况下失效('aRefCell 的生命周期)。通过适当的封装,您可以将结构的内容保密,并避免用户执行这种不安全的操作。

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

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

    fn next(&mut self) -> Option<Self::Item> {
        self.vec_iter.next()
    }
}

【讨论】:

    猜你喜欢
    • 2020-09-11
    • 1970-01-01
    • 2021-10-07
    • 2021-08-27
    • 1970-01-01
    • 2016-07-18
    • 1970-01-01
    • 1970-01-01
    • 2023-02-05
    相关资源
    最近更新 更多