可见性和私有性

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

这两个术语经常被交替使用,以表达 "此条目是否可在此位置使用?" 的概念。

Rust 的名称解析是在全局层次结构的命名空间中运行。 层级结构中的每个层级都可以视为某个条目。 这些条目是上面提到的其中之一,也包括外部 crate 。 声明或定义新模块可以视为在定义位置将一个新树插入层级结构中。

为了控制接口是否可以跨模块使用, Rust 检查每个条目可见性,以确定是否允许使用。 有时会产生私有性警告,会有 "你使用了另一个模块的私有条目,但不允许。" 的提示。

默认情况下,所有内容都是 私有的 ,有两个例外: 在 pub Trait 中的关联条目默认是公开的;在 pub 枚举中的枚举变量也默认是公开的。 当一个条目被声明为 pub 时,视为外界可访问。例如:

fn main() {}
// 声明一个私有结构体
struct Foo;

// 声明一个公开结构体,其中有一个私有字段
pub struct Bar {
    field: i32,
}

// 声明一个公开枚举类型,其中有两个公开变体
pub enum State {
    PubliclyAccessibleState,
    PubliclyAccessibleState2,
}

在 Rust 中,通过将条目定义为公开或私有,允许在以下两种情况下访问条目:

  1. 如果条目是公开的,则可以从一些模块 m 外部访问它,如果你可以从 m 访问所有条目的祖先模块,则还可以通过重新导出的方式命名该条目。
  2. 如果条目是私有的,则当前模块及其子代可以访问它。

这两种情况对于创建公开 API 的模块层级结构并隐藏内部实现细节非常有用。以下是一些用例及其含义:

  • 库开发人员需要向链接其库的 crate 公开功能。作为第一种情况的结果,这意味着任何可在外部使用的内容必须从根到目标条目都是 pub 的。链中的任何私有条目都将禁止外部访问。
  • 一个 crate 需要一个仅对自己可用的全局 "工具模块" ,但它不想将该模块公开为公开 API 。为此,crate 层次结构的根将具有一个私有模块,该模块内部具有 "公开 API" 。由于整个 crate 是根的子代,因此整个本地 crate 可以通过第二种情况访问此私有模块。
  • 在为模块编写单元测试时,通常的惯用语法是让待测试的模块的直接子条目命名为 mod test。此模块可以通过第二种情况访问父模块的任何条目,从而可以轻松测试内部实现细节。

在第二种情况中,它提到了私有条目可以被当前模块和其子代模块 "访问" ,但是访问条目的确切含义取决于该条目是什么。 例如,访问模块将意味着查看其中的内容 (以导入更多条目) 。另一方面,访问函数将意味着调用它。 此外,路径表达式和导入语句被认为是访问条目,因为只有在目标在当前可见范围内时,导入/表达式才是有效的。

下面是一个程序示例,说明了上述三种情况:

// 这个模块是私有的,意味着没有外部 crate 可以访问这个模块。然而,因为它在当前 crate 的根目录下是私有的,所以任何在 crate 中的模块都可以访问这个模块中任何公开可见的条目。
mod crate_helper_module {

    // 这个函数可以被当前 crate 中的任何东西使用
    pub fn crate_helper() {}

    // 这个函数 *不能* 被 crate 中的其他任何东西使用。它在 `crate_helper_module` 之外不可公开访问,因此只有这个当前模块及其子代可以访问它。
    fn implementation_detail() {}
}

// 这个函数是 "对根可见的公开",这意味着它可以在链接到这个 crate 的外部 crate 中使用。
pub fn public_api() {}

// 类似于 'public_api',这个模块是公开的,因此外部 crate 可以查看其内部。
pub mod submodule {
    use crate::crate_helper_module;

    pub fn my_method() {
        // 通过上述两条规则的组合,本地 crate 中的任何条目都可以调用辅助模块的公开接口。
        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 必须是正在声明其可见性的条目的祖先模块。
  • pub(crate) 使得一个条目在当前 crate 内可见。
  • pub(super) 使得一个条目对其父模块可见。这等价于 pub(in super)
  • pub(self) 使得一个条目对当前模块可见。这等价于 pub(in self) 或者不使用 pub

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

这是一个例子:

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() {}

        // 此函数在整个 crate 中可见
        pub(crate) fn crate_visible_fn() {}

        // 此函数在 `super` 中可见
        pub(super) fn super_mod_visible_fn() {
            // 因为在同一 `mod` 中,所以此函数可见
            inner_mod_visible_fn();
        }

        // 此函数仅在 `inner_mod` 中可见,
        // 这等同于将其声明为 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();

        // 由于已经在 `inner_mod` 的外部,因此此函数不再可见。
        // 错误! `inner_mod_visible_fn` 是私有的
        //inner_mod::inner_mod_visible_fn();
    }
}

fn bar() {
    // 因为我们在同一 crate 中,所以此函数仍然可见。
    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() }

注意: 这种语法只是增加了一个对条目可见性的限制,它并不保证该条目在指定范围内的所有部分都可见。 要访问一个条目,它的所有父级条目直到当前范围仍然必须可见。

重新导出和可见性

Rust 允许通过 pub use 公开且重新导出条目。因为这是一个使其公开的指令,从而允许通过上面的规则在当前模块中使用该条目。 这实际上允许了对重新导出的条目的公开访问。例如,这个程序是有效的:

pub use self::implementation::api;

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

fn main() {}

这意味着,外部 crate 通过 implementation::api::f 引用将违反私有性,而允许以路径 api::f 引用。

当重新导出一个私有条目时,可以将其视为通过重新导出 "私有链条" 的快捷路径,而不是像通常一样通过命名空间层次结构传递。