Observe changes of variable

Hey guys,
I guess this isn't possible in rust, but I'm curious whether it is possible to trigger an event when the value of a variable changes, or the field of a struct updates.

I thought about borrowing the reference, but then you actually aren't able to update it anymore,
Overloading the assignment of a variable is also not possible.

One use case:
Collecting updated data -> upload those changes automatically after some time.

Any thoughts on this?

The only way to do this is to make the fields private and force everyone to modify them through helper methods. Then you can call the listeners manually in the helper methods.

2 Likes

Alright, thanks :slight_smile:
I guess I have to accept that ^^

Have a nice day :smiley:

you can use Deref and DerefMut to run code when accessing a value

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=257db6c18866c4190e3024c38f4357aa

1 Like

This wouldn’t detect every update but every deref. Users could hold onto a reference returned by the Deref(Mut) implementation for a potentially very long time and do lots of updates without triggering any extra code to be run.

3 Likes

Oh that's insane!
Thank you very much! :grinning:

Edit: okay that could be an issue, but I could put that into the docs.
Also: that's an advantage, because you can define when you finished mutating.

Note that what you can do by using Deref is executing code at the start of the borrow. You won’t see when the mutating is finished, instead you only see once the next mutation starts. One way to indicate when one kind-of atomic “mutation operation” is finished is by offering some kind of guard object that will execute some code once it’s dropped. The guard object itself would then implement Deref and DerefMut in order to provide access to the thing that it’s guarding. I’m thinking somewhat similar in API to Mutex and RefCell in std.

1 Like

If you are OK with not observing every individual memory write, but you are only interested in observing (potentially accumulated or batch) changes once a mutable borrower is done with your data, then a more principled approach would be to define an RAII guard, something like RefCell's RefMut or Mutex's MutexGuard, which DerefMut's to the inner data, and calls any necessary event handling logic in its destructor. Something like this (Playground):

struct ObserverLock<T, F> {
    inner: T,
    callback: F,
}

impl<T, F> ObserverLock<T, F>
    where
        F: FnMut(&mut T),
{
    pub fn new(inner: T, callback: F) -> Self {
        ObserverLock { inner, callback }
    }
    
    pub fn lock(&mut self) -> ObserverLockGuard<T, F> {
        ObserverLockGuard { lock: self }
    }
}

struct ObserverLockGuard<'a, T, F: FnMut(&mut T)> {
    lock: &'a mut ObserverLock<T, F>,
}

impl<'a, T, F: FnMut(&mut T)> DerefMut for ObserverLockGuard<'a, T, F> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.lock.inner
    }
}

impl<'a, T, F: FnMut(&mut T)> Drop for ObserverLockGuard<'a, T, F> {
    fn drop(&mut self) {
        (self.lock.callback)(&mut self.lock.inner)
    }
}
2 Likes

I didn't think of that...

Using Deref also has the problem that f is called before the deref, not after

so either locking or having a map function would be better.
lock: Rust Playground which has the problem that Lock can just be leaked and it wont run F

map: Rust Playground

but all of these have their pros an cons

Edit: @H2CO3 beat me to it by a second

1 Like

Wow thank you all so much!
Your explanations are great, and I guess I'll dig a bit deeper to understand everything :slight_smile:
Sry for the late reply btw :blush:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.