Advice on error handling while getting a mutable reference

I have a config which has a list of profiles. I want to return one of these profiles as a mutable reference. Basically a &mut Profile, referring to the profile in the config. I was doing this inline but I want to extract this into a function. The inline code is this:

	// Get a mutable reference to the active profile
	let profile = if let Some(profile) = config.profiles.get_mut(config.active_profile) {
		profile
	} else {
		// Error handling
	};

config.active_profile is a usize index.

The function I have this far is this:

fn get_profile(config: &mut json::Config, config_file: std::fs::File) -> FResult<&mut json::Profile> {
	if let Some(profile) = config.profiles.get_mut(config.active_profile) {
		Ok(profile)
	} else {
		if config.profiles.is_empty() { // cannot borrow `config.profiles`
			...
		}
		json::write_to_config(config_file, &config)?; // cannot  borrow `config`
		...
	}
}

Because I'm mutably borrowing the config when I'm trying to get the profile from it, I can't access the config again. However I need the config to correct the mistake or provide error information. How can I do this?

My current guess is to call get_mut() once and drop the value just to check if there was an error not. Then if there was no error we can call and unwrap get_mut() again and capture (and return) the value. This feels pretty unidiomatic and I would like some advice on this

It's NLL Problem Case #3, a shortcoming in the current borrow checker. I didn't come up with a great workaround, but one option is something like

fn get_profile(config: &mut json::Config, config_file: std::fs::File) -> FResult<&mut json::Profile> {
    let index = config.active_profile;
    match config.profiles.len() {
        0 => Err(()), // cannot borrow config.profiles
        x if index < x => Ok(&mut config.profiles[index]),
        _ => {
        	json::write_to_config(config_file, &config)?;
	        Err(())
        }
    }
}

Playground.

3 Likes

This is a known limitation of the current version of the borrow checker. AFAIK the next-generation borrow checker, Polonius, will correctly allow this kind of code.

A possible workaround is to switch to a collection type featuring an API explicitly designed to work around this sort of problem, e.g.: Playground

fn get_profile(config: &mut Config, config_file: File) -> Result<&mut Profile, Error> {
	let profile = config.profiles.entry(config.active_profile).or_insert_with(|| {
	    println!("No profiles found!");
	    Profile
	});
	
	Ok(profile)
}
4 Likes

Thanks for all your replies. I thought that the borrow checker will drop the 'reference' when an if statement comparison is done but it doesn't seem to for now. I had a hunch it was the borrow checker being a bit more conservative but just seems to be a limitation/known bug. However I wonder why it works when its inline (i.e. the first code snippet)?

The limitation kicks in when the reference is returned from the function. If all your work in the first snippet is happening within a single function, NLL today can figure it out.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.