可见性与隐私

语法
可见性 :
      pub
   | pub ( crate )
   | pub ( self )
   | pub ( super )
   | pub ( in SimplePath )

这两个术语经常互换使用,它们试图传达的是对以下问题的答案:“这个项(item)可以在这个位置使用吗?”

Rust 的名称解析基于全局的命名空间层级结构运行。层级结构中的每一层都可以视为某个项。这些项包括上面提到的那些,但也包含外部 crate。声明或定义一个新的模块可以视为在定义位置向层级结构中插入一棵新的树。

为了控制接口是否可以在模块间使用,Rust 会检查每个项的使用,以确定是否应该允许。这时会生成隐私警告,或者提示“你使用了另一个模块的私有项且未获得允许”。

默认情况下,一切都是私有的,有两个例外:`pub` Trait 中的关联项默认是公有的;`pub` enum 中的枚举变体(enum variant)也默认是公有的。当一个项被声明为 `pub` 时,可以认为它对外部世界是可访问的。例如

fn main() {}
// Declare a private struct
struct Foo;

// Declare a public struct with a private field
pub struct Bar {
    field: i32,
}

// Declare a public enum with two public variants
pub enum State {
    PubliclyAccessibleState,
    PubliclyAccessibleState2,
}

有了项是公有或私有的概念后,Rust 在两种情况下允许访问项

  1. 如果一个项是公有的,那么你可以从某个模块 `m` 外部访问它,前提是你能够从 `m` 访问该项的所有祖先模块。你还可以通过重导出(re-exports)来命名该项。详见下文。
  2. 如果一个项是私有的,它只能被当前模块及其后代访问。

这两种情况在创建暴露公共 API 同时隐藏内部实现细节的模块层级结构时非常强大。为了帮助解释,这里有一些用例及其含义

  • 库开发者需要向链接其库的 crate 暴露功能。根据第一种情况,这意味着任何可在外部使用的东西,都必须从根模块到目标项一路都是 `pub` 的。链条中的任何私有项都将阻止外部访问。

  • 一个 crate 需要一个对自身全局可用的“辅助模块”,但又不希望将该辅助模块作为公共 API 暴露。为此,crate 层级结构的根模块可以包含一个私有模块,该私有模块内部再有一个“公共 API”。由于整个 crate 都是根模块的后代,因此整个本地 crate 可以通过第二种情况访问这个私有模块。

  • 在为一个模块编写单元测试时,一个常见的习惯做法是为待测试模块创建一个紧邻的子模块,命名为 `mod test`。该模块可以通过第二种情况访问父模块的任何项,这意味着内部实现细节也可以从子模块无缝地测试。

在第二种情况下,它提到私有项可以被当前模块及其后代“访问”,但访问一个项的确切含义取决于该项是什么。

例如,访问一个模块意味着查看其内部(以导入更多项)。另一方面,访问一个函数意味着调用它。此外,路径表达式(path expression)和导入语句(import statement)也被视为访问一个项,因为只有当目标在当前可见性范围内时,导入或表达式才是有效的。

这里有一个程序示例,它展示了上述三种情况

// This module is private, meaning that no external crate can access this
// module. Because it is private at the root of this current crate, however, any
// module in the crate may access any publicly visible item in this module.
mod crate_helper_module {

    // This function can be used by anything in the current crate
    pub fn crate_helper() {}

    // This function *cannot* be used by anything else in the crate. It is not
    // publicly visible outside of the `crate_helper_module`, so only this
    // current module and its descendants may access it.
    fn implementation_detail() {}
}

// This function is "public to the root" meaning that it's available to external
// crates linking against this one.
pub fn public_api() {}

// Similarly to 'public_api', this module is public so external crates may look
// inside of it.
pub mod submodule {
    use crate::crate_helper_module;

    pub fn my_method() {
        // Any item in the local crate may invoke the helper module's public
        // interface through a combination of the two rules above.
        crate_helper_module::crate_helper();
    }

    // This function is hidden to any module which is not a descendant of
    // `submodule`
    fn my_implementation() {}

    #[cfg(test)]
    mod test {

        #[test]
        fn test_my_implementation() {
            // Because this module is a descendant of `submodule`, it's allowed
            // to access private items inside of `submodule` without a privacy
            // violation.
            super::my_implementation();
        }
    }
}

fn main() {}

要让一个 Rust 程序通过隐私检查阶段,考虑到上述两条规则,所有路径都必须是有效的访问。这包括所有的 `use` 语句、表达式、类型等。

`pub(in path)`、`pub(crate)`、`pub(super)` 和 `pub(self)`

除了公有和私有之外,Rust 还允许用户声明一个项只在给定的作用域(scope)内可见。`pub` 限制的规则如下:

  • `pub(in path)` 使项在提供的 `path` 内可见。`path` 必须是一个简单路径(simple path),解析为被声明可见性项的一个祖先模块。`path` 中的每个标识符必须直接引用一个模块(而不是 `use` 语句引入的名称)。
  • `pub(crate)` 使项在当前 crate 内可见。
  • `pub(super)` 使项对其父模块可见。这等同于 `pub(in super)`。
  • `pub(self)` 使项对其当前模块可见。这等同于 `pub(in self)` 或根本不使用 `pub`。

版本差异:从 2018 版本开始,`pub(in path)` 的路径必须以 `crate`、`self` 或 `super` 开头。2015 版本也可以使用以 `::` 开头或从 crate 根模块开始的路径。

这里有一个例子

pub mod outer_mod {
    pub mod inner_mod {
        // This function is visible within `outer_mod`
        pub(in crate::outer_mod) fn outer_mod_visible_fn() {}
        // Same as above, this is only valid in the 2015 edition.
        pub(in outer_mod) fn outer_mod_visible_fn_2015() {}

        // This function is visible to the entire crate
        pub(crate) fn crate_visible_fn() {}

        // This function is visible within `outer_mod`
        pub(super) fn super_mod_visible_fn() {
            // This function is visible since we're in the same `mod`
            inner_mod_visible_fn();
        }

        // This function is visible only within `inner_mod`,
        // which is the same as leaving it private.
        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();

        // This function is no longer visible since we're outside of `inner_mod`
        // Error! `inner_mod_visible_fn` is private
        //inner_mod::inner_mod_visible_fn();
    }
}

fn bar() {
    // This function is still visible since we're in the same crate
    outer_mod::inner_mod::crate_visible_fn();

    // This function is no longer visible since we're outside of `outer_mod`
    // Error! `super_mod_visible_fn` is private
    //outer_mod::inner_mod::super_mod_visible_fn();

    // This function is no longer visible since we're outside of `outer_mod`
    // Error! `outer_mod_visible_fn` is private
    //outer_mod::inner_mod::outer_mod_visible_fn();

    outer_mod::foo();
}

fn main() { bar() }

注意

这种语法只是对项的可见性增加了额外的限制。它不保证项在指定范围内的所有部分都可见。要访问一个项,其直到当前范围的所有父项也必须是可见的。

重导出与可见性

Rust 允许通过 `pub use` 指令公开重导出项。因为这是一个公共指令,它允许根据上述规则在当前模块中使用该项。它本质上允许对重导出项进行公共访问。例如,以下程序是有效的

pub use self::implementation::api;

mod implementation {
    pub mod api {
        pub fn f() {}
    }
}

fn main() {}

这意味着任何引用 `implementation::api::f` 的外部 crate 将收到隐私违规错误,而路径 `api::f` 则被允许。

当重导出私有项时,可以认为是通过重导出缩短了“隐私链”,而不是像通常那样通过命名空间层级结构传递。