Почему я не могу присвоить одно разыменование ссылки на другое, когда внешнее время жизни отличается? - PullRequest
3 голосов
/ 13 апреля 2020

Я хочу написать следующую функцию:

fn foo<'a, 'b, 'c>(rr1: &'a mut &'c mut u32, rr2: &'b mut &'c mut u32) {
    *rr1 = *rr2;
}

Но компилятор жалуется:

error[E0623]: lifetime mismatch
 --> src/lib.rs:2:12
  |
1 | fn foo<'a, 'b, 'c>(rr1: &'a mut &'c mut u32, rr2: &'b mut &'c mut u32) {
  |                                 -----------       ------------------- these two types are declared with different lifetimes...
2 |     *rr1 = *rr2;
  |            ^^^^ ...but data from `rr2` flows into `rr1` here

Моя ментальная модель времени жизни Руста не соглашается с тем, что код неправильный. Я прочитал тип rr2 как «Ссылка с временем жизни 'b на ссылку с временем жизни 'c на u32». Таким образом, когда я разыменую rr2, я получаю ссылку с временем жизни 'c на u32. Это должно быть безопасно для хранения в *rr1, который имеет тот же тип.

Если мне требуется, чтобы 'b пережил 'c, он работает:

fn foo<'a, 'b: 'c, 'c>(rr1: &'a mut &'c mut u32, rr2: &'b mut &'c mut u32) {
    *rr1 = *rr2;
}

Это делает меня думайте, что тип &'b mut &'c mut u32 означает, что u32 в конце цепочки ссылок доступен только во время пересечения 'b и 'c.

Какое правильное объяснение поведения Rust здесь? И почему ссылки на ссылки ведут себя так, а не так, как я думал?

1 Ответ

7 голосов
/ 13 апреля 2020

Вы не можете разыменовать a &'b mut &'c mut u32 и получать &'c mut u32, потому что:

  • &mut ссылки не могут быть скопированы, поэтому вы не можете copy the &'c mut u32; и
  • Вы не можете выйти за пределы ссылки, поэтому вы также не можете переместить &'c mut u32 (что повлечет за собой зависание внешней ссылки).

Вместо этого компилятор перезагружает u32 с внешним временем жизни, 'b. Вот почему вы получаете сообщение об ошибке, согласно которому данные из rr2 поступают в rr1.

Если foo разрешено компилировать, вы можете использовать его для получения двух &mut ссылок на один и тот же u32, что запрещено правилами ссылок:

let (mut x, mut y) = (10, 20);
let mut rx = &mut x;
let mut ry = &mut y;
foo(&mut rx, &mut ry); // rx and ry now both refer to y
std::mem::swap(rx, ry); // undefined behavior!

Если мне требуется, чтобы 'b переживало' c, это работает

Потому что 'c должен уже пережить 'b, если вам требуется, чтобы 'b также пережил 'c, из этого следует, что 'c = 'b. Обновленная подпись эквивалентна этому:

fn foo<'a, 'b>(rr1: &'a mut &'b mut u32, rr2: &'b mut &'b mut u32)

То есть вы объединили 'c и 'b, и теперь нет проблем с заимствованием &'b mut u32 из rr2, потому что внутренний и внешний обе жизни живут за 'b. Однако теперь компилятор не позволит вам написать испорченный код в примере, который я дал ранее, поскольку ry уже заимствован на весь срок его жизни.

Интересно, если вы сделаете внутреннюю ссылку не- mut, это также работает:

fn foo<'a, 'b, 'c>(rr1: &'a mut &'c u32, rr2: &'b mut &'c u32) {
    *rr1 = *rr2;
}

Это потому, что ссылки & равны Copy, поэтому *rr2 не является повторным заимствованием, а фактически копией внутреннего значения. Для получения дополнительной информации читайте Почему связь времени жизни имеет значение только с изменяемыми ссылками?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...