为什么 Rust 认为我的私有类型必须是公开的,除非我使用 pub(crate)?

问题描述

我使用宏来生成一个模块,该模块定义了一个函数,该函数返回用户传入的类型:

printf

如果用户传入的是非公开类型:

macro_rules! generate_mod {
    ($name:ident: $type:ty = $e:expr) => {
        mod $name {
            use super::*;
            
            static DATA: $type = $e;
            
            pub fn get() -> &'static $type
            {
                return &DATA;
            }
        }
    }
}

我收到一个错误

struct TestData(i32);

generate_mod!(foo: TestData = TestData(5));

这令人困惑,因为 rustc 抱怨的 private type `TestData` in public interface 方法get 具有相同的可见性。如果我将 TestData 定义中的 pub 更改为 get,一切正常。

I reread the module documentation 并且我仍然不理解这种行为。 pub(crate) 应该只使 pub 可见一层(如文档所述,您需要一个公开链直到您要访问的项目),并且只要包含 {{1} } 不是 get 我不明白该类型是如何逃脱的。 get 使该函数对整个 crate 可见,这听起来在将事情公开方面应该更糟糕,所以我完全不明白为什么 rustc 更喜欢它。

Playground link.

解决方法

如果你展开宏调用,你会得到:

struct TestData(i32);

mod foo {
    use super::*;

    static DATA: TestData = TestData(5);

    pub fn get() -> &'static TestData {
        return &DATA;
    }
}

由于此错误而无法编译:

error[E0446]: private type `TestData` in public interface
 --> src/lib.rs:8:5
  |
1 | struct TestData(i32);
  | --------------------- `TestData` declared as private
...
8 |     pub fn get() -> &'static TestData {
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't leak private type

据说 TestData 是私有的,但它被 pub 函数泄露了。尽管 mod foo 不是 pub,但它在 crate 中的任何位置都可见(因为 root 模块 默认为 pub(crate) - 而 struct TestData不是)。来自您喜欢自己的文档:

// 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 {...}

让我强调相关部分:

因为它在当前 crate 的根目录中是私有的,所以 crate 中的任何模块都可以访问该模块中任何公开可见的项目。

为了编译,你可以让你的结构体pub

pub struct TestData(i32);

或者为了保密,将您的函数设为 pub(super),以便只有来自 foo 的超级模块可以看到它:

#[derive(Debug)]
struct TestData(i32);

mod foo {
    use super::*;

    static DATA: TestData = TestData(5);

    pub(super) fn get() -> &'static TestData {
        return &DATA;
    }
}

fn main() {
    println!("{:?}",foo::get());
}
,

让我们看下面的例子:

pub mod container {
    mod foo {
        pub struct Bar;
        pub(super) struct Baz;
        struct Qux;
    }
    
    fn get_bar() -> foo::Bar {
        foo::Bar
    }

    fn get_baz() -> foo::Baz {
        foo::Baz
    }

    // error[E0603]: struct `Qux` is private
    // fn get_qux() -> foo::Qux {
    //     foo::Qux
    // }

    pub fn pub_get_bar() -> foo::Bar {
        foo::Bar
    }

    // error[E0446]: restricted type `Baz` in public interface
    // pub fn pub_get_baz() -> foo::Baz {
    //     foo::Baz
    // }

    // error[E0603]: struct `Qux` is private
    // pub fn pub_get_qux() -> foo::Qux {
    //     foo::Qux
    // }

    pub use foo::bar;
}

这里有两件事需要考虑:代码所在的位置,以及从哪里可以看到代码。在 Rust 中,可见性的工作方式有两种:

  • “私有”,或仅对指定路径内的代码可见。 “私有”代码的说明符是:

    • pub(self):对位于当前模块中的代码可见
    • pub(super):对位于父模块中的代码可见
    • pub(crate):对位于 crate 根目录中的代码可见
    • pub(in foo::bar):对位于给定路径内的代码可见,该路径必须是当前路径的祖先。1

    正如我之前提到的,你总是可以访问你的祖先可以访问的任何东西,所以这实际上意味着一个项目被认为“位于”它的所有祖先中(例如 foo::bar::baz 也可以看到任何东西 {{ 1}} 或 pub(in foo::bar))。

  • “公共”:这是通过普通的 pub(in foo) 指定的。公共项目在任何地方可见,只要其父项可见。 crate 根中的公共项目在外部可见。

(默认可见性是 pub,而不是 pub(self),尽管它们在 crate 根的含义相同。正如您所见,“pub”有点用词不当,因为 {{ 1}} 实际上使事情变得私有,事实上这是明确地将某些事情私有化的唯一方法)

函数签名要求所有类型至少与函数本身一样可见2

在上面的示例中,pub(crate) 的可见性默认为 pub(...),这实际上表示 container::foo。在 pub(self)(即 pub(in container))内的私有函数的签名中:

  • 我们可以使用 container,因为它是公开的,即使它的父级不是3
  • 我们可以使用 pub(in container),因为它的可见性是 container::foo::Bar,它至少与函数本身一样可见(在本例中,同样可见)。
  • 我们不能使用 container::foo::Baz,因为它的可见性是 pub(in container),它比函数本身更不可见。事实上,我们甚至无法在函数体内访问它,因为我们不在 container::foo::Qux 中。

对于 pub(in container::foo) 中的公共函数:

  • 我们可以使用 container::foo,因为它是公开的,即使它的父级不是3
  • 我们不能使用 container,因为它是私有的,但这是一个公共函数。这就是您面临的问题。
  • 我们不能像以前一样使用 container::foo::Bar

1。在 Rust 2018 中,路径必须是当前路径的祖先。以前,这在技术上可能是一个外部路径,甚至是一个外部 crate,这会使其成为半“公共”(私有给外部模块;奇怪,我知道,尽量避免它)。除此之外,私人物品只能在当前箱子中访问。

2.这有点奇怪,因为您可以对私有类型设置特定的泛型边界。

3.另一个不寻常的怪癖是公共项目总是被认为是公共的,即使它们似乎不能公开访问(至少通过直接路径到他们的声明)。但是,您始终可以“重新导出”它们:在示例中,container::foo::Baz 使 container::foo::Qux 通过 pub use foo::Bar 公开访问。这就是您的代码不起作用的原因。尽管如此,我的示例在没有该语句的情况下编译,并且在外部您可以完全使用 Bar 返回的 container::Bar 的任何实例,即使您无法访问类型本身(并且 rustdoc 甚至不会生成文档为了它)。由于这很奇怪,我强烈建议不要将公共项目放入私有模块中,除非您确保重新导出所有内容。