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(())
}
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