Невозможно взять файл из & mut self (ошибка msg: не может выйти из заимствованного контента)

use std::fs::File;
use std::io::Read;

pub struct Foo {
    maybe_file: Option<File>,
}

impl Foo {
    pub fn init(&mut self) {
        self.maybe_file = Some(File::open("/proc/uptime").unwrap());
    }

    pub fn print(&mut self) {
        let mut file = self.maybe_file.unwrap();
        let mut s = String::new();
        file.read_to_string(&mut s).unwrap();
        println!("Uptime: {}", s);
    }
}

fn main() {}

Компиляция это даст мне:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:14:24
   |
14 |         let mut file = self.maybe_file.unwrap();
   |                        ^^^^ cannot move out of borrowed content

Почему это происходит? Что я могу сделать для его решения?

Ответ 1

self имеет тип &mut Foo в print, то есть это заимствованная изменяемая ссылка на значение типа Foo. Типы в Rust переходят в собственность по умолчанию, то есть, принимая что-то по-значение, статически недействительны источник и не позволяют программисту снова использовать его (если он не будет повторно инициализирован). В этом случае unwrap имеет подпись:

impl Option<T> {
    fn unwrap(self) -> T { ...

То есть он принимает значение Option по значению и, таким образом, пытается поглотить его владение. Следовательно, self.maybe_file.unwrap() пытается использовать данные в maybe_file, которые оставят self, указывая на частично недействительные данные (после этого нельзя использовать поле maybe_file). Невозможно, чтобы компилятор мог применять это с заимствованными ссылками, которые должны быть действительны всегда, поскольку они могут указывать в любом месте, поэтому незаконно перемещаться.

К счастью, можно избежать этой проблемы: метод as_ref создает Option<&T> из &Option<T> и as_mut создает Option<&mut T> из &mut Option<T>. Получаемый Option затем больше не находится за ссылкой, поэтому законно использовать его через unwrap:

let mut file = self.maybe_file.as_mut().unwrap();

Это немного отличается, потому что file имеет тип &mut File вместо file, но, к счастью, &mut File - это все, что необходимо для остальной части кода.

Другой подход к выполнению этой работы - использование ручной сопоставления шаблонов:

match self.maybe_file {
    Some(ref mut file)  => println!(...),
    None => panic!("error: file was missing")
}

Это делает то же самое, что и .as_mut().unwrap(), более явно: ref mut создает ссылку, указывающую непосредственно в память, занятую self.maybe_file, точно так же, как as_mut.