На границе API у вызывающего есть изменяемая ссылка, а функция принимает неизменяемую: как Rust допускает т...

На границе API у вызывающего есть изменяемая ссылка, а функция принимает неизменяемую: как Rust допускает такую передачу, не перемещая исходную ссылку?

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

Краткий ответ

Rust выполняет неявное приведение &mut T к &T через временное совместное повторное заимствование. Исходная изменяемая ссылка не перемещается и обычно снова доступна после завершения вызова, если совместное заимствование больше не используется.

Исторический контекст

Модель заимствований Rust должна одновременно поддерживать безопасный общий доступ к данным и предотвращать конфликт между чтением и изменением. Поэтому изменяемую ссылку можно временно ослабить до неизменяемой, но обратное преобразование невозможно: наличие &T не доказывает право на эксклюзивное изменение значения.

Такой подход позволяет функциям принимать минимально необходимые права доступа. Функция, которой нужно только читать данные, объявляет параметр как &T, а вызывающий может передать ей как обычную, так и изменяемую ссылку.

Постановка проблемы

&mut T не является типом, реализующим обычное копирование: одновременно существовать несколько изменяемых ссылок нельзя. Поэтому наивное понимание может привести к ошибочному выводу, что передача &mut T функции с параметром &T перемещает исходную ссылку и делает её недоступной.

Если бы Rust разрешал такое преобразование через обычное перемещение или позволял сохранять совместную ссылку без ограничений, можно было бы нарушить правило: пока существует активное чтение, изменение того же значения запрещено.

Подробное решение

В позиции вызова Rust применяет coercion — специальное неявное приведение типов. Изменяемая ссылка &mut T превращается в совместное повторное заимствование &T, действующее в течение необходимого времени.

После завершения вызова это заимствование обычно заканчивается, поэтому исходную &mut T можно использовать снова. В современных версиях Rust точная граница заимствования определяется анализом последнего использования, известным как NLL — non-lexical lifetimes.

fn inspect(value: &i32) { println!("{}", value); } fn main() { let mut number = 10; let mutable_ref = &mut number; inspect(mutable_ref); *mutable_ref += 1; }

Вызов inspect(mutable_ref) не передаёт изменяемую ссылку как значение-владельца. Rust временно создаёт из неё совместный доступ к тому же i32; после вызова изменяемая ссылка снова может использоваться.

Пока совместное заимствование активно, через исходную ссылку нельзя изменять значение. Преобразование одностороннее: из &mut T можно получить &T, но из &T нельзя получить &mut T, поскольку неизменяемая ссылка сама по себе не гарантирует эксклюзивность.

Если совместная ссылка сохраняется в структуре или возвращается наружу, её время жизни может быть длиннее одного вызова. Тогда исходную изменяемую ссылку нельзя использовать для изменения значения до окончания действия сохранённого заимствования.

Ситуация из практики

В библиотеке есть функция форматирования, которой требуется только чтение состояния. Рассматривались три варианта: принимать &mut T, принимать значение T или принимать &T.

Параметр &mut T излишне ограничил бы вызывающий код: функция получила бы право на изменение, хотя оно не нужно. Передача T могла бы переместить дорогое или невладеющее значение и сделала бы API менее универсальным.

Выбран &T. Вызывающий код может передать как &T, так и &mut T; во втором случае Rust создаёт временное совместное заимствование. Это сохраняет минимальные права функции, не требует копирования и оставляет возможность изменить значение после завершения чтения.

Что кандидаты часто упускают

  1. Является ли такое преобразование обычным приведением через as?

Нет. as предназначен для явных преобразований определённых типов, например числовых. Преобразование &mut T в &T относится к системе неявных приведений типов и обычно выполняется в специальных coercion-позициях, включая аргументы функций и инициализацию переменных с известным типом.

  1. Можно ли изменить значение через исходную &mut T, пока функция получает &T?

Нет, пока действует переданное совместное заимствование. Rust запрещает одновременно читать значение через &T и изменять его через &mut T; это ограничение относится к периоду действия заимствования, а не обязательно ко всему лексическому блоку.

  1. Что произойдёт, если функция сохранит полученную &T?

Совместное заимствование будет действовать столько, сколько требуется сохранённой ссылке. Исходную &mut T нельзя будет использовать для изменения значения до завершения этого периода. При этом передача всё равно не становится перемещением самой изменяемой ссылки: ограничивается её использование из-за активного заимствования.