【发布时间】:2021-06-09 07:59:54
【问题描述】:
我正在尝试了解如何使用内部可变性。这个问题与my previous question密切相关。
我有一个通用结构Port<T>,它拥有一个Vec<T>。我们可以将端口 B 链接到端口 A,因此,在读取端口 A 的内容时,我们能够读取端口 B 的内容。但是,这种链接对端口 A 的阅读器是隐藏的。这就是我实现iter(&self) 方法的原因:
use std::rc::Rc;
pub struct Port<T> {
values: Vec<T>,
ports: Vec<Rc<Port<T>>>,
}
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 is_empty(&self) -> bool {
self.values.is_empty() && self.ports.is_empty()
}
pub fn chain_port(&mut self, port: Rc<Port<T>>) {
if !port.is_empty() {
self.ports.push(port)
}
}
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>>)
)
}
pub fn clear(&mut self) {
self.values.clear();
self.ports.clear();
}
}
应用程序具有以下伪代码行为:
- 创建端口
- 循环:
- 用值填充端口
- 链端口
- 迭代端口的值
- 清除端口
main 函数应如下所示:
fn main() {
let mut port_a = Rc::new(Port::new());
let mut port_b = Rc::new(Port::new());
loop {
port_a.add_value(1);
port_b.add_value(2);
port_a.chain_port(port_b.clone());
for val in port_a.iter() {
// read data
};
port_a.clear();
port_b.clear();
}
}
但是,编译器抱怨:
error[E0596]: cannot borrow data in an `Rc` as mutable
--> src/modeling/port.rs:46:9
|
46 | port_a.add_value(1);
| ^^^^^^ cannot borrow as mutable
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<Port<i32>>`
我已经阅读了几篇文章等,看来我需要使用Rc<RefCell<Port<T>>> 才能改变端口。我改了Port<T>的实现:
use std::cell::RefCell;
use std::rc::Rc;
pub struct Port<T> {
values: Vec<T>,
ports: Vec<Rc<RefCell<Port<T>>>>,
}
impl<T> Port<T> {
// snip
pub fn chain_port(&mut self, port: Rc<RefCell<Port<T>>>) {
if !port.borrow().is_empty() {
self.ports.push(port)
}
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.values.iter().chain(
self.ports
.iter()
.flat_map(|p| Box::new(p.borrow().iter()) as Box<dyn Iterator<Item = &T>>),
)
}
// snip
}
这也不编译:
error[E0515]: cannot return value referencing temporary value
--> src/modeling/port.rs:35:31
|
35 | .flat_map(|p| Box::new(p.borrow().iter()) as Box<dyn Iterator<Item = &T>>),
| ^^^^^^^^^----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| | |
| | temporary value created here
| returns a value referencing data owned by the current function
我想我知道问题出在哪里:p.borrow() 返回对被链接的端口的引用。我们使用该引用来创建迭代器,但是一旦函数完成,引用就会超出范围并且迭代器不再有效。
我不知道如何处理这个问题。我设法实现了以下不安全的方法:
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.values.iter().chain(self.ports.iter().flat_map(|p| {
Box::new(unsafe { (&*p.as_ref().as_ptr()).iter() }) as Box<dyn Iterator<Item = &T>>
}))
}
虽然这可行,但它使用了不安全的代码,并且必须有一个安全的解决方法。
我设置了playground 以获取有关我的应用程序的更多详细信息。应用程序编译并输出预期的结果(但使用了不安全的代码)。
【问题讨论】:
-
为什么 main 必须是这样的?你不能每次迭代都做一个
new()port_a, port_b 并保存手动clean()调用吗?如果不是,为什么?这已经解决了所有直接的所有权问题——因为它们是该公式所固有的。为了摆脱剩余的复杂性,也许将递归迭代器重写为迭代器。 -
我们的想法是我们有两个独立运行的不同“实体”。每个实体都有一个
Port<T>(或Rc<RefCell<Port<T>>>),包含port_b的实体可以向包含port_a的实体发送消息。当然,这是一个非常简单的案例。通常,您将拥有数十个实体,每个实体都有 2 或 3 个端口,端口如何“链接”也取决于用例。在每次迭代中创建/销毁端口是否有效? -
您已经通过在每次迭代后手动清理结构来完成大部分工作。跨迭代携带结构没有用,但对所有权推理来说是一个重大(在这种情况下是致命的)负担。
标签: rust ownership reference-counting refcell