Weird borrow checker error: for loop keeps references after its scope ends


#1

I asked this question on the IRC channel and got some responses, but no one could explain the reason why I’m getting this weird error.

The code is this:

use std::fs::File;
use std::fs::OpenOptions;
use std::fs;
use std::path::{Path, PathBuf};
use time::Tm;

pub struct Logger {
    log_path: PathBuf,
    fds: Vec<(String, File)>,
}

impl Logger {
    fn get_file(&mut self, target: &str) -> &mut File {
        // Check if we already opened the file
        for &mut (ref serv, ref mut file) in &mut self.fds {
            if serv == target {
                return file;
            }
        }

        // Open it (create if necessary)
        let mut log_path = self.log_path.clone();
        log_path.push(target);
        let file = OpenOptions::new().append(true).open(log_path).unwrap();
        self.fds.push((target.to_owned(), file));
        self.get_file(target)
    }
}

The borrow checker complains that the for loop borrows self.fds mutably, and then the last line also borrows it, which doesn’t make sense because the reference hold by the for loop should be discarded when the loop’s scope ends. Anyone know what’s going on here?


#2

Unfortunately, it’s a limitation in a current version of borrow checker. See this bug:

Workaround is quite ugly, you’d need to put the &mut borrow inside an if, so basically, you’ll need to iterate twice. Something like that should work:

        // Check if we already opened the file
        let already_opened = self.fds.iter().any(|x| x.0 == target);

        if already_opened {
            for &mut (ref serv, ref mut file) in &mut self.fds {
                if serv == target {
                    return file;
                }
            }
            unreachable!();
        }

#3

How about using Iterator::position to avoid scanning twice?


#4

Thanks for this hint! So the code now is a lot simpler:

        // Check if we already opened the file
        let position = self.fds.iter().position(|x| x.0 == target);

        if let Some(idx) = position {
            return &mut self.fds[idx].1;
        }

Also, no need to explain why the unreachable! is necessary!


#5

Thanks for the answers.

It’s annoying that the borrow checker is causing problems in even such simple cases. Anyone know if this is going to be fixed soon?


#6

It’s annoying indeed. The problem (lexical lifetimes) is well known and I know solutions are being explored (non lexical lifetimes :slight_smile:), but I can’t answer the “soon” part. Rest assured, though, that the right people are on it.