Мне интересно узнать об ошибках компилятора для следующей функции, которая является примером из книги Rust Brown Book (без редактирования).
fn remove_zeros(v: &mut Vec<i32>) {
for (i, t) in v.iter().enumerate().rev() {
if *t == 0 {
v.remove(i);
v.shrink_to_fit();
}
}
}
Сначала я думал, что аргумент v:&mut vec<i32>
— это изменяемая ссылка, поэтому он не может сосуществовать с v.iter().enumerate().rev()
. Но затем я обнаружил, что ошибка ничего не говорит о вводе аргумента, а вызывает ошибку только при первом реальном изменяемом использовании v.
error[E0502]: cannot borrow `*v` as mutable because it is also borrowed as immutable
--> test.rs:5:13
|
3 | for (i, t) in v.iter().enumerate().rev() {
| --------------------------
| |
| immutable borrow occurs here
| immutable borrow later used here
4 | if *t == 0 {
5 | v.remove(i);
| ^^^^^^^^^^^ mutable borrow occurs here
Разве не было бы логичнее, если бы компилятор проверял совместимость типа аргумента с использованием внутри него?
Сначала меня это не особо волновало, а потом я обнаружил, что Книга коричневой ржавчины продолжает задавать вопросы о том, что такое ошибка «сначала недопустимое неизменяемое заимствование» ИЛИ «сначала недопустимое изменяемое заимствование».
Обратите внимание, что вашу функцию можно записать более эффективно как v.retain (|t| *t != 0); v.shrink_to_fit();
Я думаю, что использование типа внутри цикла может быть правильным. Добавьте break
сразу после shrink_to_fit()
, и все скомпилируется нормально. На самом деле ошибка возникает из-за выполнения следующего цикла.
Что касается вашего вопроса, то совершенно нормально совершать неизменяемые вызовы по ссылке &mut
. Проблема возникает только тогда, когда вы начинаете чередовать изменяемые и неизменяемые вызовы (например, вызов v.remove
, пока v.iter
все еще активен).
@Jmb, но нет ли правила, спрашивающего: «В любой момент времени вы можете иметь либо одну изменяемую ссылку, либо любое количество неизменяемых ссылок на значение». и в этом случае неизменяемая ссылка вплетается в живую область изменяемой ссылки.
@kmdreko да, если аргумент был изменчивым, следует ли вообще остановить v.iter?
@Jmb, значит, чистое объявление изменяемого объекта не учитывается в этом правиле?
Изменяемую ссылку можно легко перезаимствовать как неизменяемую ссылку или даже в другую изменяемую ссылку (часто с более коротким сроком действия):
let v_ref: & _ = &*v;
let v_mut: &mut _ = &mut *v;
Это делается компилятором неявно все время при вызове методов и передаче параметров - как в вашем случае v.iter()
создает неизменяемую ссылку на v
(для передачи как self
) путем повторного заимствования. Это также должно иметь интуитивно понятный смысл: если вам разрешено изменять значение, почему бы вам также не было разрешено не изменять его?
Не нарушает ли это правило «изменяемые ссылки являются исключительными»?
Нет. Повторное заимствование фактически «заблокирует» исходную изменяемую ссылку, пока перезаимствованная ссылка все еще жива. Вот на что здесь жалуется компилятор: вы создали ссылку через v.iter()
, которая удерживается до тех пор, пока не завершится for
-цикл, который «блокирует» v
от использования, но вы используете v
внутри этого цикла, что создает конфликт.
Я не уверен, что понимаю вопрос. Считаете ли вы, что
v.iter()
(неизменяемый вызов) нельзя разрешать, еслиv
является изменяемой ссылкой?