ProgrammingLibrary Developer / Rust Library Developer

How is access control to struct fields implemented in Rust, and how is it related to module visibility? How can public APIs be organized correctly to minimize errors when using structures outside the module?

Pass interviews with Hintsage AI assistant

Answer.

Background:

In languages like C++ or Java, access modifiers (public, private, protected) ensure visibility of class members, but they are quite flexible — and often do not prevent incorrect API usage. In Rust, from early versions, they made the access control system strict and explicitly modular to limit "leakage" of internal details outside.

The Problem:

Structs often need to partially hide themselves from external code: for example, fields are private, while only methods are exposed. If access is not restricted, invariants of the struct can be unintentionally spoiled (for instance, by directly exposing an internal Vec). Reckless publication of a struct makes the API fragile.

The Solution:

In Rust, everything is private by default. The pub keyword is used for explicit exports: you can declare the struct itself as visible, while keeping fields hidden. Methods are declared as pub or private individually. Additionally, non-standard forms like pub(crate) or pub(super) allow fine-tuning of the access level.

Code Example:

mod domain { pub struct User { pub name: String, age: u32, // private field } impl User { pub fn new(name: String, age: u32) -> Self { Self { name, age } } pub fn age(&self) -> u32 { self.age } } } use domain::User; fn main() { let u = User::new("Eve".to_string(), 24); println!("{} {}", u.name, u.age()); // u.age — access error! field is closed outside the module }

Key Features:

  • By default, everything is private, including fields and functions
  • pub only exposes explicitly selected items to the outside
  • pub(crate), pub(super) provide flexible access control for larger projects

Tricky Questions.

Can a struct be pub, but all its fields remain private? How can instances be created outside the module then?

Yes. This is often done: the struct is pub, the fields are private, creation is only through public constructors (for example, pub fn new...).

Will a struct field be visible in the external code when declaring pub struct Foo?

No, by default each field remains private — you need to explicitly declare pub for the field. pub struct only makes the type visible.

Does pub work for enum in the same way?

For enum, pub applies to all variants, but for associated data in variants (e.g., a field inside Variant(value: T)), you still need to explicitly specify pub if you want to make the internals accessible.

Common Mistakes and Anti-Patterns

  • Making all fields pub for simplicity, violating encapsulation
  • Attempting to access private fields directly from external modules (compilation error)
  • Forgetting to create a public method for constructing/modifying the struct if all fields are closed

Real-life Example

Negative Case

In the library, the struct was declared as pub struct Config, all fields also pub — so the user could "see" them. As a result, any external code could arbitrarily change the state, violate invariants, causing a panic out of nowhere.

Pros:

  • Maximum openness and flexibility for the user

Cons:

  • Violation of encapsulation
  • Difficulties with future migration and versioning of the API
  • Increased bugs due to incorrect use of fields

Positive Case

The struct Config is pub, all fields are private. For configuration — only through builder methods, default constructor, or setter/getter functions. In the module's context, invariants cannot be broken.

Pros:

  • Clean API, invariants under control
  • Easier to maintain backward compatibility

Cons:

  • For complex structs — more code (methods, constructors, tests)