Fix E0261: Field of Struct is Private

Rust beginner Linux macOS Windows WebAssembly

1. Symptoms

When you attempt to access a struct field that is not marked as public, the Rust compiler halts compilation and emits error E0261. The error message clearly identifies both the field name and the struct to which it belongs. The diagnostic output includes the exact location of the attempted access and specifies that the field is private to its defining module.

Consider a scenario where you have defined a configuration structure in a module and are trying to initialize or read its fields from another module:

// lib.rs
mod config {
    pub struct ServerConfig {
        host: String,
        port: u16,
    }

    impl ServerConfig {
        pub fn new() -> Self {
            ServerConfig {
                host: String::from("localhost"),
                port: 8080,
            }
        }
    }
}

fn main() {
    let cfg = config::ServerConfig::new();
    cfg.host = String::from("0.0.0.0");  // E0261: field `host` of struct `ServerConfig` is private
    println!("{}", cfg.port);            // E0261: field `port` of struct `ServerConfig` is private
}

The compiler output will resemble:

error[E0261]: field `host` of struct `ServerConfig` is private
 --> src/main.rs:12:5
  |
11 |     let cfg = config::ServerConfig::new();
12 |     cfg.host = String::from("0.0.0.0");
  |         ^^^^ private field
  |
note: the field `host` is not accessible because it's defined in a different module
  --> src/lib.rs:4:5

Similar output appears for the port field access. The error occurs because Rust’s privacy rules restrict field access to the module hierarchy where the struct is defined.

2. Root Cause

Rust implements an encapsulation model based on module boundaries rather than individual types. By default, all struct fields have private visibility, meaning they can only be accessed from within the same module where the struct is defined or from child modules of that module. This default behavior serves as a foundational principle of Rust’s module system: encapsulation is opt-out, not opt-in.

The privacy system exists to enforce data hiding, a critical concept for maintaining invariants. When a struct’s fields are private, the struct’s author retains control over how instances are constructed and modified. External code cannot arbitrarily change field values, which prevents invalid states from emerging. The struct author can expose only those operations that preserve the type’s internal consistency.

In the example above, ServerConfig defines fields host and port without the pub keyword, making them private to the config module. The main function resides in the crate root, which is a sibling module to config, not a child. Consequently, the privacy boundary prevents direct field access from main. This restriction applies regardless of whether you are attempting to read or write the field value.

The Rust compiler enforces this rule at the type-checking phase, ensuring that visibility violations cannot propagate to runtime. The error message specifically references “different module” to guide developers toward understanding the hierarchical nature of Rust’s privacy model. Understanding that modules form a tree structure, with child modules inheriting access to parent module items but not vice versa, is essential for working effectively with Rust’s visibility system.

3. Step-by-Step Fix

Resolving E0261 requires adjusting the visibility of the struct fields or restructuring your code to use the appropriate public interface. Here are the primary approaches:

Approach 1: Make Fields Public

If direct field access is the intended API, mark the fields as public:

Before:

mod config {
    pub struct ServerConfig {
        host: String,
        port: u16,
    }
}

After:

mod config {
    pub struct ServerConfig {
        pub host: String,
        pub port: u16,
    }
}

This approach grants unrestricted access to the fields from any module. Use this only when field values should be directly modifiable without validation.

Approach 2: Provide Public Accessor Methods

For better encapsulation, expose fields through getter methods:

Before:

mod config {
    pub struct ServerConfig {
        host: String,
        port: u16,
    }

    impl ServerConfig {
        pub fn new() -> Self {
            ServerConfig {
                host: String::from("localhost"),
                port: 8080,
            }
        }
    }
}

After:

mod config {
    pub struct ServerConfig {
        host: String,
        port: u16,
    }

    impl ServerConfig {
        pub fn new() -> Self {
            ServerConfig {
                host: String::from("localhost"),
                port: 8080,
            }
        }

        pub fn host(&self) -> &str {
            &self.host
        }

        pub fn port(&self) -> u16 {
            self.port
        }
    }
}

