Overlapping read and write to a struct

Hello,

I am trying to figure out a strategy/language mechanism that allows for reading one field in a struct and, while in scope of the read, write to another field. It seems should be able to inform Rust this is safe (assuming I'm right that it is) but I can't figure out how.

It seems I would have to completely extract the value/reference from the struct and then separately write it back, which would seems messy in other situations than just manipulating a string.

The code below doesn't compile complaining of too much borrowing.

Playground is here.

Thank you!

use std::collections::HashMap;

struct Container {
    pub map:HashMap<String, String>,
    pub s:String,
}

impl Container {
    fn new() -> Container {
        Container {
            map:HashMap::new(),
            s:String::new(),
        }
    }
    
    fn get_string(&self,key:&str) -> Option<&String> {
        self.map.get(key)
    }
    
    fn set_s(&mut self, value:&str) {
        self.s = value.to_string();
    }
}

fn main() {
    let mut c = Container::new();

    let _s = match c.get_string("foo") {
        Some(val) => {
            c.set_s(val);
        }
        None => panic!("oops"),
    };

}

Look into "splitting borrows".

This compiles:

use std::collections::HashMap;

struct Container {
    pub map:HashMap<String, String>,
    pub s:String,
}

impl Container {
    fn new() -> Container {
        Container {
            map:HashMap::new(),
            s:String::new(),
        }
    }
    
    
    fn get_then_set<T: AsRef<str>>(&mut self, key: T) -> bool {
        let key = key.as_ref();
        // split the borrows
        let map = &self.map;
        let s = &mut self.s;
        
        if let Some(val) = map.get(key) {
            *s = val.to_string();
            true
        } else {
            false
        }
    }
}

fn main() {
    let mut c = Container::new();
    println!("Success? {}", c.get_then_set("foo"))
}

The compiler only knows how to do this if both of the internal references are generated in the same function. One approach is to accept a closure to allow arbitrary user code to be run in a specific context:

impl Container {
    fn new() -> Container {
        Container {
            map:HashMap::new(),
            s:String::new(),
        }
    }

    fn update_s_from_key(&mut self, key:&str, func:impl FnOnce(& str)->String) -> Result<(),()> {
        match self.map.get(key) {
            Some(s) => { self.s = (func)(s); Ok(()) },
            None => Err(())
        }
    }
}

fn main() {
    let mut c = Container::new();

    c.update_s_from_key("foo", |s| s.to_string()).unwrap();

}

(Playground)

1 Like

For some reason, this reminds me of pin projection (not entirely related, but the idea we can alter the wrapper of individual fields in structs applies here to the degree that we need a split Ref and RefMut). Maybe, this calls for implementing a borrow-projection macro to make it easier for beginners, thus removing the need to do everything in a single function?

Nothing is coming to mind as to what that implementation might look like, but maybe this sparks a thought in someone else ...

It’s also possible to define an explicit borrow splitting method, and use newtypes to control what the downstream user can do with the split references.

pub struct MapRef<'a> ( & 'a mut HashMap<String,String>);
impl<'a> MapRef<'a> {
    /* methods that are independent of `s` */
}

pub struct StrRef<'a>( &'a mut String );
impl<'a> StrRef<'a> {
    /* methods that are independent of `map` */
}

pub struct ContainerRefs<'a> {
    pub map: MapRef<'a>,
    pub s: StrRef<'a>,
}

impl Container {
    pub fn split_mut(&mut self)->ContainerRefs<'_> {
        ContainerRefs {
            map: MapRef(&mut self.map),
            s: StrRef(&mut self.s)
        }
    }

    /* methods that require both `map` and `s` */
}
2 Likes

Is the essense of the reason this works due to the .to_string() creating a new instance of the value that isn't associated w/ the map?

I can do something similar (I think?) like this a/ a clone: Playground

Thanks

Not exactly; the problem you were having was that main needed an &mut Container (to update s) at the same time as an &Container (to get the key). Both versions of update_s_from_key get around this by accessing Container’s fields directly instead of through functions, which lets the compiler treat them as independent variables.

Thank you both for the insights. Each of these examples is correct, instructive and helpful. I'm awarding the solution to the one that is closest to what I implemented, but all could work.