Видимость и приватность
Syntax
Visibility →
pub
| pub ( crate )
| pub ( self )
| pub ( super )
| pub ( in SimplePath )
Эти два термина часто используются взаимозаменяемо, и то, что они пытаются передать, — это ответ на вопрос “Можно ли использовать этот элемент в этом месте?”
Разрешение имён в Rust работает на глобальной иерархии пространств имён. Каждый уровень в иерархии можно рассматривать как некоторый элемент. Элементы — это один из тех, что упомянуты выше, но также включают внешние крейты. Объявление или определение нового модуля можно рассматривать как вставку нового дерева в иерархию в месте определения.
Чтобы контролировать, могут ли интерфейсы использоваться между модулями, Rust проверяет каждое использование элемента, чтобы определить, должно ли оно быть разрешено или нет. Здесь генерируются предупреждения о приватности, или иначе “вы использовали приватный элемент другого модуля, и это не было разрешено.”
По умолчанию всё является приватным, с двумя исключениями: Ассоциированные
элементы в pub трейте являются публичными по умолчанию; Варианты перечислений
в pub перечислении также являются публичными по умолчанию. Когда элемент объявлен как pub,
его можно рассматривать как доступный внешнему миру. Например:
fn main() {} // Объявляем приватную структуру struct Foo; // Объявляем публичную структуру с приватным полем pub struct Bar { field: i32, } // Объявляем публичное перечисление с двумя публичными вариантами pub enum State { PubliclyAccessibleState, PubliclyAccessibleState2, }
С понятием элемента как публичного или приватного Rust разрешает доступ к элементам в двух случаях:
- Если элемент публичный, то он может быть доступен извне из некоторого модуля
m, если вы можете получить доступ ко всем модулям-предкам элемента изm. Вы также потенциально можете называть элемент через реэкспорты. Смотрите ниже. - Если элемент приватный, он может быть доступен текущим модулем и его потомками.
Эти два случая удивительно мощны для создания иерархий модулей, открывающих публичные API, скрывая внутренние детали реализации. Чтобы помочь объяснить, вот несколько случаев использования и что они влекут:
-
Разработчику библиотеки нужно предоставить функциональность крейтам, которые линкуются с их библиотекой. Как следствие первого случая, это означает, что всё, что можно использовать извне, должно быть
pubот корня до целевого элемента. Любой приватный элемент в цепочке запретит внешние доступы. -
Крейту нужен глобально доступный “вспомогательный модуль” для себя, но он не хочет раскрывать вспомогательный модуль как публичное API. Чтобы достичь этого, корень иерархии крейта имел бы приватный модуль, который затем внутренне имеет “публичное API”. Поскольку весь крейт является потомком корня, то весь локальный крейт может обращаться к этому приватному модулю через второй случай.
-
При написании модульных тестов для модуля часто общепринятой идиомой является наличие непосредственного потомка тестируемого модуля с именем
mod test. Этот модуль мог бы обращаться к любым элементам родительского модуля через второй случай, что означает, что внутренние детали реализации также могут быть бесшовно протестированы из дочернего модуля.
Во втором случае упоминается, что приватный элемент “может быть доступен” текущим модулем и его потомками, но точное значение доступа к элементу зависит от того, что представляет собой элемент.
Доступ к модулю, например, означал бы заглядывание внутрь него (чтобы импортировать больше элементов). С другой стороны, доступ к функции означал бы, что она вызывается. Дополнительно, выражения путей и операторы импорта считаются доступом к элементу в том смысле, что импорт/выражение действителен только если цель находится в текущей области видимости.
Вот пример программы, которая иллюстрирует три случая, описанные выше:
// Этот модуль приватный, что означает, что никакой внешний крейт не может получить доступ к этому // модулю. Однако, поскольку он приватный в корне текущего крейта, любой // модуль в крейте может получить доступ к любому публично видимому элементу в этом модуле. mod crate_helper_module { // Эта функция может использоваться чем угодно в текущем крейте pub fn crate_helper() {} // Эта функция *не может* использоваться чем-либо ещё в крейте. Она не // публично видима вне `crate_helper_module`, поэтому только этот // текущий модуль и его потомки могут получить к ней доступ. fn implementation_detail() {} } // Эта функция "публична для корня", что означает, что она доступна внешним // крейтам, линкующимся с этим. pub fn public_api() {} // Аналогично 'public_api', этот модуль публичный, поэтому внешние крейты могут заглядывать // внутрь него. pub mod submodule { use crate::crate_helper_module; pub fn my_method() { // Любой элемент в локальном крейте может вызывать публичный интерфейс // вспомогательного модуля через комбинацию двух правил выше. crate_helper_module::crate_helper(); } // Эта функция скрыта от любого модуля, который не является потомком // `submodule` fn my_implementation() {} #[cfg(test)] mod test { #[test] fn test_my_implementation() { // Поскольку этот модуль является потомком `submodule`, ему разрешено // обращаться к приватным элементам внутри `submodule` без нарушения // приватности. super::my_implementation(); } } } fn main() {}
Чтобы программа на Rust прошла проверку приватности, все пути должны быть допустимыми доступами согласно двум правилам выше. Это включает все use-операторы, выражения, типы и т.д.
pub(in path), pub(crate), pub(super), и pub(self)
В дополнение к публичному и приватному, Rust позволяет пользователям объявлять элемент как
видимый только в пределах заданной области. Правила для ограничений pub таковы:
pub(in path)делает элемент видимым в пределах предоставленногоpath.pathдолжен быть простым путём, который разрешается в модуль-предок элемента, чья видимость объявляется. Каждый идентификатор вpathдолжен ссылаться непосредственно на модуль (а не на имя, введённое операторомuse).
pub(crate)делает элемент видимым в пределах текущего крейта.
pub(super)делает элемент видимым для родительского модуля. Это эквивалентноpub(in super).
pub(self)делает элемент видимым для текущего модуля. Это эквивалентноpub(in self)или отсутствиюpubвообще.
2018 Edition differences
Начиная с редакции 2018, пути для
pub(in path)должны начинаться сcrate,selfилиsuper. Редакция 2015 также может использовать пути, начинающиеся с::или модули из корня крейта.
Вот пример:
pub mod outer_mod { pub mod inner_mod { // Эта функция видима в пределах `outer_mod` pub(in crate::outer_mod) fn outer_mod_visible_fn() {} // То же, что выше, это действительно только в редакции 2015. pub(in outer_mod) fn outer_mod_visible_fn_2015() {} // Эта функция видима для всего крейта pub(crate) fn crate_visible_fn() {} // Эта функция видима в пределах `outer_mod` pub(super) fn super_mod_visible_fn() { // Эта функция видима, поскольку мы в том же `mod` inner_mod_visible_fn(); } // Эта функция видима только в пределах `inner_mod`, // что то же самое, что оставить её приватной. pub(self) fn inner_mod_visible_fn() {} } pub fn foo() { inner_mod::outer_mod_visible_fn(); inner_mod::crate_visible_fn(); inner_mod::super_mod_visible_fn(); // Эта функция больше не видима, поскольку мы вне `inner_mod` // Ошибка! `inner_mod_visible_fn` приватна //inner_mod::inner_mod_visible_fn(); } } fn bar() { // Эта функция всё ещё видима, поскольку мы в том же крейте outer_mod::inner_mod::crate_visible_fn(); // Эта функция больше не видима, поскольку мы вне `outer_mod` // Ошибка! `super_mod_visible_fn` приватна //outer_mod::inner_mod::super_mod_visible_fn(); // Эта функция больше не видима, поскольку мы вне `outer_mod` // Ошибка! `outer_mod_visible_fn` приватна //outer_mod::inner_mod::outer_mod_visible_fn(); outer_mod::foo(); } fn main() { bar() }
Note
Этот синтаксис только добавляет ещё одно ограничение к видимости элемента. Он не гарантирует, что элемент видим во всех частях указанной области. Чтобы получить доступ к элементу, все его родительские элементы до текущей области также должны быть видимы.
Реэкспорт и видимость
Rust позволяет публично реэкспортировать элементы через директиву pub use. Поскольку
это публичная директива, это позволяет использовать элемент в текущем
модуле через правила выше. Это по существу позволяет публичный доступ к
реэкспортированному элементу. Например, эта программа действительна:
pub use self::implementation::api; mod implementation { pub mod api { pub fn f() {} } } fn main() {}
Это означает, что любой внешний крейт, ссылающийся на implementation::api::f,
получил бы нарушение приватности, в то время как путь api::f был бы разрешён.
При реэкспорте приватного элемента можно считать, что это позволяет “цепочке приватности” быть сокращённой через реэкспорт вместо прохождения через иерархию пространств имён, как это было бы обычно.