如何为Rc包裹的嵌套结构实现迭代器?
我定义了以下Rust结构:
struct Foo { parent: Option<Weak<Foo>>, some_collection: BTreeMap<u32, u32>, } struct Container { items: RefCell<Vec<Rc<Foo>>>, } struct Ref<'a> { container: &'a Container, foo: Rc<Foo>, }
因为需要可变性,我用RefCell包裹了items;又因为无法直接获取Foo的引用,所以把它放进了Rc里。由于Foo高度依赖容器类型才能发挥作用,我把Foo的引用封装在Ref结构中,只要能存储这个引用结构,这种方式就能正常工作。
现在我想实现一个迭代器,沿着Foo的父级关系遍历所有some_collection的元素,但尝试完全失败——我无法在丢弃或移动Rc的同时保存Foo::some_collection的迭代器引用。我知道这和“能否在一个结构体中存储同一对象的引用”类问题相似,但现有方案解决不了我的问题。我想要的迭代器大致如下,请问该如何解决?
struct MyIterator<'a> { iter: std::collections::btree_map::Iter<'a, u32, u32>, foo: Ref<'a>, } impl<'a> Iterator for MyIterator<'a> { type Item = (&'a u32, &'a u32); fn next(&mut self) -> Option<Self::Item> { if let Some(item) = self.iter.next() { Some(item) } else if (/*retrieve parent*/) { self.iter = parent.some_collection.iter(); self.next() } else { None } } }
你的核心问题在于:自定义迭代器中,iter和foo的生命周期没有绑定。BTreeMap::iter()返回的迭代器持有对some_collection的引用,而这个引用的有效性依赖于Foo实例的存在。当你替换foo字段时,旧的Rc<Foo>可能被销毁(引用计数归零),导致旧iter的引用悬空,Rust编译器会直接禁止这种不安全的行为。
方案1:用标准库迭代器组合实现(推荐)
不需要自定义迭代器结构体,直接用std::iter::successors和flat_map就能实现需求,代码简洁且符合Rust的生命周期规则:
use std::collections::{BTreeMap, Weak}; use std::rc::Rc; use std::iter; struct Foo { parent: Option<Weak<Foo>>, some_collection: BTreeMap<u32, u32>, } // 生成遍历Foo及其所有父级some_collection的迭代器 fn traverse_foo(start: Rc<Foo>) -> impl Iterator<Item = (&u32, &u32)> { // successors生成从start开始,依次遍历父节点的迭代器(父节点存在则继续) iter::successors(Some(start), |current_foo| { current_foo.parent.upgrade() }) // flat_map把每个Foo的some_collection迭代器扁平化成一个连续的元素流 .flat_map(|foo| foo.some_collection.iter()) }
这个实现的原理是:
successors会维护当前遍历的Rc<Foo>,只要迭代器还在运行,Rc的引用计数就大于0,保证Foo实例不会被销毁。flat_map中每个foo.some_collection.iter()生成的迭代器,其引用的有效性由对应的Rc<Foo>保证,编译器能自动推导正确的生命周期。
方案2:自定义迭代器结构体
如果你必须自定义迭代器,可以把当前遍历的Rc<Foo>和对应的迭代器绑定在一起,用选项保存状态,确保迭代器的引用永远不会悬空:
use std::collections::{BTreeMap, Weak}; use std::rc::Rc; struct Foo { parent: Option<Weak<Foo>>, some_collection: BTreeMap<u32, u32>, } struct MyIterator { // 保存当前正在遍历的Foo的Rc和对应的迭代器 current: Option<(Rc<Foo>, std::collections::btree_map::Iter<'static, u32, u32>)>, } impl MyIterator { // 从给定的Foo创建迭代器 fn new(start: Rc<Foo>) -> Self { let iter = unsafe { // 用unsafe绕过生命周期检查,实际安全性由Rc保证: // 只要current中的Rc存在,迭代器引用的Foo就不会被销毁 std::mem::transmute(start.some_collection.iter()) }; MyIterator { current: Some((start, iter)), } } } impl Iterator for MyIterator { type Item = (&u32, &u32); fn next(&mut self) -> Option<Self::Item> { let (foo, iter) = self.current.as_mut()?; // 先尝试从当前迭代器取元素 if let Some(item) = iter.next() { return Some(item); } // 当前迭代器耗尽,尝试获取父节点 let parent = foo.parent.upgrade()?; // 创建父节点的迭代器,同样用transmute绕过生命周期 let parent_iter = unsafe { std::mem::transmute(parent.some_collection.iter()) }; // 更新当前状态为父节点及其迭代器 self.current = Some((parent, parent_iter)); // 递归调用next,从父节点的迭代器取第一个元素 self.next() } }
注意:这个方案用到了unsafe,因为我们手动保证了迭代器引用的有效性——只要current中的Rc<Foo>存在,对应的Foo实例就不会被销毁,迭代器的引用就是安全的。如果不是必须自定义迭代器,优先选择方案1,避免unsafe代码。
关于Ref结构体的适配
如果你的迭代器需要依赖Ref结构体,可以在创建迭代器时从Ref中取出Rc<Foo>,然后传入上述函数或构造方法即可:
struct Ref<'a> { container: &'a Container, foo: Rc<Foo>, } impl<'a> Ref<'a> { fn traverse(&self) -> impl Iterator<Item = (&u32, &u32)> + '_ { traverse_foo(self.foo.clone()) } }
内容的提问来源于stack exchange,提问作者milck

