How to pass immutable reference to a function called on the same but mutable reference?

I'm working on implementing a file scanning app, which keeps track of various statistics on scanned files. Each statistics would be separately implemented, and they would intercept file adds or removes to update their internal state. Also, during the file add or remove operation, a statistic struct should be able to use the current state of the file system as additional information to update it's internal state.

For that reason, the file state itself is mutable, as it needs to keep track of all files during add / remove, but to update statistics it needs to be passed in as immutable to the struct so it can use other info to update it's state.

Rust is not allowing this, presumably because it thinks the file system reference which is borrowed as immutable, lives longer than the add / remove method handle call.

How to go around this issue?

Relevant gist:

This isn't allowed because a function with the signature of handle_remove could read and write to the Statistics value, and the compiler would assume that the write has no effect on the read. So you could do this:

struct Statistics {
    data: i32,
}

impl Statistics {
    fn handle_remove(&mut self, file: &File, file_system: &Filesystem) {
        self.data = 8;
        println!("{}", file_system.stats.data);
    }
}

And the compiler might produce something that doesn't print "8".

Since you already have access to Statistics, you could pass the rest of Filesystem[1].

fn handle_remove(&mut self, file: &File, file_system: &[File])

Or you can stop passing self and retrieve it from file_system.

fn handle_remove(file: &File, file_system: &mut Filesystem) {
    let self_stats = &mut file_system.stats;
    let files = &file_system.files;
    // ...
}

  1. for one field this is easy, but if you have multiple fields you may want to combine them into another struct so you only need to pass one field ↩ī¸Ž

3 Likes

I think I understand, so the immutable borrow indicates to the compiler that the entire file_system reference is immutable, including whatever is inside it no matter if its publicly accessible. file_system.stats would not be publicly accessible to Statistics, but I guess it does not matter.

Just to expand on the example, the idea is to have multiple statistic structs each with its own implementation of handle_add and handle_remove. They could be dynamically added to the Filesystem to act as interceptors, so for each different struct there would be a trait implementation of Statistics which would allow Filesystem to iterate trough all attached stats and call the handle_add or handle_remove on it.

I think this makes option of not passing self to handle_remove not feasible. Not sure if this is a good approach in Rust, open to suggestions. I'm referring to suggestion:

As for

I think I can go with this approach by separating the Statistics structs, and the Filesystem struct. Maybe a struct FilesystemProxy which would keep track of all the Statistics, and also own Filesystem. The Statistics would not be aware of the Proxy, but that would mean all calls to the Filesystem now have to go through the Proxy.

Let me try this and see how it goes.

1 Like