Some people have wrote about this issue before but I'm not sure people have realized the (negative) implication for macros, so maybe I am missing something. What I want to write is this:
#![allow(dead_code)]
mod top {
mod outer {
struct PrivateType();
mod inner {
use super::PrivateType;
pub fn foo() -> PrivateType { PrivateType() }
}
fn use_foo() {
inner::foo();
}
}
}
Intuitively, I would expect this to work because anyone who can use
inner::foo
also has access to PrivateType
.
However, this errors:
error[E0446]: private type `PrivateType` in public interface
--> src/lib.rs:10:13
|
5 | struct PrivateType();
| --------------------- `PrivateType` declared as private
...
10 | pub fn foo() -> PrivateType { PrivateType() }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't leak private type
Assuming you want to keep PrivateType
private, and you want the
ability to call inner::foo
from inside the outer
module, AFAICT
your only choice to fix it is by making foo
be pub(super)
instead
of pub
:
pub(super) fn foo() -> PrivateType { PrivateType() }
Being pub(super)
prevents putting a pub use
declaration in outer
exporting foo
to top
, so it seems the compiler is pre-emptively
worried I might do that, even though I haven't. If you are surprised
by this there is an existing issue that is closed because the behavior
is considered intended.
The nice thing about being able to use pub
instead of pub(super)
would be that it would let the author of the outer
module control
whether or not foo
is exposed to higher level modules without
changing the code of inner
, by choosing whether or not to re-export.
However because of this error the visibility modifier on PrivateType
and on foo
must change together. Either PrivateType
is pub, and is
intended to be used in top
, and then foo
also needs to be pub
,
or PrivateType
is private, and then foo
needs to be pub(super)
.
Why is this a problem? Macros can't generate new identifiers, so if
you want to generate multiple items instead you generate a single
module with multiple items inside. However, if inner
is generated by
say generate_mod!(inner, PrivateType)
, then generate_mod
can't
just assume foo
can be made pub
and let the user control
visibility by deciding whether or not to re-export. Instead the macro
must take the desired visibility as an argument, generate_mod!(inner, PrivateType, pub(super))
. However, that visibility doesn't obviously
map to anything that the user would care about! It's not the
visibility of the generated module, and it's not the visibility of the
passed in type. As a user I would be completely confused if I were asked to supply this argument.
Note that this problem only happens because the compiler is overly conservative and guards against a problem that doesn't actually exist. If it delayed erroring until I actually exposed the type, this would all go away.