Мутируемый заимствование себя не меняется до неизменяемого
Этот код не проходит проверку страшного заимствования (игровая площадка):
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.
Есть ли способ обойти это? Не совсем, но в зависимости от причины, по которой вам это понадобилось в первую очередь, могут подойти другие способы структурирования кода:
- Вы можете вернуть копию/клон вместо ссылки
- Иногда вы можете разделить
fn(&mut self) → &T
на две части: одна принимает &mut self
а другая возвращает &T
, как предлагает @Chris здесь - Как это часто бывает в Rust, может помочь перестройка ваших структур, чтобы они были "ориентированными на данные", а не "объектно-ориентированными"
- Вы можете вернуть общую ссылку из метода:
fn(&mut self) → (&Self, &T)
(из этого ответа) - Вы можете сделать сноску взять общую
&self
ссылку и использовать внутреннюю переменчивость (т.е. определение частей Self
, которые должны быть мутировали, как Cell<T>
или RefCell<T>
). Это может показаться обманом, но на самом деле это уместно, например, когда вам нужна изменчивость в качестве детали реализации логически неизменяемого метода. В конце концов, мы заставляем метод принимать &mut self
не потому, что он мутирует части self
, а чтобы он был известен вызывающей стороне, чтобы можно было понять, какие значения могут изменяться в сложной программе.