为啥 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)?的主要内容,如果未能解决你的问题,请参考以下文章