可见性和隐私

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

这两个术语经常可以互换使用,它们试图传达的是对问题“此项目可以在此位置使用吗?”的回答。

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

为了控制接口是否可以在模块之间使用,Rust 检查每个项目的使用情况,以确定是否应该允许。这就是生成隐私警告的地方,或者说是“你使用了另一个模块的私有项目,并且不允许使用”。

默认情况下,一切都是私有的,但有两个例外:pub Trait 中的关联项默认是公共的;pub 枚举中的枚举变体也默认是公共的。当一个项目被声明为 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 外部访问它。你还可以通过重新导出命名该项目。请参见下文。
  2. 如果一个项目是私有的,则当前模块及其后代可以访问它。

这两种情况对于创建暴露公共 API 同时隐藏内部实现细节的模块层次结构来说,非常强大。为了帮助解释,这里有一些用例以及它们会涉及的内容

  • 库开发人员需要向链接到其库的 crate 公开功能。作为第一种情况的结果,这意味着任何可在外部使用的内容都必须从根到目标项目都是 pub 的。链中的任何私有项目都会阻止外部访问。

  • 一个 crate 需要一个全局可用的“辅助模块”供自己使用,但它不想将辅助模块作为公共 API 公开。为了实现这一点,crate 层次结构的根将具有一个私有模块,该模块在内部具有一个“公共 API”。因为整个 crate 是根的后代,所以整个本地 crate 可以通过第二种情况访问此私有模块。

  • 在编写模块的单元测试时,通常的做法是将要测试的模块的直接子模块命名为 mod test。此模块可以通过第二种情况访问父模块的任何项目,这意味着内部实现细节也可以从子模块无缝测试。

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

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

这是一个示例程序,它阐述了上面概述的三种情况

// 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 还允许用户声明一个项目仅在给定的范围内可见。pub 限制的规则如下

  • pub(in path) 使项目在提供的 path 中可见。path 必须是一个简单路径,解析为声明其可见性的项目的祖先模块。path 中的每个标识符都必须直接引用一个模块(而不是由 use 语句引入的名称)。
  • 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 {
        // 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 将被允许。

重新导出私有项目时,可以认为它允许通过重新导出而不是像通常那样通过命名空间层次结构来短路“隐私链”。