Мутируемый заимствование себя не меняется до неизменяемого

Этот код не проходит проверку страшного заимствования (игровая площадка):

struct Data {
    a: i32,
    b: i32,
    c: i32,
}

impl Data {
    fn reference_to_a(&mut self) -> &i32 {
        self.c = 1;
        &self.a
    }
    fn get_b(&self) -> i32 {
        self.b
    }
}

fn main() {
    let mut dat = Data{ a: 1, b: 2, c: 3 };
    let aref = dat.reference_to_a();
    println!("{}", dat.get_b());
}

Ошибка:

error[E0502]: cannot borrow 'dat' as immutable because it is also borrowed as mutable
  --> <anon>:19:20
   |
18 |     let aref = dat.reference_to_a();
   |                --- mutable borrow occurs here
19 |     println!("{}", dat.get_b());
   |                    ^^^ immutable borrow occurs here
20 | }
   | - mutable borrow ends here

Может кто-нибудь объяснить, почему это так? Я бы подумал, что изменяемый заем dat преобразуется в неизменяемый при возврате reference_to_a(), потому что эта функция возвращает только неизменяемую ссылку. Является ли проверка заимствований еще недостаточно умной? Это запланировано? Есть ли способ обойти это?

Ответ 1

Время жизни отличается от того, является ли ссылка изменчивой или нет. Работа с кодом:

fn reference_to_a(&mut self) -> &i32

Несмотря на то, что время жизни сокращено, это эквивалентно:

fn reference_to_a<'a>(&'a mut self) -> &'a i32

то есть. время входа и выхода одинаково. Это единственный способ назначить время жизни такой функции (если только она не вернула ссылку &'static на глобальные данные), поскольку вы не можете составить время жизни вывода из ничего.

Это означает, что если вы сохраните возвращаемое значение, сохранив его в переменной, вы также сохраните &mut self.

Другой способ задуматься о том, что &i32 является подзаголовком &mut self, поэтому действует только до истечения этого срока.

Как указывает @aSpex, это описанный в nomicon.

Ответ 2

Почему это ошибка: хотя более 2,5 лет назад @Chris уже дал более точное объяснение, вы можете прочитать fn reference_to_a(&mut self) → &i32 как объявление, которое:

"Я хочу исключительно позаимствовать self, а затем вернуть общую/неизменную ссылку, которая длится столько же, сколько и первоначальный эксклюзивный заем" (источник)

Очевидно, это может даже помешать мне выстрелить себе в ногу.

Является ли проверка заимствований еще недостаточно умной? Это запланировано?

До сих пор нет способа выразить: "Я хочу исключительно брать себя на время разговора и возвращать общую ссылку с отдельным временем жизни". Он упоминается в nomicon, как указано @aSpex, и указан в списке вещей, которые Rust не позволяет вам делать в конце 2018 года.

Я не мог найти конкретные планы для решения этой проблемы, так как ранее другие улучшения проверки заемщиков считались более приоритетными. Идея о разрешении отдельных "жизненных ролей" для чтения/записи (Ref2<'r, 'w>) была упомянута в NLL RFC, но, насколько я понимаю, никто не превратил ее в собственный RFC.

Есть ли способ обойти это? Не совсем, но в зависимости от причины, по которой вам это понадобилось в первую очередь, могут подойти другие способы структурирования кода: