ПрограммированиеRust Backend разработчик

Объясните, как работает borrowing и aliasing в Rust. Какие ограничения по одновременному использованию мутабельных и немутируемых ссылок, как это контролируется компилятором и что может пойти не так?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Borrowing (заимствование) в Rust — это механизм временного "одалживания" переменной через ссылки. Rust различает немутируемое заимствование (&T) и мутабельное (&mut T). Одновременно может существовать сколько угодно немутируемых ссылок, но только одна мутабельная. Не допускается одновременное существование мутабельных и немутируемых ссылок на один и тот же объект.

Это правило гарантирует отсутствие data race на этапе компиляции и делает Rust безопасным для конкурентного программирования.

Пример:

let mut value = 5; let r1 = &value; let r2 = &value; // let r3 = &mut value; // ОШИБКА: нельзя создавать &mut пока есть & println!("{} {}", r1, r2); // r3 запрещен до конца области видимости r1/r2

Вопрос с подвохом

Вопрос: Можно ли в одной области видимости создать одну &mut ссылку и сколько угодно & ссылок на один объект?

Типичный неверный ответ: Да, но только если они не пересекаются по времени жизни.

Правильный ответ: Одновременно не может существовать мутабельная и немутируемая ссылка на один объект (даже если обращения к ним не пересекаются статически в коде), пока одна жива — другие запрещены. Безопасность проверяет borrow checker.

Пример:

let mut x = 10; let y = &x; let z = &mut x; // Ошибка, y еще в области видимости println!("{}", y); // y нужен позже

Примеры реальных ошибок из-за незнания тонкостей темы


История

В крупном проекте с параллельной обработкой данных разработчик решил использовать немутируемые ссылки на вектор, а затем попытался получить мутабельную для сортировки. Код работал в тесте, но перестал компилироваться после рефакторинга, т.к. расширился срок жизни немутируемых ссылок.


История

Во внутреннем сервисе изменяли структуру через &mut, сохраняя при этом ссылки на поля для дальнейшей отправки в другой поток. Возникла гонка данных и crash из-за несоблюдения borrow правил — Rust защищает от этого только на этапе компиляции, но ошибки были в unsafe-блоках, где гарантии сняты.


История

Некорректное документирование API: библиотека принимала одновременно & и &mut на разные поля структуры, но из-за aliasing нарушалась инвариантность и возникали трудноуловимые баги с сервисами, интегрировавшими эту библиотеку.