Skip to main content

Visibility

Visibility controls who is allowed to access what.

Rust’s philosophy:

Everything is private by default.

You must explicitly allow access using pub.

Visibility applies to:

  • modules
  • functions
  • structs
  • enums
  • fields
  • methods
  • constants
  • traits

The pub Keyword – What It Means

pub means:

“This item can be accessed from outside the current module.”

But pub does NOT mean globally accessible.

Visibility is still constrained by:

  • module hierarchy
  • crate boundaries

The Golden Rule of Visibility

You can only access an item if every module in the path is visible.

This is the most important rule to remember.

Private by Default (No pub)

mod secrets {
fn hidden() {
println!("Top secret");
}
}

fn main() {
// secrets::hidden(); ❌ error
}

Why?

  • hidden() is private
  • Only accessible inside secrets

Making Functions Public

mod secrets {
pub fn reveal() {
println!("Not so secret");
}
}

fn main() {
secrets::reveal(); // ✅
}

Module Visibility (pub mod)

Modules themselves must also be public.

Common mistake

mod api {
pub fn call() {}
}

fn main() {
// api::call(); ❌ api is private
}

Correct version

pub mod api {
pub fn call() {}
}

fn main() {
api::call(); // ✅
}

Visibility Chains (Path Rule)

Broken visibility chain

mod outer {
mod inner {
pub fn run() {}
}
}

fn main() {
// outer::inner::run(); ❌
}

Fixed

pub mod outer {
pub mod inner {
pub fn run() {}
}
}

fn main() {
outer::inner::run(); // ✅
}

Every level in the path must be pub

Struct Visibility

Struct itself vs fields

mod model {
pub struct User {
pub name: String,
age: u8,
}
}

What’s accessible?

use model::User;

fn main() {
let u = User {
name: "Alice".into(),
// age: 30 ❌ private field
};

println!("{}", u.name); // ✅
}

Key rule

  • pub struct → type is public
  • fields are private unless marked pub

Enum Visibility

Enums are simpler.

pub enum Status {
Active,
Inactive,
}
  • All variants are automatically public
  • No need to mark each variant pub

Method Visibility (impl blocks)

Methods follow the same rule.

pub struct Account {
balance: i32,
}

impl Account {
pub fn new() -> Self {
Self { balance: 0 }
}

fn secret_reset(&mut self) {
self.balance = 0;
}
}
  • new() → public API
  • secret_reset() → internal helper

Visibility with use

use does not change visibility.

Wrong assumption

mod hidden {
fn secret() {}
}

use hidden::secret; // ❌ still private

Must be public first

mod hidden {
pub fn secret() {}
}

use hidden::secret; // ✅

pub(crate) – Crate-Level Visibility

Rust offers fine-grained visibility.

pub(crate) fn internal_only() {}

Accessible:

  • anywhere inside the same crate
  • NOT from other crates

Other Visibility Modifiers

ModifierAccessible From
pubEverywhere
pub(crate)Same crate
pub(super)Parent module
pub(self)Same module only
mod parent {
pub(super) fn call_parent() {}
}

File-Based Example (Real Structure)

src/
├── lib.rs
└── auth/
├── mod.rs
└── login.rs

lib.rs

pub mod auth;

auth/mod.rs

pub mod login;

auth/login.rs

pub fn login_user() {
println!("Logged in");
}

fn helper() {} // private

External usage

use my_lib::auth::login::login_user;

Re-exporting with pub use

This lets you control your public API.

mod internal {
pub struct User;
}

pub use internal::User;

Result

use my_lib::User;

Users don’t need to know about internal

Binary + Library Visibility Pattern

Recommended design

  • Library crate
    • exposes pub APIs
    • hides internals
  • Binary crate
    • uses the public API only
// lib.rs
mod engine;
pub use engine::run;

Why Rust Is So Strict About Visibility

Rust forces you to:

  • design clean APIs
  • avoid accidental coupling
  • make internal changes safely
  • think in terms of boundaries

This is a feature, not friction.