Rust Borrow Checker: Why does reordering assertions affect mutable borrow lifetime from &mut dyn Trait in tests?

⚓ Rust    📅 2025-10-17    👤 surdeus    👁️ 1      

surdeus

I have a struct in rust defined as below:

pub struct ObjectDictionary<'a> {
    entries: BTreeMap<u16, ObjectEntry>,
    storage: Option<&'a mut dyn ObjectDictionaryStorage>,
}

The ObjectDictionary has a storage attribute which I use to store data. ObjectDictionaryStorage is a trait because the storage could be of many different types.

pub trait ObjectDictionaryStorage {
    fn load(&mut self) -> Result<BTreeMap<(u16, u8), ObjectValue>, Error>;
    fn save(&mut self, parameters: &BTreeMap<(u16, u8), ObjectValue>) -> Result<(), Error>;
    fn clear(&mut self) -> Result<(), Error>;
    fn restore_defaults_requested(&self) -> bool;
}

Now, I am trying to run some tests and I created the following mock to emulate storage:

    struct MockStorage {
        saved_data: BTreeMap<(u16, u8), ObjectValue>,
        restore_requested: bool,
        save_called: bool,
        load_called: bool,
        clear_called: bool,
    }
    impl MockStorage {
        fn new() -> Self {
            // Not shown here for brevity.
        }
    }
    impl ObjectDictionaryStorage for MockStorage {
        fn load(&mut self) -> Result<BTreeMap<(u16, u8), ObjectValue>, Error> {
            // Not shown here for brevity.
        }
        fn save(
            &mut self,
            params: &BTreeMap<(u16, u8), ObjectValue>,
        ) -> Result<(), Error> {
            // Not shown here for brevity.
        }
        fn clear(&mut self) -> Result<(), Error> {
            self.clear_called = true;
            self.saved_data.clear();
            Ok(())
        }
        fn restore_defaults_requested(&self) -> bool {
            self.restore_requested
        }
        fn request_restore_defaults(&mut self) -> Result<(), Error> {
            self.restore_requested = true;
            Ok(())
        }
        fn clear_restore_defaults_flag(&mut self) -> Result<(), Error> {
            self.restore_requested = false;
            Ok(())
        }
    }

Now, during my tests, I would like to check if the restore_requested and clear_called flags have changed. I can easily check restore_requested because one of the traits' functions return it. But since none of the trait functions return clear_called, I am not being able to access it because the MockStorage is mutably borrowed to the ObjectDictionary.

See below an example of a test that would fail.

    #[test]
    fn test_init_restores_defaults_if_flagged() {
        let mut storage = MockStorage::new();
        storage
            .saved_data
            .insert((0x6000, 0), ObjectValue::Unsigned32(999));
        storage.restore_requested = true;

        let mut od = ObjectDictionary::new(Some(&mut storage));
        od.insert(
            0x6000,
            ObjectEntry {
                object: Object::Variable(ObjectValue::Unsigned32(0)),
                name: "StorableVar",
                access: AccessType::ReadWriteStore,
            },
        );

        od.init().unwrap();

        assert!(storage.clear_called); // Test fails here.

        assert!(!od
            .storage
            .as_ref()
            .unwrap()
            .restore_defaults_requested()); 

        assert_eq!(od.read_u32(0x6000, 0).unwrap(), 0); // Back to default
    }

The error message reads

cannot use storage.clear_called because it was mutably borrowed; use of borrowed storage.

Weirdly enough, I just realized that if I place assert!(storage.clear_called)'; at the end of the test as the last command, everything builds!

    #[test]
    fn test_init_restores_defaults_if_flagged() {
        let mut storage = MockStorage::new();
        storage
            .saved_data
            .insert((0x6000, 0), ObjectValue::Unsigned32(999));
        storage.restore_requested = true;

        let mut od = ObjectDictionary::new(Some(&mut storage));
        od.insert(
            0x6000,
            ObjectEntry {
                object: Object::Variable(ObjectValue::Unsigned32(0)),
                name: "StorableVar",
                access: AccessType::ReadWriteStore,
            },
        );

        od.init().unwrap();

        assert!(!od
            .storage
            .as_ref()
            .unwrap()
            .restore_defaults_requested()); 

        assert_eq!(od.read_u32(0x6000, 0).unwrap(), 0);

        assert!(storage.clear_called); // Test doesn't fail anymore!
    }

Can someone explain to me what is happening and what am I missing? Probably something about the assert! macro that I am not familiar with I guess...

8 posts - 3 participants

Read full topic

🏷️ Rust_feed