DEV Community

Cover image for Encapsulation in Rust and Python
Antonov Mike
Antonov Mike

Posted on • Edited on

Encapsulation in Rust and Python

Disclaimer

I am sure that there are inaccuracies, and maybe even mistakes in this text. I am always happy to receive objective criticism in an acceptable form.

Introduction

Encapsulation is a fundamental concept in object-oriented programming that allows for the bundling of data with the methods that operate on that data. Rust and Python both support encapsulation, which means you can control the access to the attributes and methods of a class or a trait using public, private, and protected modifiers. This concept is implemented in different ways in Rust and Python due to languages design and paradigms. In Rust, we use pub to make methods and structs accessible outside their module because default they are private. In Python, we use double underscores __ to public by default attributes and methods private. Both languages provide mechanisms to control access to the data, ensuring encapsulation.

Encapsulation in Rust

In Rust, encapsulation is achieved through the use of struct and impl blocks. A struct is used to define a data type with named fields, and an impl block is used to define methods associated with the struct. In Rust, encapsulation is achieved through the use of pub and pub(crate) (protected) modifiers. Here's a simple example:
src/employee.rs

// Define a struct with field (it is private by default)
pub struct Account {
    // Private fields
    balance: f64,
}

impl Account {
    // Public method to create a new Account
    pub fn new(initial_balance: f64) -> Account {
        Account {
            balance: initial_balance,
        }
    }

    // Public method to get the balance
    pub fn get_balance(&self) -> f64 {
        self.balance
    }

    // Public method to deposit money
    pub fn deposit(&mut self, amount: f64) {
        self.balance += amount;
    }

    // Private method to apply fees
    fn apply_fees(&mut self) {
        const FEE: f64 = 2.5;
        self.balance -= FEE;
    }
}
Enter fullscreen mode Exit fullscreen mode

src/main.rs

mod employee;
use employee::Account;

fn main() {
    let mut account = Account::new(100.0);
    account.deposit(50.0);
    println!("Balance: {}", account.get_balance());
    // This line will not compile because apply_fees is private
    account.apply_fees();
}
Enter fullscreen mode Exit fullscreen mode

Attempting to call the apply_fees method will result in an error:

error[E0624]: method `apply_fees` is private
  --> src/main.rs:9:13
   |
9  |     account.apply_fees();
   |             ^^^^^^^^^^ private method
   |
  ::: src/employee.rs:26:5
   |
26 |     fn apply_fees(&mut self) {
   |     ------------------------ private method defined here
Enter fullscreen mode Exit fullscreen mode

In this Rust example:

In the Rust example, we have a simple Account struct with a private field called balance. Here’s how it works:

  1. Struct Definition: We define the pub struct Account in the src/employee.rs file. The balance field is private by default (not exposed outside the module).
  2. Public Methods: We provide public methods new, get_balance, deposit. These methods allow controlled access to the balance field.
  3. Private Method: The apply_fees method is marked as private (not accessible outside the struct). It deducts a fixed fee from the account balance.
  4. Usage in main.rs: In the main function, we create an Account instance, deposit money, and print the balance. Attempting to call apply_fees results in a compilation error since it’s private.

Python Example

Python uses a different approach to encapsulation, primarily through the use of underscores to denote private attributes and methods. However, it's important to note that Python's privacy is more of a convention than a strict enforcement. In Python, all attributes and methods are public by default. To make an attribute or method private, you prefix its name with double underscores (__). This does not make the attribute or method truly private, but it signals to other developers that it is intended for internal use within the class. The provided Python example demonstrates encapsulation by defining a class with a private attribute (__balance) and public methods (__init__, get_balance, deposit). The __apply_fees method is intended to be private, but due to Python's lack of strict access control, it can still be accessed from outside the class, although it is not recommended.

# Define a class with private attributes
class Account:
    def __init__(self, initial_balance):
        self.__balance = initial_balance  # Private attribute

    def get_balance(self):
        return self.__balance

    def deposit(self, amount):
        self.__balance += amount

    def __apply_fees(self):  # Private method
        FEE = 2.5
        self.__balance -= FEE

# Create an instance of Account
account = Account(100)
account.deposit(50)
print("Balance:", account.get_balance())
# This line will raise an AttributeError because __apply_fees is private
account.__apply_fees()
Enter fullscreen mode Exit fullscreen mode

Attempting to call the apply_fees method will result in an error:

AttributeError: 'Account' object has no attribute '__apply_fees'
Enter fullscreen mode Exit fullscreen mode

In this Python example:

  1. Constructor and Private Attribute: The __init__ method initializes an Account instance with an initial balance. The __balance attribute is marked as private.
  2. Public Methods: We define public methods: get_balance and deposit (to add funds). These methods allow controlled access to the private __balance.
  3. Private Method: The __apply_fees method is marked as private. It deducts a fixed fee from the account balance.
  4. Usage: We create an account instance, deposit money, and print the balance. Attempting to call __apply_fees raises an AttributeError due to its private status.

Summary

Both examples demonstrate encapsulation by hiding implementation details and providing controlled access to data and methods. Rust achieves encapsulation through module visibility, while Python uses name mangling and access modifiers
Both Rust and Python support encapsulation, but they implement it in ways that reflect their language design principles and paradigms. Rust's approach is more explicit and enforced by the language, while Python's approach is more flexible but relies on convention and name mangling.
It's important to remember that Python's approach to encapsulation is more about convention and best practices rather than strict language enforcement.

Useful articles:

Encapsulation in Rust

Rust Is Beyond Object-Oriented, Part 1: Intro and Encapsulation, Jimmy Hartzell, 2022-12-12
Object-Orientation in Rust
How to Implement Object-Oriented Programming Concepts in Rust, 2023-04-02
How to write properly encapsulation?, Harry Ying, February 2018

Encapsulation in Python

Encapsulation in Python, Updated on: 2021-08-28
Embedding Python in Rust with WebAssembly, Asen Alexandrov, June 2023
Encapsulation in Python
Python Private Attributes
Characteristics of Object-Oriented Languages, doc.rust-lang.org

Rust + Python:

Integrating Rust into Python, Edward Wright, 2021-04-12
Examples for making rustpython run actual python code
Calling Rust from Python using PyO3
Writing Python inside your Rust code — Part 1, 2020-04-17
RustPython, RustPython
Rust for Python developers: Using Rust to optimize your Python code
PyO3 (Rust bindings for Python)
Musing About Pythonic Design Patterns In Rust, Teddy Rendahl, 2023-07-14

Image created by Bing and edited by me

Other articles about the similarities and differences between Rust and Python

  1. Python Classes vs. Rust Traits
  2. Python classes vs Rust structures
  3. Polymorphism in Rust and Python
  4. Abstraction in Rust and Python
  5. Encapsulation in Rust and Python
  6. Composition in Rust and Python

Top comments (0)