admin管理员组

文章数量:1124655

I'm trying to write a simple (I think!) synchronous, single-threaded publish-subscribe system in Rust. I want a centralized event router to which I can attach and remove callbacks dynamically. I'm not sure if I'm just approaching this wrong or if it's actually not possible, so I'm looking for advice.

Here's a trimmed-down sketch:

struct EventListener {}
impl EventListener {
    fn on_event(&self, event: &str) {
        println!("{}", event);
    }
}

struct EventRouter {
    // TODO: How can I express the necessary lifetime relationship between the EventRouter and listeners?
    listeners: Vec<&EventListener>
}

impl EventRouter {
    fn add_listener(&mut self, listener: &EventListener) {
        self.listeners.push(listener);
    }

    // A stand-in for a way to remove listeners dynamically
    fn clear(&mut self) {
        self.listeners.clear();
    }

    fn send(&self, event: &str) {
        for listener in &self.listeners {
            listener.on_event(event);
        }
    }
}

fn main() {
    let mut router = EventRouter { listeners: Vec::new() };

    {
        let listener = EventListener {};

        router.add_listener(&listener);

        router.send("Hello, world!");
        router.clear();
    }

    router.send("Hello, world!");
}

How can I express to Rust that all of the EventListener references held by the EventRouter have lifetimes that are a) within the EventRouter lifetime b) potentially all different from one another? Is this simply not possible to express? I'm open to solutions that use things like channels or tokio, as long as I can get the qualities I described initially.

(Also, I recognize that I might ultimately be better served by abandoning the single-threaded/synchronous constraint, but I'm currently exploring translating an existing system with these qualities to Rust and I'm curious about how/if this can work.)

I'm trying to write a simple (I think!) synchronous, single-threaded publish-subscribe system in Rust. I want a centralized event router to which I can attach and remove callbacks dynamically. I'm not sure if I'm just approaching this wrong or if it's actually not possible, so I'm looking for advice.

Here's a trimmed-down sketch:

struct EventListener {}
impl EventListener {
    fn on_event(&self, event: &str) {
        println!("{}", event);
    }
}

struct EventRouter {
    // TODO: How can I express the necessary lifetime relationship between the EventRouter and listeners?
    listeners: Vec<&EventListener>
}

impl EventRouter {
    fn add_listener(&mut self, listener: &EventListener) {
        self.listeners.push(listener);
    }

    // A stand-in for a way to remove listeners dynamically
    fn clear(&mut self) {
        self.listeners.clear();
    }

    fn send(&self, event: &str) {
        for listener in &self.listeners {
            listener.on_event(event);
        }
    }
}

fn main() {
    let mut router = EventRouter { listeners: Vec::new() };

    {
        let listener = EventListener {};

        router.add_listener(&listener);

        router.send("Hello, world!");
        router.clear();
    }

    router.send("Hello, world!");
}

How can I express to Rust that all of the EventListener references held by the EventRouter have lifetimes that are a) within the EventRouter lifetime b) potentially all different from one another? Is this simply not possible to express? I'm open to solutions that use things like channels or tokio, as long as I can get the qualities I described initially.

(Also, I recognize that I might ultimately be better served by abandoning the single-threaded/synchronous constraint, but I'm currently exploring translating an existing system with these qualities to Rust and I'm curious about how/if this can work.)

Share Improve this question asked 2 days ago abinghamabingham 1,3361 gold badge11 silver badges18 bronze badges 7
  • If you want to be able to store a listener reference in your router, then that listener must live longer than the router. The easiest for your case is probably to store Box<EventListener> or Rc<EventListener>. – Jmb Commented 2 days ago
  • @jmb I probably simplified the example too much. The listeners in the real code would be references to some trait, and not necessarily copyable objects (or objects that I would want to copy). In any event, I need the router to have a longer lifetime than the listeners. Am I trying to express something inexpressible? – abingham Commented 2 days ago
  • Nothing in rust is automatically tracking individual references other than you. The borrow checker doesn't detect removed references from the router. You probably need clear ownership to model a system like that, using smart pointers as stated by @Jmb. Not everything implemented in other languages translates well to rust, especially OOP patterns in combination with shared references – fdan Commented 2 days ago
  • @abingham the borrow checker can't determine at compile time that you remove the shorter lived references in time to not pose a use-after-free problem, so no, storing items with a lifetime not statically known to be longer is not possible in a struct. – cafce25 Commented 2 days ago
  • "I need the router to have a longer lifetime than the listeners" → that's not possible with references because the compiler has no way to ensure that you remove the reference from the router before the listener is destroyed. The only way to do that is to have the router own the listener (either exclusive ownership with Box or shared ownership with Rc). – Jmb Commented 2 days ago
 |  Show 2 more comments

1 Answer 1

Reset to default 0

Based on comments, it seems clear that what I’m trying to do simply isn’t possible with Rust. The lifetimes of the references I’m trying to use can’t be proven safe, so Rust won’t allow it. I need to switch to some design involving heap allocations and moves/copies of some data.

本文标签: