【发布时间】:2018-04-10 16:31:48
【问题描述】:
作为学习练习,我一直在将一个相当标准的 C++ 信号实现翻译成 Rust:
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::{Rc, Weak};
trait Notifiable<E> {
fn notify(&self, e: &E);
}
struct SignalData<'l, E>
where
E: 'l,
{
listeners: BTreeMap<usize, &'l Notifiable<E>>,
}
struct Signal<'l, E>
where
E: 'l,
{
next_id: usize,
data: Rc<RefCell<SignalData<'l, E>>>,
}
struct Connection<'l, E>
where
E: 'l,
{
id: usize,
data: Weak<RefCell<SignalData<'l, E>>>,
}
impl<'l, E> Signal<'l, E>
where
E: 'l,
{
pub fn new() -> Self {
Self {
next_id: 1,
data: Rc::new(RefCell::new(SignalData {
listeners: BTreeMap::new(),
})),
}
}
pub fn connect(&mut self, l: &'l Notifiable<E>) -> Connection<'l, E> {
let id = self.get_next_id();
self.data.borrow_mut().listeners.insert(id, l);
Connection {
id: id,
data: Rc::downgrade(&self.data),
}
}
pub fn disconnect(&mut self, connection: &mut Connection<'l, E>) {
self.data.borrow_mut().listeners.remove(&connection.id);
connection.data = Weak::new();
}
pub fn is_connected_to(&self, connection: &Connection<'l, E>) -> bool {
match connection.data.upgrade() {
Some(data) => Rc::ptr_eq(&data, &self.data),
None => false,
}
}
pub fn clear(&mut self) {
self.data.borrow_mut().listeners.clear();
}
pub fn is_empty(&self) -> bool {
self.data.borrow().listeners.is_empty()
}
pub fn notify(&self, e: &E) {
for (_, l) in &self.data.borrow().listeners {
l.notify(e);
}
}
fn get_next_id(&mut self) -> usize {
let id = self.next_id;
self.next_id += 1;
id
}
}
impl<'l, E> Connection<'l, E>
where
E: 'l,
{
pub fn new() -> Self {
Connection {
id: 0,
data: Weak::new(),
}
}
pub fn is_connected(&self) -> bool {
match self.data.upgrade() {
Some(_) => true,
None => false,
}
}
pub fn disconnect(&mut self) {
match self.data.upgrade() {
Some(data) => {
data.borrow_mut().listeners.remove(&self.id);
self.data = Weak::new();
}
None => (),
}
}
}
impl<'l, E> Drop for Connection<'l, E>
where
E: 'l,
{
fn drop(&mut self) {
self.disconnect();
}
}
对于简单的测试代码,这将按预期进行编译和运行:
struct Event {}
struct Listener {}
impl Notifiable<Event> for Listener {
fn notify(&self, _e: &Event) {
println!("1: event");
}
}
fn main() {
let l1 = Listener {};
let l2 = Listener {};
let mut s = Signal::<Event>::new();
let c1 = s.connect(&l1);
let mut c2 = s.connect(&l2);
println!("c2: {}", c2.is_connected());
s.disconnect(&mut c2);
println!("c2: {}", c2.is_connected());
let e = Event {};
s.notify(&e);
println!("done!");
}
但是,如果我尝试与设想的用例更相似的东西,它不会编译:
struct Event {}
struct System<'l> {
signal: Signal<'l, Event>,
}
struct Listener<'l> {
connection: Connection<'l, Event>,
}
impl<'l> Notifiable<Event> for Listener<'l> {
fn notify(&self, _e: &Event) {
println!("1: event");
}
}
fn main() {
let mut listener = Listener {
connection: Connection::new(),
};
let mut system = System {
signal: Signal::new(),
};
listener.connection = system.signal.connect(&listener);
println!("is_connected(): {}", listener.connection.is_connected());
system.signal.disconnect(&mut listener.connection);
println!("is_connected(): {}", listener.connection.is_connected());
let e = Event {};
system.signal.notify(&e);
println!("done!");
}
这给出了以下错误:
error[E0597]: `listener` does not live long enough
--> src/main.rs:147:50
|
147 | listener.connection = system.signal.connect(&listener);
| ^^^^^^^^ borrowed value does not live long enough
...
157 | }
| - `listener` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
看来我的问题源于SignalData,我将侦听器集合存储为引用:listeners: BTreeMap<usize, &'l Notifiable<E>>。生命周期的要求似乎一直向外传播。
Connection 类(至少在 C++ 中)的目的是允许从Listener 端断开连接,并管理连接的生命周期,当信号退出时从信号中删除Listener 条目范围。
Connection 的生命周期必须小于或等于 Signal 和相关 Listener 的生命周期。但是,Listener 和 Signal 的生命周期应该是完全独立的。
有没有办法改变我的实现来实现这一点,还是从根本上破坏了?
【问题讨论】:
-
不相关,但对于惯用代码:
if let而不是带有一只手臂的match、Option::is_some()和Option::map_or。这些将缩短您的实施时间 -
你在 C++ 中使用了引用,还是使用了智能指针?
-
@MatthieuM。好吧,从技术上讲是
std::function...(我猜它又会存储对*this的引用)。我最初确实尝试使用Fn,但遇到了问题。 -
使用 nightly 和
#![feature(nll)]编译会提供更好的错误消息。 -
@user673679:Rust 不代表“由调用者决定”,除非函数被标记为
unsafe。这使得实现观察者变得更加棘手......但更安全。
标签: rust signals-slots