当前位置: 首页 > 知识库问答 >
问题:

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

仉姚石
2023-03-14

我试图定义一个结构,该结构可以作为向量的迭代器,该向量保存在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();

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

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

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

然后我无法使用 HoldsVecInRefCellIter { ... } 语法同时初始化这两个字段(例如,请参阅 Rust 是否有用于使用较早字段初始化结构字段的语法?)。如果我尝试用一个结构来分流顺序初始化,比如

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()返回它。如果您试图在结构本身中存储对结构的一部分的引用,也会发生结构的这种自借用(为什么我不能在同一个结构中存储一个值和对该值的引用?),这将阻止安全移动结构的实例。相比之下,如果您能够完成初始化,像<code>HoldsVecInRefCellIter</code>这样的结构在移动时会做正确的事情,因为所有内部引用都指向此结构之外的数据。

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

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

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

共有1个答案

徐茂材
2023-03-14

我们将不得不在一生中欺骗和撒谎。

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

这只是因为移动< code>Ref不会使< code>Iter无效,因为< code>Ref指向< code>Vec,而< code>Iter指向< code>Vec的存储,而不是指向< code>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()
    }
}
 类似资料:
  • 我有一个Java Spring Boot应用程序,在我的Maven pom.xml中将Flyway配置为依赖项(我有一个父pom和一个项目pom...Flyway是在我的项目pom中定义的)。 application.properties中只有几个条目: 我可以运行一个maven任务来让Flyway运行来创建/更新我的数据库,然后针对该数据库运行我的应用程序,但是我很难通过运行我的应用程序(这在p

  • 问题内容: 我有多个上下文文件。要求是:在其余的Bean中首先初始化一个特定的Bean(进行一些配置更改)。 有没有一种方法可以首先加载该bean? 一种选择是使用“取决于”属性。 但这将需要更新所有其他bean,因此这似乎不是最佳解决方案。 我们有更好的选择吗? 问题答案: 恕我直言,您应该等待它们修复https://jira.spring.io/browse/SPR-3948 一种可能的方法是

  • 问题内容: 我正在尝试发现初始化发生的顺序,或者更确切地说,为什么要按此顺序进行初始化的原因。给定代码: 输出: 但是,将的声明移动到初始化块之前会产生: 而且我完全不知道为什么会以这种顺序发生。此外,如果我在的声明中消除了关键字,则init块和构造函数均不会触发。谁能帮我这个忙吗? 问题答案: 我认为您只是缺少JLS的12.4.2节,其中包括: 接下来,以文本顺序执行类的类变量初始化器和静态初始

  • 问题内容: 通常,我将初始化一个类似的结构: 但是,我最近看到了用括号初始化的代码: 返回相同的名称。 括号中的内容起什么作用?何时首选? 这是一些Go代码可以尝试一下: 游乐场:https : //play.golang.org/p/_gsaruS_DVi 问题答案: 没什么特别的,那两行是相同的。 但是,例如,当您要在语句中使用它时,将需要使用括号,否则会出现编译时错误: 结果是: 预期的布尔

  • 问题内容: 我不知道如何初始化嵌套结构。在此处找到示例:http: //play.golang.org/p/NL6VXdHrjh 问题答案: 好吧,有什么特定的原因不使Proxy成为自己的结构? 无论如何,您有2个选择: 正确的方法是,只需将proxy移至其自己的结构,例如: 不太正确和丑陋的方法,但仍然有效:

  • 问题内容: import React, { Component } from ‘react’; 通常我看到的是,如果他使用es6类,人们会在构造函数中执行this.state。如果不是,他可能会使用getinitialstatestate函数放置状态。但是上面的代码(是的,这是一个有效的代码),两者都没有使用。我有2个问题,这里的状态是什么?那是局部变量吗?如果是,为什么没有?prevState来