Sling Academy
Home/Rust/Encapsulation in Rust: Getter and Setter Methods for Struct Fields

Encapsulation in Rust: Getter and Setter Methods for Struct Fields

Last updated: January 03, 2025

Encapsulation is a fundamental concept in object-oriented programming that bundles the data (variables) and methods (functions) that work on the data into a single unit known as a class. It provides the benefit of restricting unauthorized access and modification of the data. In Rust, which is primarily a systems programming language, we achieve encapsulation using structs, and to access or modify the data, we employ getter and setter methods.

Unlike languages like Java or C++, Rust doesn’t automate getter and setter methods. Instead, you manually define methods for accessing and modifying private fields of a struct. This approach gives you explicit control over which fields are accessible and how they can be changed.

Defining a Struct

Let's start by defining a simple struct in Rust:

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

In the example above, we have defined a Rectangle struct with two fields: width and height. If we want to restrict direct modification of these fields, we should make them private and provide getter and setter methods.

Private Fields

In Rust, fields are private to the module where they're defined, by default. You can declare them as explicitly private to indicate this intent clearly:

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

Even though fields are private to the module by default, marking them with pub explicitly exposes the struct's fields, so we usually avoid this if encapsulation is intended.

Implementing Getter Methods

Getter methods allow you to access the private fields. In Rust, you define a getter method within an impl block associated with your struct. Here’s how you implement getter methods for the Rectangle struct:

impl Rectangle {
    pub fn width(&self) -> u32 {
        self.width
    }

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

Each of these methods returns the associated field value. Notice that they take &self as a parameter, meaning they require a reference to the instance from which to read.

Implementing Setter Methods

Setter methods allow you to modify a struct's private fields safely. To implement a setter method, you use a mutable reference &mut self, which gives you the ability to write to the struct fields:

impl Rectangle {
    pub fn set_width(&mut self, width: u32) {
        self.width = width;
    }

    pub fn set_height(&mut self, height: u32) {
        self.height = height;
    }
}

In these examples, we define methods set_width and set_height, each method takes a mutable reference to the struct instance and modifies the respective fields. This way, we can safely control how and when a field is changed, offering a layer of protection and flexibility.

Why Use Getter and Setter Methods?

Getter and setter methods encapsulate the fields in such a way that preventive measures such as validation, logging, or property change notifications can be added seamlessly later. For instance, if in the future you decide that width should not exceed a certain value, you can easily update your setter method to include this validation.

impl Rectangle {
    pub fn set_width(&mut self, width: u32) {
        if width > 0 {
            self.width = width;
        }
    }
}

In the preceding example, the method is updated to only allow a positive width. In practice, using getters and setters helps maintain control over how the data is managed, thereby adhering to the principle of encapsulation.

Conclusion

In summary, getter and setter methods in Rust for struct fields enable you to organize your data effectively while preserving the capacity to enforce rules on how fields are accessed and changed. They encapsulate the data, giving you a robust mechanism to control field read/write permissions and even enhance functionality over time. So, as you design your Rust applications, utilizing getter and setter methods can contribute significantly to building more secure and maintainable code.

Next Article: Using Default Implementations for Rust Struct Initialization

Previous Article: PhantomData in Rust Structs: Zero-Sized Helpers for Generic Constraints

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