Вызов вложенных методов с существующими изменяемыми ссылками

Следующий код успешно компилируется:

let mut v = vec![1];
let r = &mut v;
r.push(r.len());

пока этот не работает:

let mut v = vec![1];
let r = &mut v;
r.push(v.len());

с ошибкой:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
    |
    |     let r = &mut v;
    |             ------ mutable borrow occurs here
    |     r.push(v.len());
    |            ^ immutable borrow occurs here
    |     r.push(r.len());
    |     - mutable borrow later used here
  • Почему первый пример компилируется правильно? Это потому, что одна и та же ссылка: r используется во внешних и внутренних вызовах? Или это потому, что он применяет RFC 2025, двухфазные заимствования? Или что-то другое?
  • Почему второй пример терпит неудачу, если первый пример успешен? Почему RFC 2025, Two-Phase Borrows соответствует здесь не применимо?

Я подозреваю, что в первом примере ошибок нет, потому что компилятор не создает промежуточных ссылок и использует ту же ссылку: r, чтобы не было множественных заимствований. Однако, если это так, почему следующий код не может быть скомпилирован

let mut v = vec![1];
let r = &mut v;
r.push({r.push(0);1});

person Angelo    schedule 28.12.2020    source источник


Ответы (2)


Во втором примере v по-прежнему взаимно заимствуется, когда вы пытаетесь получить его длину. Vec::len принимает &self, поэтому получение его длины означало бы неизменное заимствование, в то время как оно уже заимствовано изменчиво.

Доступ с len() по r все еще в порядке, потому что вы заимствуете через тот же заем, а не заимствуете его снова.


Обратите внимание, что даже первый пример не работает в Rust 1.30 и ранее (или 1.35 с версией 2015 года), потому что он полагается на NLL (нелексическое время жизни). Проблема с NLL заключается в том, что не совсем интуитивно понятно, что разрешено. По сути, это означает, что заимствования, которые не переживают лексический объем данных, допустимы, а также также несколько других интуитивно правильных случаев. Ваш третий пример - это один из случаев, которые все еще не разрешены NLL.

person Peter Hall    schedule 28.12.2020
comment
Я разместил здесь тот же вопрос: users.rust-lang.org/t/ - person Angelo; 29.12.2020
comment
Я хотел бы знать, почему третий пример не разрешен NLL. Как компилятор оценивает код, чтобы рассматривать первый пример нормально, а третий - нет? - person Angelo; 29.12.2020
comment
Это действительно моя точка зрения. Можно рассуждать о лексическом времени жизни, но NLL просто означает, что позволяет больше, и нелегко точно описать пределы. - person Peter Hall; 29.12.2020
comment
просто уточнение: когда вы говорите NLL, вы также имеете в виду двухфазные заимствования или нет? - person Angelo; 29.12.2020
comment
Фактическая реализация средства проверки заимствований с NLL (будь то двухфазная модель, многослойные заимствования или мел) трудно представить, потому что это с точки зрения MIR, а не кода, который вы на самом деле написали. Возможно, удастся придумать хорошую ментальную модель, чтобы предсказать, какой код Rust удовлетворит или не удовлетворит текущую реализацию средства проверки заимствований, но у меня ее нет. В целом, я уверен в своем понимании лексических заимствований и некоторых нелексических случаев, основанных на опыте. Я не мог записать правила, кроме лексических падежей, и не уверен, что многие люди могли бы это сделать. - person Peter Hall; 29.12.2020
comment
И я не уверен, что усилия того стоят: я бы также ожидал, что в будущем будет принято больше программ, либо от улучшений текущей реализации, либо от возможного перехода на мел. - person Peter Hall; 29.12.2020
comment
Хорошо, спасибо, не могли бы вы взглянуть на мой ответ и сказать, звучит ли он разумно для вас? - person Angelo; 30.12.2020

1-й пример:

let mut v = vec![1];
let r = &mut v;
r.push(r.len());

Без двухфазного заимствования код не будет компилироваться, потому что внешний вызов создает повторный заимствование r: &mut *r, а внутренний вызов - новый неизменяемый заимствование того же значения: &*r.

При двухэтапном заимствовании первый повторный заем преобразуется в &mut2 *r и позже активируется, когда второй повторный заем выходит за рамки.

2-й пример:

let mut v = vec![1];
let r = &mut v;
r.push(v.len());

Даже с двухфазными заимствованиями он не компилируется.

Внутренний вызов вызывает повторный заимствование v: &mut v, что конфликтует с r.

3-й пример:

let mut v = vec![1];
let r = &mut v;
r.push({r.push(0);0});

Даже с двухфазными заимствованиями он не компилируется.

Внутренний вызов требует &mut2 повторных заимствований *r, что не допускается 2-фазными заимствованиями, поскольку внешний вызов уже создал &mut2 повторных заимствований *r.

Ссылки:

person Angelo    schedule 30.12.2020