【问题标题】:Rust: implementing an iterator from a vector of std::Rc smart pointersRust:从 std::Rc 智能指针向量实现迭代器
【发布时间】:2021-08-24 11:48:32
【问题描述】:

我从 Rust 开始,我已经面临数据所有权问题。

我想实现一个名为Port<T> 的通用结构,它有一个值向量Vec<T>。此外,这个结构有一个引用计数指针向量,指向相同类型的其他结构,Vec<Rc<Port<T>>>:

use std::slice::Iter;
use std::rc::Rc;

pub struct Port<T> {
    values: Vec<T>,
    ports: Vec<Rc<Port<T>>>,
}

想法如下:有多个Port&lt;T&gt; 类型的结构。您可以将T 类型的值添加到给定端口。每个端口将这些值存储在其values 属性中。但是,可以使用引用计数指针将一个端口“链接”到其他端口:

impl <T> Port<T> {
    pub fn new() -> Self {
        Self { values: vec![], ports: vec![] }
    }

    pub fn add_value(&mut self, value: T) {
        self.values.push(value);
    }

    pub fn chain_port(&mut self, port: Rc<Port<T>>) {
        if !port.is_empty() {
            self.ports.push(port)
        }
    }

    pub fn is_empty(&self) -> bool {
        self.values.is_empty() || self.ports.is_empty()
    }

    pub fn clear(&mut self) {
        self.values.clear();
        self.ports.clear();
    }
}

到目前为止,代码已编译。现在,我想为一个端口实现一个迭代器,它返回对该端口拥有的值的引用,但也引用由每个链接端口的迭代器生成的值:

pub struct PortIterator<'a, T> {
    values: Iter<'a, T>,                     // Iterates over values owned by Port<T>
    port: Option<Box<PortIterator<'a, T>>>,  // Pointer to current port iterator
    ports: Vec<Rc<Port<T>>>,                 // Pointers to remaining chained ports
}

// Note that the iterator is created from an immutable reference to Port<T>
impl<'a, T: 'a> IntoIterator for &'a Port<T> {
    type Item = &'a T;  // the iterator returns references to values
    type IntoIter = PortIterator<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        // We clone all the reference-counting pointers so the iterator owns them
        let mut ports = vec![];
        for port in self.ports.iter() {
            ports.push(port.clone())
        }
        PortIterator {values: self.values.iter(), port: None, ports}
    }
}

现在,让我们为PortIterator 定义Iterator trait:

impl <'a, T: 'a> Iterator for PortIterator<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        // We first iterate over values of the original port
        if let Some(v) = self.values.next() {
            return Some(v)
        }
        // If the first iterable is done, we try to iterate over a chained port
        if let Some(port) = &mut self.port {
            if let Some(v) = port.next() {
                return Some(v)
            }
        }
        // if the chained port is over, we try to consume the next chained port
        if let Some(port) = self.ports.get(self.next_port) {
            self.next_port += 1;
            self.port = Some(Box::new(port.as_ref().into_iter()));
            return self.next()
        }
        None
    }
}

现在,程序无法编译。问题似乎在第三个if let 块中,它与生命周期有关。编译器是这样说的:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/modeling/port.rs:69:40
   |
69 |         if let Some(port) = self.ports.get(self.next_port) {
   |                                        ^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 57:5...
  --> src/modeling/port.rs:57:5
   |
57 |     fn next(&mut self) -> Option<Self::Item> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/modeling/port.rs:69:29
   |
69 |         if let Some(port) = self.ports.get(self.next_port) {
   |                             ^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 54:7...
  --> src/modeling/port.rs:54:7
   |
54 | impl <'a, T: 'a> Iterator for PortIterator<'a, T> {
   |       ^^
note: ...so that the expression is assignable
  --> src/modeling/port.rs:71:25
   |
71 |             self.port = Some(Box::new(port.as_ref().into_iter()));
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: expected `Option<Box<PortIterator<'a, _>>>`
              found `Option<Box<PortIterator<'_, _>>>`

我不确定如何处理这个问题。我一直在尝试其他选项和实现,但我一直在绕圈子。

【问题讨论】:

    标签: rust ownership reference-counting


    【解决方案1】:

    我认为有更简单的方法可以实现您想要实现的目标。让我们从小事做起:您的 Port&lt;T&gt; 需要一个 iter(&amp;self) 方法,该方法返回一个分发 &amp;T 项目的迭代器:

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        // ...
    }
    

    此函数需要将迭代器链接到self.values,即self.values.iter() 与链接端口上的迭代器。你想写的是这样的:

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.values
            .iter()
            .chain(self.ports.iter().flat_map(|p| p.iter()))
    }
    

    但是,这不会编译,因为编译器会抱怨“递归不透明类型”。那是因为p.iter() 的类型与我们的impl Iterator&lt;...&gt; 完全相同,因此它必须包含自己。这在概念上与您在构建PortIterator 时遇到的问题相同,您通过将链接的PortIterator 装箱解决了这个问题。我们可以用同样的方式解决它,通过装箱内部迭代器并动态调度它:

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.values.iter().chain(
            self.ports
                .iter()
                .flat_map(|p| Box::new(p.iter()) as Box<dyn Iterator<Item = &T>>),
        )
    }
    

    【讨论】:

    • 非常感谢!是的,这种方式看起来好多了。我一直在测试这个,我发现,一旦你将一个端口链接到另一个端口,先前的端口就会被移动并且不能再使用了。我想我将不得不使用Vec&lt;Rc&lt;RefCell&lt;Port&lt;T&gt;&gt;&gt;&gt; 来解决这个问题。
    • @RománCárdenas 我认为这会很好地工作,并且像 iter() 这样的方法是 Rust 类型的约定,例如Vec 有一个。
    • @RománCárdenas 要为&amp;Port&lt;T&gt; 实现IntoIterator,可以用一个函数替换给flat_map() 的闭包(这应该是可能的,因为闭包不捕获任何东西)并返回一个仅在来自Port::iter() 的T 上键入泛型。返回类型类似于Chain&lt;Iter&lt;T, FlatMap&lt;slice::Iter&lt;Rc&lt;Port&lt;T&gt;&gt;&gt;, Box&lt;dyn Iterator&lt;Item = &amp;T&gt;&gt;, fn(&amp;Rc&lt;Port&lt;T&gt;&gt;) -&gt; Box&lt;dyn Iterator&lt;Item = &amp;T&gt;&gt;&gt;。然而,由于生命周期问题,它没有编译,并且很快就变得非常毛茸茸。如果你不是绝对需要IntoIterator,我会选择这个。
    • 感谢您的回答。我正在尝试使用 Rc>> 链接端口,但我遇到了新问题。正如您所建议的,我发布了一个新问题,其中包含有关我的案例的更多详细信息:stackoverflow.com/questions/67899965/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-25
    相关资源
    最近更新 更多