The Problem
You’ve likely just moved your code into a separate module to clean things up, only to find that it no longer compiles. Rust takes a "need-to-know" approach to data. By default, every struct field is private, even if the struct itself is marked as public.
This error usually crops up during refactoring. You might transition from a single 300-line main.rs to a multi-file project and realize your sub-modules can no longer "see" each other's internal data. If the field isn't explicitly marked for export, the compiler blocks access to protect encapsulation.
The Error Message
The compiler is quite specific when this happens. You will see an output similar to this in your terminal:
error[E0616]: field `balance` of struct `Account` is private
--> src/main.rs:10:20
|
10 | println!("{}", account.balance);
| ^^^^^^^ private field
Why Rust is Strict Here
If you are coming from Java or C#, you might expect a public class to automatically expose its members. Rust requires explicit permission for every single field. Even if you define pub struct User, the fields inside remain hidden from any code outside that specific module. This strictness prevents "spaghetti code" where every part of an application touches the internal state of every other part.
Step-by-Step Fixes
Method 1: The Quick Fix (pub)
If a field is truly meant to be part of your public API, add the pub keyword. This is the standard approach for simple data containers, like a Point or Config struct, where there are no complex internal rules to protect.
Before:
pub struct User {
username: String,
}
After:
pub struct User {
pub username: String, // Now accessible outside the module
}
Method 2: The Middle Ground (pub(crate))
What if you want your whole project to see a field, but you want it hidden from external users of your library? Use pub(crate). It allows visibility within your own project while keeping the external API clean and secure.
pub struct Database {
pub(crate) connection_string: String, // Visible in your project only
password: String, // Strictly private to this module
}
You can also use pub(super) to limit visibility strictly to the parent module.
Method 3: The Professional Choice (Getter Methods)
Exposing fields directly is often a risky habit. It prevents you from changing the internal data type later without breaking every piece of code that uses it. Instead, keep the fields private and provide a public method to read the data.
In src/models.rs:
pub struct Account {
balance: f64,
}
impl Account {
pub fn new(amount: f64) -> Self {
Self { balance: amount }
}
// Read-only getter
pub fn balance(&self) -> f64 {
self.balance
}
}
In src/main.rs:
mod models;
use models::Account;
fn main() {
let my_acc = Account::new(1500.50);
// println!("{}", my_acc.balance); // This triggers E0616
println!("Current balance: {}", my_acc.balance()); // This works!
}
Verification
Run cargo check in your terminal to verify the fix. It is significantly faster than a full build because it skips the code generation phase. If the error E0616 is gone, your visibility settings are correct. If you chose the Getter method, ensure you updated your calling code to use the account.balance() syntax instead of account.balance.
Pro-Tips for Visibility
- **The 80/20 Rule:** Aim to keep 80% of your struct fields private. Only expose what is absolutely necessary for the rest of the program to function.
- **Child Access:** In Rust, child modules can see their parent's private fields, but parent modules cannot see into the private fields of their children.
- **Validation Logic:** Use private fields combined with "setter" methods if you need to ensure a value—like a `user_age`—never drops below zero.
- **Crate-Level Privacy:** Use `pub(crate)` extensively in large projects to avoid leaking internal implementation details to your end-users.

