How to fix error E0603: module is private
Short answer: the path you wrote is real — every name in it exists — but one of the modules
along it is private, and the module you are writing in is not allowed to look inside it. The fix is
to add visibility at the point the path crosses the wall: pub on the module, pub on the item
inside it, or a pub use re-export that keeps the module private and publishes only the one path
callers need. If the private module belongs to a crate you do not own, there is no pub you can
add — the fix is to use the path the crate exports instead. The headline names which step of the
path is closed — module \tcp` is private, function `connect` is private` — so read it as a
map of the wall, not a generic complaint. Where
E0432 means the path does not resolve at all, E0603
means it resolves and visibility stops it.
The error
Section titled “The error”A module with a function in it, called from outside that module:
mod net { mod tcp { pub fn connect() -> &'static str { "connected" } }}
fn main() { println!("{}", net::tcp::connect());}Compiling that with cargo build prints:
error[E0603]: module `tcp` is private --> src/main.rs:10:25 |10 | println!("{}", net::tcp::connect()); | ^^^ ------- function `connect` is not publicly re-exported | | | private module |note: the module `tcp` is defined here --> src/main.rs:2:5 | 2 | mod tcp { | ^^^^^^^
For more information about this error, try `rustc --explain E0603`.Two things in this diagnostic are worth reading closely. First, the note at the bottom: tcp
was declared without a pub, and in Rust that is not a style choice — it is the visibility. Every
item is private by default, and “private” has a precise meaning: an item is visible in the module
that declares it and in that module’s descendants, and nowhere else. tcp is declared inside
net, so it is visible inside net and its children. main sits in the crate root, which is
net’s parent — outside the wall — so net::tcp stops there.
Second, the double underline. The compiler underlines tcp and calls it a private module, but it
also underlines connect and notes that the function is not publicly re-exported. It understood
exactly what the path was reaching for — the connect function — and is reporting the first
locked door on the way there. That is the whole model of this error: a path is a walk from module
to module, and E0603 fires at the first step you are not allowed to take.
Fix one: put pub on the module
Section titled “Fix one: put pub on the module”mod net { pub mod tcp { pub fn connect() -> &'static str { "connected" } }}
fn main() { println!("{}", net::tcp::connect());}This prints:
connectedtcp got the pub; net did not — and the path still works, because visibility is judged one
name at a time. main lives in the same module where net is declared, so reaching net itself
is not crossing any wall; reaching into tcp is. In a real project the same question repeats at
every level of the tree: mod net; in main.rs or lib.rs, mod tcp; inside net.rs, pub fn connect inside tcp.rs. The error’s note points at the line where the closed door was declared,
so the fix usually goes on the line the note names.
The module is open, but the item is not
Section titled “The module is open, but the item is not”E0603 does not only report modules. Once the module is public, the same error fires one level deeper, and the headline changes with it:
mod net { pub mod tcp { fn connect() -> &'static str { "connected" } }}
fn main() { println!("{}", net::tcp::connect());}Compiling that with cargo build prints:
error[E0603]: function `connect` is private --> src/main.rs:10:30 |10 | println!("{}", net::tcp::connect()); | ^^^^^^^ private function |note: the function `connect` is defined here --> src/main.rs:3:9 | 3 | fn connect() -> &'static str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.Now every module on the path is open and the function itself is not — so the headline says
function \connect` is privateinstead ofmodule `tcp` is private. The fix is the same move at the new location: pub fn connect`. A path needs visibility at every name it crosses, and the
diagnostic always names the first name that fails, which is why it is safe to just fix what the
headline points at and recompile.
Don’t publish the module — re-export it
Section titled “Don’t publish the module — re-export it”pub mod tcp opens the whole module: everything inside it becomes reachable from outside, forever,
as net::tcp::whatever. Half the time that is more surface than you meant to hand out. The other
fix keeps tcp private and publishes one path through it:
mod net { mod tcp { pub fn connect() -> &'static str { "connected" } }
pub use self::tcp::connect;}
fn main() { println!("{}", net::connect());}This prints:
connectedCallers now write net::connect, and net::tcp stays invisible — swap the whole tcp module for
a different implementation and no caller notices. That is why so many crates keep their internal
or platform modules private and re-export the useful names at the crate root: the re-export is
the crate’s public API, and the module layout behind it is private.
The self:: in pub use self::tcp::connect; is not decoration. Inside a module, a use path
must start with self::, super::, crate::, or a crate name — a bare tcp::connect does not
resolve, and the compiler offers self:: as its suggestion
(E0432 again, one level down).
When the module is not yours
Section titled “When the module is not yours”Everything so far assumed the private module is in your own code, where adding a pub is yours to
do. When E0603 fires on a path into a dependency, the wall is deliberate: the crate’s author
decided that module is an implementation detail, and no amount of editing on your side changes
that — your cargo build compiles the crate from crates.io, not your wishlist.
What to do instead, in order:
- Check the crate root. Most crates re-export their public API one level deep or at the root:
if
some_crate::value::Valuefails,some_crate::Valuevery often works. The docs.rs front page for the crate is the list of what is actually exported. - Search the crate’s docs for the item’s name. If it appears anywhere public, the docs show the path you are meant to use.
- If it appears nowhere public, it is not part of the API. Reaching internals through
#[doc(hidden)]paths or copied source couples you to a private implementation that can vanish in the next release; the durable fix is the documented way, or a different crate.
How much visibility does a pub grant?
Section titled “How much visibility does a pub grant?”Plain pub is “visible to anyone who can reach the module”. Three narrower forms cover the
in-between cases: pub(crate) makes the item visible anywhere in the current crate but no
further, pub(super) makes it visible in the parent module, and pub(in path) restricts it to a
named ancestor. They matter most in library code with private modules like the tcp example: a
helper that is pub inside tcp but only needed by sibling modules in net can be pub(super)
— reachable through the re-export, invisible to the crate’s users. If a pub feels too wide but
private is too narrow, one of these is usually the answer.
Not this error
Section titled “Not this error”- unresolved import — E0432. That error means the
path does not resolve: a name is misspelled, a module does not exist, or the
usepath needs aself::/crate::prefix. E0603 means the path resolved fine and privacy stopped it. The tell is in the help blocks: E0432 talks about paths that do not exist, E0603 points at the line where the private item was declared. - undeclared crate or module —
E0433. Same
family, earlier in the walk: the very first segment of the path is not a crate or module the
compiler knows — usually a dependency missing from
Cargo.toml. - method is private — E0624. Calling a private method with dot syntax does not say
module is private; it sayserror[E0624]: method `open` is privatewith aprivate method defined herenote. Same wall, different syntax: paths through modules report E0603, dot-calls on methods report E0624.