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.