Sling Academy
Home/Rust/Understanding Public vs Private Fields in Rust Structs

Understanding Public vs Private Fields in Rust Structs

Last updated: January 03, 2025

When programming with Rust, developers often encounter structures or structs, which are fundamental data types that allow for the grouping and managing of related data. A critical consideration in struct design is the visibility of fields, which are the variables stored within the struct. In Rust, field visibility defaults to private, but understanding how to define and work with both public and private fields is essential for efficient and secure coding.

Defining Structs with Fields

First, let's observe how to define a basic struct in Rust. Suppose we have a struct named Rectangle:

struct Rectangle {
    width: u32,
    height: u32,
}

In this example, both width and height are private because, in Rust, fields inside a struct are private to the module by default. This means they cannot be accessed directly from outside the module that defines the struct.

Accessing Public Fields

To make fields accessible from outside the module, you need to mark them as public using the pub keyword:

struct Rectangle {
    pub width: u32,
    pub height: u32,
}

Now, with the pub keyword, both fields of Rectangle are accessible outside its parent module. These fields can now be read or modified by external code. Here is a demonstration:

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("Width: {}, Height: {}", rect.width, rect.height);
}

This code would successfully compile and run, displaying the output of both width and height.

Maintaining Privacy

Sometimes you may only want certain parts of a struct to be viewable or modifiable from outside, keeping other parts private for encapsulation. This can be useful to hide internal details. Here is how you can achieve this:

struct Rectangle {
    pub width: u32,
    height: u32, // remains private
}

In this setup, while width is accessible outside the module, height is not. This would help ensure that any modifications to height happen strictly through module-defined functions or methods, which might perform additional logic or safeguarding:

impl Rectangle {
    pub fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }

    pub fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle::new(30, 50);
    println!("Area: {}", rect.area());
}

This code example employs an impl block where area is a method that can calculate and provide the area of the rectangle without exposing the height field directly.

Best Practices

  • Limit Exposure: Only set fields to be pub when necessary for external use. Encapsulation helps protect data and maintain control over changes.
  • Provide Getters and Setters: When fields must be controlled or validated upon access or modification, consider using public methods that enforce any constraints or logical rules.
  • Document Visibility Intentions: When writing code that will be consumed by others, make sure visibility is clearly documented to avoid misuse.
  • Encapsulation: Encapsulation facilitates unit testing by allowing independent sections of the codebase to be tested in isolation.

Overall, choosing public or private visibility impacts a program's ability to maintain secure and efficient code interaction across modules. Rust's attention to privacy underscores its commitment to safety, enabling developers to make deliberate decisions about what entities, methods, and data are exposed or protected.

Next Article: Initializing Structs and the Struct Update Syntax in Rust

Previous Article: Working with Tuple Structs and Unit-Like Structs in Rust

Series: Working with structs in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior