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

Posted

技术标签:

【中文标题】为啥 Rust 认为我的私有类型必须是公共的,除非我使用 pub(crate)?【英文标题】:Why does Rust think my private type must be public unless I use pub(crate)?为什么 Rust 认为我的私有类型必须是公共的,除非我使用 pub(crate)? 【发布时间】:2021-04-21 17:04:29 【问题描述】:

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

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));

我收到一个错误:

private type `TestData` in public interface

这令人困惑,因为 rustc 抱怨的 get 方法与 TestData 具有相同的可见性。如果我将get 定义中的pub 更改为pub(crate),一切正常。

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

Playground link.

【问题讨论】:

可能会掩盖宏的东西,因为没有它就可以复制:playground 这能回答你的问题吗? How to reference private types from public functions in private modules? @kmdreko 不,如果有任何事情增加了我的困惑:) 【参考方案1】:

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

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,它在板条箱中的任何位置都可见(因为根模块 默认为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(crate)!我认为你总是必须使用pub 来让内部模块暴露给 crate 根以让它们暴露给其他 crate ......但是如果它们仍然可以被整个 crate 访问,那么一个***pub use inner::nested::foo 就足够了? 我认为您声称模块默认为 pub(crate) 的说法是不正确的。如果您的声明为真,则删除 this example 中的 pub(crate) 应该不会产生影响,但它会改变示例是否编译。 该声明仅对根模块有效。您的示例不使用根模块。它的工作原理如上面突出显示的文档 sn-p 中所述......我将在答案中澄清这一点。【参考方案2】:

我们看下面的例子:

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 也可以看到任何内容 pub(in foo::bar)pub(in foo))。

“Public”:这是通过普通的pub 指定的。只要其父项可见,公共项在任何地方都是可见的。 crate 根目录中的公共项目对外可见。

(默认可见性是pub(self),而不是pub(crate),尽管它们在板条箱根目录中的含义相同。正如您所看到的,“pub”有点用词不当,因为pub(...)实际上是在制造东西私有的,事实上它是明确将某些东西设为私有的唯一方法)

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

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

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

对于container 内的公共功能:

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

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

2。这有点奇怪,因为您可以对私有类型设置某些通用范围。

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

【讨论】:

以上是关于为啥 Rust 认为我的私有类型必须是公共的,除非我使用 pub(crate)?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Rust 认为泄漏内存是安全的?

为啥 Vec::len 是方法而不是公共属性?

C#:为啥我必须在类的变量中使用公共访问修饰符?

为啥类成员是私有的而属性是公共的? [复制]

为啥非 const 方法是私有的时不调用公共 const 方法?

在 Rust 库中仅公开泛型私有类型的具体变体