3

I'm using a macro to generate a module, and that module defines a function that returns a type that the user passes in:

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

If the user passes in a non-public type:

struct TestData(i32);

generate_mod!(foo: TestData = TestData(5));

I get an error:

private type `TestData` in public interface

Which is confusing, because the get method that rustc is complaining about has the same visibility as TestData. If I change the pub in get's definition to be pub(crate) everything works.

I reread the module documentation and I still don't understand this behavior. pub should only be making get visible one layer up (as the documentation explains you need a chain of publicness down to the item you want to access), and as long as the module containing get isn't pub I don't see how the type could escape. pub(crate) makes the function visible to the whole crate which sounds like it should be strictly worse in terms of making things public, so I'm totally confused why rustc prefers it.

Playground link.

Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165
  • Can probably gloss over the macro stuff since it repros without it: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b13e7c197bc401ae451c53f120f9470c) – kmdreko Jan 17 '21 at 04:40
  • Does this answer your question? [How to reference private types from public functions in private modules?](https://stackoverflow.com/questions/39334430/how-to-reference-private-types-from-public-functions-in-private-modules) – kmdreko Jan 17 '21 at 04:56
  • @kmdreko No if anything that has added to my confusion :) – Joseph Garvin Jan 17 '21 at 06:17

2 Answers2

3

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.

Coder-256
  • 5,212
  • 2
  • 23
  • 51
1

If you expand your macro invocation, you get this:

struct TestData(i32);

mod foo {
    use super::*;

    static DATA: TestData = TestData(5);

    pub fn get() -> &'static TestData {
        return &DATA;
    }
}

which fails to compile due to this error:

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

It's saying that TestData is private but it's leaked by a pub function. Even though mod foo is not pub, it's visible from anywhere in the crate (because a root module is pub(crate) by default - while the struct TestData is not). From the docs you liked yourself:

// 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 {...}

Let me highlight the relevant part:

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.

To make it compile, you could make your struct pub:

pub struct TestData(i32);

Or to keep it private, make your function pub(super) so only the super-module from foo can see it:

#[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());
}
Renato
  • 12,940
  • 3
  • 54
  • 85
  • I think the main thing I was missing is that modules default to `pub(crate)`! I thought you always had to use `pub` to get inner modules exposed to the crate root to get them exposed for other crates... But if they are accessible to the whole crate anyway shouldn't one top level `pub use inner::nested::foo` be enough? – Joseph Garvin Jan 17 '21 at 17:09
  • I think your claim that modules are `pub(crate)` by default is incorrect. Removing `pub(crate)` in [this example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=89af08219808db0a4afb996e413d1df5) should not make a difference if your claim is true, but it changes whether the example compiles. – Joseph Garvin Jan 17 '21 at 19:43
  • The claim is only valid for root modules. Your example doesn't use a root module. It works as described in the highlighted doc snippet above... I will clarify that in the answer. – Renato Jan 18 '21 at 12:11