fn main() {
    let cfg = config::ServerConfig::new();
    println!("Host: {}", cfg.host());
    println!("Port: {}", cfg.port());
}

Approach 3: Provide a Builder or Constructor with Parameters

When external code needs to set field values during construction:

Before:

mod config {
    pub struct ServerConfig {
        host: String,
        port: u16,
    }
}

After:

mod config {
    pub struct ServerConfig {
        host: String,
        port: u16,
    }

    impl ServerConfig {
        pub fn new(host: String, port: u16) -> Self {
            ServerConfig { host, port }
        }

        pub fn with_default_host(port: u16) -> Self {
            ServerConfig {
                host: String::from("localhost"),
                port,
            }
        }
    }
}

fn main() {
    let cfg = config::ServerConfig::new(String::from("0.0.0.0"), 8080);
}

Approach 4: Use a Module-Internal Submodule

If the consuming code should have access but the crate should not expose it publicly:

pub mod config {
    pub mod internal {
        use super::ServerConfig;
        pub fn create_config() -> ServerConfig {
            ServerConfig::new()
        }
    }

    pub struct ServerConfig {
        host: String,
        port: u16,
    }

    impl ServerConfig {
        pub(crate) fn new() -> Self {
            ServerConfig {
                host: String::from("localhost"),
                port: 8080,
            }
        }
    }
}

4. Verification

After applying one of the fixes, recompile your project to confirm the error has been resolved:

cargo build

A successful build produces no error output:

   Compiling myproject v0.1.0 (/path/to/myproject)
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s

For the accessor method approach, verify that the values are accessible correctly by running:

cargo run

You should see the expected output displaying the configuration values. If you implemented setter methods, test them by modifying values and observing the results.

To ensure your fix doesn’t break existing functionality, run the test suite:

cargo test

All tests should pass, confirming that the visibility changes maintain the intended behavior while resolving the compilation error.

5. Common Pitfalls

When resolving E0261, developers frequently encounter several recurring mistakes that can complicate the fix or introduce new problems.

Exposing too much data through public fields. Marking struct fields as pub without considering the implications breaks encapsulation entirely. Once a field is public, external code can assign arbitrary values, potentially violating the struct’s invariants. For instance, a ServerConfig might later require that the port number falls within a specific range, but a public field allows any value. Prefer accessor methods that can validate or transform input values.

Confusing module privacy with visibility modifiers. Developers sometimes believe that marking a struct as pub makes all its fields accessible. This assumption leads to confusion when E0261 appears for public structs with private fields. Remember that pub on the struct applies only to the struct itself, not its members. Each field requires explicit visibility declaration.

Attempting cross-module field access from parent modules. The privacy system flows downward through the module tree. A parent module cannot access private members of its children, but children can access private members of parents. If you encounter E0261 when accessing from what you perceive as a “parent” scope, recognize that Rust’s module hierarchy treats the crate root as the topmost container, not the location of your current file.

Neglecting the pub(crate) visibility modifier. Sometimes you want fields accessible throughout the crate but not from external crates. The pub(crate) modifier serves this purpose exactly. Using pub when pub(crate) would suffice over-exposes your API surface.

Forgetting that tuple structs require the same privacy treatment. Tuple struct fields are private by default just like named fields. Accessing elements of a public tuple struct from outside its module also triggers E0261. Apply pub to each element or use the constructor pattern to control initialization.

E0451: pub const items cannot be accessed from a #[test] module in the presence of this error. This error relates to visibility restrictions in test contexts. While E0261 addresses struct field privacy, E0451 concerns how visibility modifiers interact with test modules and external crate access.

E0624: const items cannot be accessed from a #[test] binary. This error extends the theme of restricted access to certain items from testing contexts. Both E0261 and E0624 stem from Rust’s module-level privacy enforcement differing in testing versus production builds.

E0616: field {} is never initialized. This error occurs within the struct’s defining module when a field lacks initialization. While E0261 addresses access violations, E0616 addresses construction problems. Understanding both helps clarify that privacy applies equally during struct construction within the defining module.