Ситуация: функция объявлена в одном модуле без pub, а вызывается из соседнего модуля того же crate. Что произойдёт при компиляции?
Код не скомпилируется: соседний модуль не получает доступ к приватной функции, даже если оба модуля находятся в одном crate. Для такого вызова функция должна иметь подходящую видимость, например pub(crate) или pub, а путь к ней тоже должен быть доступен.
Модульная система Rust изначально решает задачу инкапсуляции: детали реализации должны быть скрыты, а публичный интерфейс — явно обозначен. Поэтому принадлежность одному crate сама по себе не отменяет границы модулей.
Такой подход уменьшает связанность компонентов. Изменение приватной функции не становится автоматически изменением контракта для всех частей проекта.
В Rust видимость проверяется относительно модуля, из которого выполняется обращение. Приватный элемент доступен своему модулю и его потомкам, но не соседним модулям и не внешнему коду.
Если ошибочно считать, что «внутри одного crate доступно всё», разработчик столкнётся с ошибкой компиляции. Если же без необходимости сделать функцию глобально публичной, внутренние детали станут частью внешнего API и будут сложнее изменяться.
Для обращения из соседнего модуля функция должна быть объявлена с ограничением видимости, охватывающим место вызова. pub(crate) открывает элемент всему текущему crate, но не внешним crate. Обычный pub делает элемент доступным за пределами crate, если весь путь до него также публичен.
Здесь reset остаётся приватной деталью storage, а clear доступна соседнему модулю api благодаря pub(crate). Модуль storage сам не обязан быть публичным для такого обращения внутри того же crate.
Видимость элемента и доступность пути — разные проверки. Даже публичная функция недоступна через путь, содержащий закрытый модуль, если обращение происходит из внешнего crate. Для более узкого интерфейса можно применять pub(super) или pub(in путь), ограничивая круг модулей-получателей.
Практический компромисс таков: pub подходит для стабильного внешнего API, pub(crate) — для внутреннего API crate, а приватность — для реализации. Чем шире видимость, тем больше зависимостей и тем осторожнее приходится менять сигнатуру или поведение элемента.
В проекте модуль хранения данных содержит низкоуровневую функцию сброса кэша. Модулю мониторинга нужно инициировать эту операцию, но внешним пользователям библиотеки она не должна быть доступна.
Вариант с приватной функцией не работает: мониторинг является соседним модулем. Вариант с pub решает доступ, но раскрывает внутреннюю операцию внешним crate и увеличивает публичный контракт.
Выбран pub(crate) для функции-операции, а низкоуровневая функция сброса оставлена приватной. В результате внутренние модули взаимодействуют через ограниченный API, внешние пользователи не получают лишнего доступа, а реализацию сброса можно менять без изменения публичного интерфейса библиотеки.
Дополнительный вопрос: Достаточно ли сделать функцию pub, если модуль, в котором она объявлена, остаётся приватным?
Ответ: Нет, для внешнего crate этого недостаточно. Видимость проверяется по всему пути: внешний код должен иметь доступ не только к функции, но и к каждому модулю или имени, через которое он к ней обращается. Внутри того же crate приватный модуль может быть доступен его потомкам, поэтому конкретное обращение всё равно нужно оценивать относительно места вызова.
Дополнительный вопрос: Почему приватный элемент может быть доступен дочернему модулю, но недоступен соседнему?
Ответ: Правило приватности Rust распространяется от модуля-владельца вниз по его дереву. Дочерний модуль находится в области, охватываемой приватностью родителя, а соседний модуль находится за пределами этой ветви. Это позволяет родителю скрывать детали от остальных частей дерева, одновременно предоставляя их своим внутренним подмодулям.
Дополнительный вопрос: Когда для внутреннего API лучше выбрать pub(crate), а когда pub(super)?
Ответ: pub(crate) выбирают, когда элемент должен быть доступен нескольким несвязанным модулям внутри crate. pub(super) подходит, когда доступ должен оставаться в родительской ветви модуля и не распространяться на весь crate. Более узкая видимость лучше защищает архитектурные границы, но может потребовать явного промежуточного метода или изменения структуры модулей.