Let's look at the following example:
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;
}
There are two things to consider here: where code is located, and where it is visible from. In Rust, visibility works one of two ways:
"Private", or visible only to code inside the specified path. The specifiers for "private" code are:
pub(self)
: visible to code located within the current module
pub(super)
: visible to code located within the parent module
pub(crate)
: visible to code located within the crate root
pub(in foo::bar)
: visible to code located within the given path, which must be an ancestor of the current path.1
As I mentioned before, you can always access anything your ancestors can access, so this effectively means that an item is considered to be "located within" all of its ancestors (e.g. foo::bar::baz
can also see anything pub(in foo::bar)
or pub(in foo)
).
"Public": this is specified via plain pub
. A public item is visible anywhere as long as its parent is visible. Public items in the crate root are visible externally.
(The default visibility is pub(self)
, not pub(crate)
, although they mean the same thing at the crate root. And as you can see, "pub" is a bit of a misnomer since pub(...)
actually makes things private, in fact it's the only way to explicitly make something private)
Function signatures require that all types are at least as visible as the function itself.2
In the example above, the visibility for container::foo
defaults to pub(self)
, which effectively means pub(in container)
. In the signature of private functions within container
(ie. pub(in container)
):
- We can use
container::foo::Bar
since it is public, even though its parent is not.3
- We can use
container::foo::Baz
because its visibility is pub(in container)
, which is at least as visible as the function itself (in this case, equally visible).
- We cannot use
container::foo::Qux
because its visibility is pub(in container::foo)
which is less visible than the function itself. In fact, we can't even access it within the function body because we are not located within container::foo
.
And for public functions within container
:
- We can use
container::foo::Bar
since it is public, even though its parent is not.3
- We cannot use
container::foo::Baz
since it is private, but this is a public function. This is the issue you are facing.
- We cannot use
container::foo::Qux
for the same reason as before.
1. In Rust 2018, the path must be an ancestor of the current path. Previously this could technically be an external path, even an external crate, which would kind of make it semi-"public" (private to an external module; weird, I know, try to avoid it). Other than this, private items are only accessible within the current crate.
2. This is a bit funky because you can have certain generic bounds on private types.
3. Yet another unusual quirk here is that public items are always considered public, even if it seems they are not publicly accessible (at least via a direct path to their declaration). However, you could always "re-export" them: in the example, pub use foo::Bar
makes Bar
publicly accessible via container::Bar
. This is why your code does not work. Still, my example compiles without that statement, and externally you could fully use any instance of Bar
returned by pub_get_bar
, even though you couldn't access the type itself (and rustdoc won't even generate documentation for it). Because of the weirdness of this, I would strongly recommend never putting public items inside a private module unless you make sure to re-export everything.