Как выбор получателя метода по владению или по заимствованию определяет, останется ли объект доступен после...

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

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

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

Если метод принимает получателя по владению, вызов перемещает объект в метод, поэтому прежнее имя больше нельзя использовать. Получатель &self только заимствует объект, а &mut self временно получает исключительный доступ; после завершения такого вызова владелец остаётся доступен.

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

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

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

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

Метод, принимающий self, может поглощать объект: например, извлекать его внутренний ресурс или превращать его в другой тип. После такого вызова исходная переменная недоступна, потому что объект уже принадлежит методу.

Методы с &self и &mut self ведут себя иначе. Они не меняют владельца, но накладывают ограничения на доступ во время вызова: первый разрешает совместное чтение, второй — эксклюзивное чтение и изменение.

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

У метода есть специальный параметр-получатель:

  • self принимает значение целиком и перемещает его в тело метода;
  • &self создаёт неизменяемое заимствование;
  • &mut self создаёт изменяемое заимствование.
struct Buffer { data: Vec<u8>, } impl Buffer { fn len(&self) -> usize { self.data.len() } fn discard(self) {} } fn use_buffer(buffer: Buffer) { let size = buffer.len(); buffer.discard(); println!("{}", size); }

Вызов len временно заимствует buffer, поэтому после него переменная всё ещё доступна. Вызов discard передаёт buffer по владению; после него использовать это имя нельзя. Метод может не использовать поля, но сам факт параметра self означает передачу владения.

Rust иногда автоматически добавляет заимствование при вызове метода с &self или &mut self. Это не происходит для метода с self: автоматическое заимствование не может превратить операцию передачи владения в безопасную передачу ссылки.

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

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

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

Представим тип конфигурации, который после сборки должен стать неизменяемым объектом запуска. Можно реализовать сборку как метод с &mut self: это позволяет повторно использовать тот же экземпляр, но оставляет промежуточное состояние доступным и требует отдельно контролировать, завершена ли настройка.

Альтернатива — метод с self, возвращающий готовый объект. Такой API гарантирует, что исходную конфигурацию нельзя случайно изменить после сборки. Минус — переменная конфигурации поглощается, поэтому для повторной сборки нужно создать новый экземпляр или явно клонировать данные.

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

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

  1. Можно ли вызвать метод с self, если объект заимствован?

    Нет. Заимствованный объект нельзя переместить через ссылку, поскольку это могло бы оставить ссылку указывающей на значение, которого больше нет на прежнем месте. Сначала все заимствования должны закончиться, а затем нужно передать самого владельца.

  2. Почему метод с &mut self может изменить поля, хотя не владеет объектом?

    Изменяемое заимствование временно предоставляет эксклюзивный доступ к объекту. Владелец на этот период не используется напрямую, поэтому компилятор сохраняет гарантию отсутствия одновременного конфликтующего доступа. После завершения заимствования владелец снова может обращаться к изменённому объекту.

  3. Всегда ли вызов метода с self физически перемещает все байты объекта?

    Не обязательно. Перемещение — семантическая передача владения; для некоторых типов компилятор может оптимизировать физическое копирование или перестановку данных. Однако правила доступа остаются теми же: прежнее имя считается недоступным, а метод становится владельцем значения.