Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

How to Make a Function Public in Rust: pub and the Four Levels Between

Sean

Platform Writer

Aug 08, 2026
8 min read

Add pub before fn and the function becomes public. The part that confuses people is that this frequently does not work: a pub function inside a private module is still unreachable, because visibility in Rust is a path, and every segment of that path has to be visible too.

How to Make a Function Public in Rust: pub and the Four Levels Between

Rust also gives you four levels between fully private and fully public, which is more granularity than most languages offer and is genuinely useful once you know they exist. Getting these right is most of what separates a crate that is pleasant to use from one that leaks its internals.

Table of contents

The basic form

Everything in Rust is private to its module by default. Adding pub opts out:

// Private: only callable within this module and its children.
fn helper() {}

// Public: callable from wherever the containing module is visible.
pub fn api() {}

The same applies to structs, enums, traits, modules, and struct fields. A pub struct with private fields is a common and useful shape — callers can hold the type and pass it around without being able to construct it or reach inside.

Note that pub on a struct does not make its fields public. Each field needs its own pub, which is the opposite of the default in most languages and is deliberate: it makes exposing internal representation a decision rather than an accident.

Why pub is not enough

This is the part that generates the confusion:

mod internal {          // private module
    pub fn thing() {}   // public function
}

// Error: module `internal` is private
internal::thing();

The function is public. The module containing it is not. Visibility applies to the whole path, so an item is reachable only if every segment leading to it is visible from where you are calling.

Two fixes, and they mean different things:

// Make the module public too - callers use internal::thing()
pub mod internal {
    pub fn thing() {}
}

// Or re-export - callers use thing(), module stays hidden
mod internal {
    pub fn thing() {}
}
pub use internal::thing;

The second is usually the better design. It lets you organise code into as many modules as you like without that structure becoming part of your public API — so moving thing() to a different module later is not a breaking change.

The four intermediate levels

Between private and pub there are four qualified forms:

  • pub(crate) — visible anywhere in this crate, invisible outside it. This is the most useful one by a wide margin.
  • pub(super) — visible to the parent module and its descendants.
  • pub(in path) — visible within a specific named ancestor module. Precise and rarely needed.
  • pub(self) — visible only in the current module, which is identical to writing nothing. It exists for macro-generated code where the visibility is a parameter.

pub(crate) is the one to reach for constantly. Any helper that several modules in your crate need, but that should not be part of your public API, wants pub(crate). Without it you face a bad choice between duplicating the helper and exposing it to the world permanently.

The value is that it is enforced. A pub(crate) item cannot be used outside the crate even by accident, so you can refactor it freely without a major version bump.

Designing the public surface

The pattern most well-designed crates use: build the module tree for your own organisation, then curate a flat public API with re-exports in lib.rs.

// lib.rs
mod client;
mod config;
mod error;
mod transport;   // never exposed

pub use client::Client;
pub use config::{Config, ConfigBuilder};
pub use error::Error;

Callers write use mycrate::Client rather than use mycrate::client::Client. The internal module layout is free to change because it was never public.

Two habits that go with this:

  1. Keep the public list short and deliberate. Every public item is a compatibility commitment. The cheapest time to not expose something is before anyone depends on it.
  2. Use #![warn(missing_docs)]. Anything public without documentation gets a warning, which turns into useful pressure — you either document it or realise it should not have been public.

The underlying principle is that in Rust, unlike in many languages, the compiler will actually hold you to this. Private means private, so refactoring behind a small public surface is genuinely safe rather than conventionally safe.

Common errors and what they mean

Three you will meet, with their real causes:

private module — you made the item public but not its module. Either make the module public or re-export the item.

function is never used — usually a private function nothing calls. If you meant it to be part of the API, it needs pub and a reachable path; the warning is telling you the path is not reachable.

private type in public interface — a public function takes or returns a private type. Callers could invoke the function but could not name the type it needs, so Rust rejects it at definition. Either make the type public or make the function less public.

That last one is worth dwelling on because it enforces something valuable. It is impossible in Rust to accidentally expose a function nobody can actually use. The compiler checks that your public API is self-consistent, which is a class of API bug that simply does not occur here.

Where this matters most

Visibility discipline pays off in proportion to how many people depend on your code. For a binary crate nobody imports, pub everywhere costs little. For a library, every public item is a promise you have to keep across versions.

The practical middle ground for application code: use pub(crate) as the default for anything shared between modules, and reserve pub for the genuinely small set of things that cross a crate boundary. Even in a binary, that separation makes the codebase easier to navigate, because pub becomes a meaningful signal rather than noise.

For a service that will be deployed rather than published, the same logic applies to the shape of the crate: a small public surface makes it obvious which parts are the interface and which are implementation, and that distinction survives contact with new contributors far better than a naming convention does.

How this fits the rest of the stack

Visibility rules are a compile-time version of a problem that shows up everywhere in a running system: which parts are the interface, and which are implementation you can change freely. Rust enforces it in the compiler; in a deployed service the same boundary is drawn with routes, environment variables, and network access rather than keywords. Services on RunxBuild covers deploying a compiled service from a repository with build logs and a live route. If you are sizing that service alongside a database and storage, the RunxBuild hosting calculator breaks the cost into separate line items.

Useful related references:

FAQ

How do I make a function public in Rust?

Add pub before fn. If it is still unreachable, the containing module is private — visibility applies to the whole path, so every module leading to the function must also be visible from the caller.

What does pub(crate) mean?

The item is visible anywhere within the current crate but not outside it. It is the right choice for helpers shared between modules that should not become part of your public API, and it is enforced by the compiler.

Why does my public function say the module is private?

Because pub on the function only makes it public within its module’s scope. Either mark the module pub as well, or re-export the item from lib.rs with pub use, which keeps the module structure private.

Does pub on a struct make its fields public?

No. Each field needs its own pub. This is deliberate — it makes exposing internal representation an explicit decision rather than something that happens by default.

What is the private type in public interface error?

A public function references a type that is not public, so callers could call it but could not name the type it requires. Either make the type public or reduce the function’s visibility to match.

#rust pub#rust visibility#rust modules#pub(crate)#rust api design