I am trying to create a straightforward event/subscriber system, but the compiler is (rightfully, I'm sure) complaining.
I'm sure that I am trying to shove some non-rust thinking (like OOP) into this, and I am open to architecture where needed. From what I understand, the compiler is right to complain. I think I'm trying to do exactly what Rust was designed to disallow.
Here are the requirements for a simplified example:
- I have an
Atlas
that owns a simplei32
- I have a
Client
that owns a vector ofsubscribers
, callables with the signaturefn(i32) -> ()
- Eventually, both of these will be owned by a
Bridge
that will act as the main entry point for the library. For now (in the simplified example), both are in themain.rs
- The
Client
needs to cycle through thesubscribers
and call each function, handing in ani32
.
I got this working well with a Client
like:
struct Client {
subscribers: Vec<Box<Fn(i32) -> i32>>,
}
impl Client {
pub fn add_subscriber<F: Fn(i32) -> i32 + 'static>(&mut self, callback: F) {
self.subscribers.push(Box::new(callback));
}
pub fn go(&self) {
let mut i = 0;
for subscriber in self.subscribers.iter() {
i = subscriber(i);
}
}
}
Then, I realized an additional requirement:
- one of those
subscriber
s needs to modify theAtlas
and I need to be able to use the atlas after the subscriber is added. I don't want to pass in theatlas
to thego()
method or add it to the function signature because not all subscribers will have that requirement.
I tried multiple things:
- using
move
with the closure to hand theatlas
to the closure:move |x| atlas.a = x
, which worked, but then I couldn't use theatlas
after the closure was defined. This makes sense, and it even talks about this exact scenario in the Book. - Adding a
context
as a second argument to the closure, and having thecontext
borrow a mutable reference toatlas
, but this had the same problem. I could not useatlas
afterwards.
Lastly, I switched to nightly and tried to make Atlas
itself callable by implementing FnMut
impl FnMut<(i32,)> for Atlas {
extern "rust-call" fn call_mut(&mut self, args: (i32,)) -> Self::Output {
self.a = args.0 + 10; // Make sure to mutate Atlas in some way
println!("Inside atlas FnMut: {}", &args.0);
}
}
Of course, I also implemented Fn
and FnOnce
as required.
That led me to an error that I wasn't expecting:
error[E0597]: `atlas` does not live long enough
Which actually makes sense. I am adding a reference to atlas
to the vector inside client
which theoretically could live longer than the function that created the atlas. In this case, I know it won't, but the compiler doesn't know that.
So, I'm stuck. I find myself wanting to reach for interior mutability, but I barely understand it and feel like that's a bandaide to cover bad design.
Is there a more idiomatic way to add a subscriber that mutates an atlas
? Or is there a way to tell the compiler that atlas
will live longer than the client
it was lended to?
For completion, here is the full snippet that produces the "does not live long enough" error:
Sorry this is so long. Just trying to be complete. Anyone who has suggestions about how to cut the message down, I'm open to that as well.