Function call when derefmut value is assigned

struct DerefMutExample<T> {
value: T
}
impl<T> DerefMut for DerefMutExample<T> {
...
}
impl<T> DerefMut for DerefMutExample<T> {
...
}

fn updated() {
println!("{}", "updated");
}

fn main() {
let mut evar = DerefMutExample { value: 'a' };
*evar = 'b'; // Want to call a function when evar is updated. i.e updated()
}

Is there anyway to call a function when evar is updated?

You can run code in the deref_mut method of the DerefMut trait.

so i can get the updated value 'b' in the function I call in the derefmut trait?

To my knowledge you cannot get the new value using the DerefMut trait. You can get the old value. To do what you seem to want one would conventionally use getter and setter methods instead of Deref and DerefMut.

struct DerefMutExample {
    value: i32
}

// Getter and setter
impl DerefMutExample {
    fn value(&self) -> &i32 {
        &self.value
    }
    fn set_value(&mut self, new_value: i32) {
        self.value = new_value;
        updated(&self.value);
    }
}

// Deref and DerefMut
impl std::ops::Deref for DerefMutExample {
    type Target = i32;
    fn deref(&self) -> &Self::Target {
        &self.value
    }
}
impl std::ops::DerefMut for DerefMutExample {
    fn deref_mut(&mut self) -> &mut Self::Target {
        possibly_updated(&self.value);
        &mut self.value
    }
}

fn possibly_updated(value: &i32) {
    println!("Mutable access, previous value was {}", value);
}

fn updated(value: &i32) {
    println!("Updated, new value is {}", value);
}

fn main() {
    let mut evar = DerefMutExample { value: 1 };
    *evar = 2;
    // Output: "Mutable access, previous value was 1"
    
    let mut evar = DerefMutExample { value: 1 };
    evar.set_value(2);
    // Output: "Updated, new value is 2"
}

No, deref_mut happens before the update. There's no way to hook in to the actual write, at least not when going through DerefMut. You can define a set method instead though.

struct DerefMutExample {
    name: String,
    value: i32
}

// Deref and DerefMut
impl std::ops::Deref for DerefMutExample {
    type Target = i32;
    fn deref(&self) -> &Self::Target {
        &self.value
    }
}
impl std::ops::DerefMut for DerefMutExample {
    fn deref_mut(&mut self) -> &mut Self::Target {
        possibly_updated(&self.name, &self.value);
        &mut self.value
    }
}

fn possibly_updated(name: &String, value: &i32) {
    println!("{}'s balance changed {}", name, value);
}

fn main() {
    let mut tomWallet = DerefMutExample { name: "Tom".to_string(), value: 100 };
    *tomWallet += 50;
    
    // Output: Tom's balance changed 100
    // Output expected: Tom's balance changed 150
}

I'd like to get updated value of DerefExample when the value is changed. Btw, it has the name attribute in it, so DerefMut is essential.

It is not possible to do this if you are modifying it through DerefMut. The following is the closest you can get:

struct DerefMutExample {
    name: String,
    value: i32
}

impl DerefMutExample {
    fn add(&mut self, delta: i32) {
        self.value += delta;
        possibly_updated(&self.name, &self.value);
    }
}

fn possibly_updated(name: &String, value: &i32) {
    println!("{}'s balance changed {}", name, value);
}

fn main() {
    let mut tomWallet = DerefMutExample { name: "Tom".to_string(), value: 100 };
    tomWallet.add(50);
}

Can we do this by adding another impl for DerefMutExample?

Instead of DerefMut, you could override operators like AddAssign:

impl std::ops::AddAssign<i32> for Wallet {
    fn add_assign(&mut self, x: i32) {
        self.value += x;
        possibly_updated(&self.name, &self.value);
    }
}


tomWallet += 50;

(Playground)

But you probably want to support arbitrary operations, not just addition. In this case, you could use a closure-based API:

impl Wallet {
    fn update(&mut self, f: impl FnOnce(&mut i32)) {
        f(&mut self.value);
        possibly_updated(&self.name, &self.value);
    }
}

tomWallet.update(|value| {
    *value += 50;
});

(Playground)

But if you give out a &mut i32 reference, there is no “hook” that lets you run arbitrary code when someone writes to it. And this can't be changed because unsafe code relies on it.

2 Likes

Another option is to return an RAII guard with a Drop implementation instead of &mut _. This isn't possible with DerefMut on the main structure, but could look something like this:

struct DerefMutExample {
    name: String,
    value: i32
}

pub struct ValueObserver<'a> (&'a mut DerefMutExample);

// Deref and DerefMut
impl<'a> std::ops::Deref for ValueObserver<'a> {
    type Target = i32;
    fn deref(&self) -> &Self::Target {
        &self.0.value
    }
}

impl<'a> std::ops::DerefMut for ValueObserver<'a> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0.value
    }
}

impl<'a> Drop for ValueObserver<'a> {
    fn drop(&mut self) {
       println!("{}'s balance changed {}", self.0.name, self.0.value);
    }
}

impl DerefMutExample {
    pub fn get_mut_value(&mut self)->ValueObserver<'_> {
        ValueObserver(self)
    }
}

fn main() {
    let mut tomWallet = DerefMutExample { name: "Tom".to_string(), value: 100 };
    *tomWallet.get_mut_value() += 50;
}

(Playground)

3 Likes