Implementing a Simple Change-detecting Wrapper Type

Hey everybody, I have a project that I'm working on where I have "jobs" that may or may not change an in-memory configuration struct. After a certain point in the program I need to check whether or not the configuration was changed and update the configuration on the host to match, but I only want to updated the config on the host if the config was changed.

In order to prevent bugs that might be introduced by forgetting to manually make sure that I set the config.modified = true flag every time something changes the configuration, I created a simple change-detecting wrapper type which is demonstrated below.

I wanted to get feedback on whether or not this was a reasonable solution.

The Debug implementation for Cb might be mostly pointless when you could just use *variable to get to the inner type which implements Debug anyway, but I implemented it just as an exercise in making sure I understood the concept correctly.

use std::ops::{Deref, DerefMut};

/// Change detecting container for other types
struct Cd<T> {
    /// Whether or not the inner type has been borrowed mutably since the last
    /// `clean()`
    dirty: bool,
    /// The inner type
    inner: T
}

impl<T> Cd<T> {
    /// Create a new change detector containing the given type
    fn new(inner: T) -> Self {
        Cd {
            dirty: true,
            inner,
        }
    }
    
    /// Mark this object as "clean". The object will become "dirty" when the
    /// inner type is borrowed mutably
    fn clean(&mut self) {
        self.dirty = false;
    }
    
    /// Get whether or not this object has been mutably borrowed since the last
    /// run of `clean()`.
    fn is_dirty(&self) -> bool {
        self.dirty
    }
}

impl<T> Deref for Cd<T> {
    type Target = T;

    /// Dereference to the inner type    
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<T> DerefMut for Cd<T> {
    /// Mutably dereference to the inner type and mark type as "dirty"
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.dirty = true;

        &mut self.inner
    }
}

impl<T: std::fmt::Debug> std::fmt::Debug for Cd<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.inner)
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create change detecting object
    let mut name = Cd::new(String::from("Ryan"));
    // Print name
    dbg!(&name);
    // Name should be dirty because `Cb`'s start off dirty
    dbg!(name.is_dirty());
    // Clean the name
    dbg!(name.clean());
    // The name should no longer be dirty
    dbg!(name.is_dirty());
    // Print the name ( this will get a reference to the `Cb`'s inner type )
    dbg!(&name);
    // The name should still not be dirty because the reference taken when
    // printing was not mutable.
    dbg!(name.is_dirty());
    // Change the name by assiging to the inner type
    dbg!(*name = String::from("Cindy"));
    // Print the name; it should be updated to "Cindy"
    dbg!(&name);
    // The name should now be dirty, marking that the name has changed since we
    // ran `clean()`
    dbg!(name.is_dirty());
    
    Ok(())
}

(Playground)

Output:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/playground`
[src/main.rs:62] &name = "Ryan"
[src/main.rs:64] name.is_dirty() = true
[src/main.rs:66] name.clean() = ()
[src/main.rs:68] name.is_dirty() = false
[src/main.rs:70] &name = "Ryan"
[src/main.rs:73] name.is_dirty() = false
[src/main.rs:75] *name = String::from("Cindy") = ()
[src/main.rs:77] &name = "Cindy"
[src/main.rs:80] name.is_dirty() = true

This looks good, unfortunately it will miss changes from interior mutability like Cell or Mutex. But there isn't a way to check for that without cloning. Should be good enough for most use cases though.

1 Like

That's a good point that I hadn't thought of, but thankfully the whole config struct itself is inside of an Arc<RwLock<T>> so I don't have to have any interior mutability in the config struct.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.