Сравните use и pub use: какой из них меняет публичный API модуля?
use только создаёт локальное имя для обращения к существующему элементу; сам по себе он не делает этот элемент доступным пользователям модуля. pub use создаёт публичный путь к элементу и тем самым может изменить внешний API библиотеки.
При этом pub use не отменяет исходные ограничения видимости: нельзя публично переэкспортировать элемент, к которому модуль, содержащий этот pub use, не имеет требуемого доступа.
Модульная система Rust разделяет внутреннюю организацию кода и публичный интерфейс. Без переэкспорта пользователю библиотеки пришлось бы знать внутреннюю структуру модулей, даже если она не имеет значения для использования API.
pub use решает эту проблему: библиотека может хранить реализацию во внутренних модулях, но предоставить стабильные и короткие публичные пути. Это также позволяет менять внутреннюю структуру без обязательного изменения путей, которыми пользуется внешний код.
Рассмотрим библиотеку, где тип находится во внутреннем модуле. Если применить обычный use, имя будет доступно только в области видимости этого импорта с учётом обычных правил приватности. Внешний crate не получит доступ к этому имени через модуль, где расположен use.
Ошибочное ожидание, что любой импорт автоматически становится частью API, приводит к двум проблемам: публичные пользователи не могут обратиться к типу, а внутренняя структура модулей начинает просачиваться в код клиентов. Обратная ошибка — бездумный pub use — может раскрыть нежелательную часть интерфейса или завершиться ошибкой видимости.
use path::Item вводит локальное имя Item. Если сам оператор use не публичен, это имя не становится доступным внешнему коду. Такой импорт предназначен для удобства внутри текущего модуля и его разрешённых областей видимости.
pub use path::Item является публичным переэкспортом. Внешний код может обращаться к элементу через новый публичный путь, если сам элемент доступен из места переэкспорта согласно правилам приватности.
Переэкспорт не создаёт копию типа, функции или значения. Это другое имя и другой путь к тому же элементу, поэтому тип, переэкспортированный из нескольких модулей, остаётся одним и тем же типом.
В примере implementation скрыт как внутренний модуль, но его публичные элементы доступны через корневые пути библиотеки. Внешнему пользователю не нужно знать о существовании implementation.
Важное ограничение: pub use не повышает видимость исходного элемента произвольно. Приватный элемент дочернего модуля не становится публичным только из-за попытки переэкспорта; кроме того, переэкспорт с ограниченной видимостью, например pub(crate) use, предназначен только для текущего crate.
Компромисс заключается в том, что переэкспорт улучшает стабильность и удобство API, но увеличивает число публичных имён, которые библиотека обязуется поддерживать. Поэтому обычно переэкспортируют концептуально важные элементы, а не всю внутреннюю реализацию.
Команда разрабатывает библиотеку конфигурации. Реализация разделена на модули parser, format и validation, но пользователям нужны только Config, parse и ошибка ConfigError.
Первый вариант — заставить пользователей импортировать элементы из внутренних модулей. Его плюс — почти отсутствие переэкспортов; минус — внешний код зависит от внутренней структуры, а любое перемещение файла или модуля становится потенциально ломающим изменением.
Второй вариант — сделать все внутренние модули публичными. Это упрощает прямой доступ, но чрезмерно расширяет API и позволяет пользователям зависеть от деталей, которые библиотека не собиралась поддерживать.
Выбранное решение — оставить внутренние модули непубличными, а нужные публичные элементы переэкспортировать из корня через pub use. В результате внешний API становится коротким и стабильным, а реализацию можно перестраивать без изменения основных путей импорта.
Может ли pub use открыть элемент из непубличного модуля?
Да, это возможно, если переэкспортируемый элемент сам имеет подходящую видимость. Родительский модуль может переэкспортировать публичный элемент своего непубличного дочернего модуля, поэтому внешний пользователь получит доступ через новый путь, но не через исходный путь с непубличным модулем.
Непубличность модуля и публичность его элемента — разные свойства. Скрытие модуля часто используется именно для сокрытия внутреннего пути при сохранении выбранных публичных точек входа.
Делает ли pub use приватную функцию публичной?
Нет. Переэкспорт должен быть разрешён в месте, где написан pub use, а приватный элемент дочернего модуля обычно недоступен его родителю. Кроме того, Rust не позволяет использовать публичный интерфейс для обхода ограничений приватности.
Поэтому для публичного переэкспорта элемент обычно объявляют как публичный в подходящем модуле, после чего при необходимости скрывают сам путь к этому модулю.
Чем pub(crate) use отличается от pub use?
pub(crate) use делает переэкспорт доступным внутри текущего crate, но не внешним crate. Это удобно для формирования общего внутреннего пути между несколькими модулями без включения этого пути в публичный API библиотеки.
pub use предназначен для внешних пользователей и становится частью публичного контракта. Выбор между ними определяет границу поддержки: изменения pub(crate) use обычно затрагивают только внутренний код, тогда как публичный переэкспорт требует учитывать совместимость внешних клиентов.