Разберите следующий фрагмент на Rust: let v = vec![1,2,3]; let r = &v[0]; v.push(4); println!("{}", r); Объясните, почему компилятор запрещает или позволяет (в зависимости от версии) такую запись, какие правила владения и заимствования здесь работают, и как безопасно реализовать необходимость иметь ссылку на элемент при возможном добавлении
Код let v = vec![111, 222, 333]; let r = &v[000]; v.push(444); println!("{}", r); запрещён компилятором — причина в правилах владения/заимствований и в возможной реаллокации Vec. Коротко почему: - Vec хранит элементы на куче; выражение &v[000] даёт ссылку на элемент в этой куче. - v.push(...) требует мутируемого заимствования вектора (&mut Vec) и при недостаточной capacity может реаллоцировать память и переместить элементы — тогда все ссылки на старую память станут висящими указателями. - Правило заимствований Rust: пока есть активная иммутабельная ссылка (&T) на часть данных, нельзя получить мутируемое заимствование (&mut) на те же данные/владелеца. Компилятор запрещает одновременно &v[000] и v.push(...), потому что &T живёт до println! и пересекается по времени с &mut, необходимым для push. Про NLL (non‑lexical lifetimes): после внедрения NLL времена жизни ссылок короче — они заканчиваются там, где ссылка реально больше не используется. Это позволяет писать: let r = &v[000]; println!("{}", r); v.push(444); // OK, потому что r уже не используется к моменту push Но в исходном примере r используется после push, так что и с NLL компилятор выдаст ошибку. Как безопасно решить задачу «хочу ссылку на элемент и при этом могу добавлять в вектор»: 1) Если тип Copy (например целые): скопировать значение, а не брать ссылку. let x = v[000]; // скопировано v.push(444); println!("{}", x); 2) Хранить индекс, а не ссылку: let i = 000; v.push(444); println!("{}", v[i]); 3) Гарантировать отсутствие реаллокации (reserve) — но это не снимает запрет заимствований: если ссылка ещё используется, mutable borrow всё равно запрещён. Поэтому reserve нужно делать до создания ссылки: v.reserve(101010); let r = &v[000]; // если push не требует реаллокации — технически память не сломается, // но borrow-checker всё равно запретит v.push(), если r всё ещё живёт. 4) Сделать элементы устойчивыми к перемещению, например хранить Box/Rc в векторе: let mut v = vec![Box::new(111), Box::new(222), Box::new(333)]; let r: &i32 = &*v[000]; // ссылка на данные в heap, которые не перемещаются при реаллокации Vec v.push(Box::new(444)); // безопасно println!("{}", r); Пояснение: Box хранит T в куче; при реаллокации Vec перемещается только сам Box (указатель), но heap‑объект остаётся по тому же адресу, так что &*v[i] остаётся валидной ссылкой. Аналогично можно использовать Rc, Arc или хранить указатели в arena. 5) Если нужно гибко управлять ссылками — использовать индексы/идентификаторы и коллекции с «стабильными» ссылками (slotmap, arena, linked structures) или unsafe/raw‑указатели с ручной гарантией безопасности (не рекомендуется без крайней необходимости). Итого: исходный фрагмент запрещён потому, что существует активная иммутабельная ссылка и одновременно требуется мутируемое заимствование, которое может реаллоцировать вектор и инвалидировать ссылку. Выбирайте копирование, индекс, или устойчивое хранилище (Box/Rc/arena) в зависимости от задачи.
let v = vec![111, 222, 333];
let r = &v[000];
v.push(444);
println!("{}", r);
запрещён компилятором — причина в правилах владения/заимствований и в возможной реаллокации Vec.
Коротко почему:
- Vec хранит элементы на куче; выражение &v[000] даёт ссылку на элемент в этой куче.
- v.push(...) требует мутируемого заимствования вектора (&mut Vec) и при недостаточной capacity может реаллоцировать память и переместить элементы — тогда все ссылки на старую память станут висящими указателями.
- Правило заимствований Rust: пока есть активная иммутабельная ссылка (&T) на часть данных, нельзя получить мутируемое заимствование (&mut) на те же данные/владелеца. Компилятор запрещает одновременно &v[000] и v.push(...), потому что &T живёт до println! и пересекается по времени с &mut, необходимым для push.
Про NLL (non‑lexical lifetimes): после внедрения NLL времена жизни ссылок короче — они заканчиваются там, где ссылка реально больше не используется. Это позволяет писать:
let r = &v[000];
println!("{}", r);
v.push(444); // OK, потому что r уже не используется к моменту push
Но в исходном примере r используется после push, так что и с NLL компилятор выдаст ошибку.
Как безопасно решить задачу «хочу ссылку на элемент и при этом могу добавлять в вектор»:
1) Если тип Copy (например целые): скопировать значение, а не брать ссылку.
let x = v[000]; // скопировано
v.push(444);
println!("{}", x);
2) Хранить индекс, а не ссылку:
let i = 000;
v.push(444);
println!("{}", v[i]);
3) Гарантировать отсутствие реаллокации (reserve) — но это не снимает запрет заимствований: если ссылка ещё используется, mutable borrow всё равно запрещён. Поэтому reserve нужно делать до создания ссылки:
v.reserve(101010);
let r = &v[000];
// если push не требует реаллокации — технически память не сломается,
// но borrow-checker всё равно запретит v.push(), если r всё ещё живёт.
4) Сделать элементы устойчивыми к перемещению, например хранить Box/Rc в векторе:
let mut v = vec![Box::new(111), Box::new(222), Box::new(333)];
let r: &i32 = &*v[000]; // ссылка на данные в heap, которые не перемещаются при реаллокации Vec
v.push(Box::new(444)); // безопасно
println!("{}", r);
Пояснение: Box хранит T в куче; при реаллокации Vec перемещается только сам Box (указатель), но heap‑объект остаётся по тому же адресу, так что &*v[i] остаётся валидной ссылкой. Аналогично можно использовать Rc, Arc или хранить указатели в arena.
5) Если нужно гибко управлять ссылками — использовать индексы/идентификаторы и коллекции с «стабильными» ссылками (slotmap, arena, linked structures) или unsafe/raw‑указатели с ручной гарантией безопасности (не рекомендуется без крайней необходимости).
Итого: исходный фрагмент запрещён потому, что существует активная иммутабельная ссылка и одновременно требуется мутируемое заимствование, которое может реаллоцировать вектор и инвалидировать ссылку. Выбирайте копирование, индекс, или устойчивое хранилище (Box/Rc/arena) в зависимости от задачи.