Sling Academy
Home/Rust/Understanding Encapsulation in Rust Without Traditional Classes

Understanding Encapsulation in Rust Without Traditional Classes

Last updated: January 06, 2025

When working with the Rust programming language, understanding how encapsulation works without traditional class structures can be illuminating. Encapsulation is a fundamental concept that focuses on bundling the data (variables) and the methods (functions) that operate on the data into a single unit or entity. In many object-oriented languages, this is typically done through classes. However, Rust takes a slightly different approach uniquely fitting its design philosophies.

Structs and Enforced Privileges

In Rust, instead of using classes, data encapsulation is achieved predominantly through structs. A struct in Rust allows you to create custom data types by grouping variables into a single entity. By default, the fields within a struct are private, enforcing encapsulation and ensuring that data is not arbitrarily accessed or modified without using methods that you've explicitly provided.

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

impl Rectangle {
    // Public method to calculate the area
    pub fn area(&self) -> u32 {
        self.width * self.height
    }
}

In this example, the Rectangle struct's width and height are kept private and only accessible through the area method. This offers a controlled interface to interact with the rectangle's data, a key feature of encapsulation.

Using Modules for Scope Management

Beyond structs, Rust leverages its module system to control visibility and encapsulation further. Modules (declared with the mod keyword) can encapsulate functionality by making types and functions private to other components, unless they are made explicitly public.

mod bank {
    pub struct Account {
        balance: f64,
    }

    impl Account {
        pub fn new() -> Self {
            Account { balance: 0.0 }
        }

        pub fn deposit(&mut self, amount: f64) {
            self.balance += amount;
        }
    }
}

In this example, an Account struct resides within the bank module. The balance of an account is hidden from public access, reinforcing encapsulation. Users of this module must call deposit() to modify an account, which provides a better control mechanism leaving room for further validation logic if needed.

Encapsulation with Enums and Pattern Matching

Rust also supports encapsulation using enums, which enables representing data that could be one of several kinds through variants. By coupling enums with pattern matching, Rust provides a robust way to encapsulate sophisticated behaviors alongside data states.

enum TrafficLight {
    Red,
    Yellow,
    Green,
}

impl TrafficLight {
    pub fn change(&self) -> &str {
        match self {
            TrafficLight::Red => "Stop!",
            TrafficLight::Yellow => "Get Ready!",
            TrafficLight::Green => "Go!",
        }
    }
}

With this TrafficLight enum, each light's logic is encapsulated within the change method, maintaining a clear, error-resistant interaction interface.

Advantages and Best Practices

One significant advantage of Rust's approach is how it complements the language’s ownership model, helping to prevent many common programming errors such as data races and invalid memory accesses. By defining data structures with limited exposure, you can ensure that the internal state remains consistent and only modifiable in controlled ways.

Best practices recommend clearly delineating between your API's public and private components, leaning into pub (public) and default-private features of the language. This distinction facilitates easy encapsulation, testing, and when maintained through documentation, offers a powerful way to express how a data structure or module is expected to behave and interact.

Conclusion

While different from more traditional OOP languages, Rust’s encapsulation model is deeply aligned with its focus on safe, concurrent code. By using structures, modules, and enums, Rust provides a flexible toolkit to achieve robust encapsulation -- empowering developers to write reliable and maintainable applications.

Next Article: Defining Structs as “Objects” in Rust: Fields and Methods

Previous Article: Introduction to Object-Oriented Concepts in Rust

Series: Object-Oriented Programming 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