Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Rust think my private type must be public unless I use pub(crate)?

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.

like image 631
Joseph Garvin Avatar asked Oct 18 '25 11:10

Joseph Garvin


1 Answers

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.

like image 193
Coder-256 Avatar answered Oct 21 '25 03:10

Coder-256