Composition in Rust and Python

antonov_mike

Antonov Mike

Posted on May 12, 2024

Composition in Rust and Python

Disclaimer

This is more my attempt to explain this topic to myself. How successful it is is for more experienced people to judge. Argumented criticism in an acceptable form is welcome as always

Introduction

Composition in programming is a way of building complex structures by combining simple structures. Rust and Python both support composition, which means you can combine the features of different classes or traits to create more complex and powerful classes or types. This approach promotes code reusability and modularity. Let's explore how composition can be used in both languages to model a car rental system.

Rust Example

In Rust, we can use traits and structs to model different aspects of a car rental system. For example, we might have a Car trait, a Rental struct, and a Customer struct.

trait Car {
    fn get_make(&self) -> &str;
    fn get_model(&self) -> &str;
}

struct Rental {
    car: Box<dyn Car>,
    customer: String,
    rental_days: u32,
}

struct Customer {
    name: String,
    rentals: Vec<Rental>,
}

struct Sedan {
    make: String,
    model: String,
}

impl Car for Sedan {
    fn get_make(&self) -> &str {
        &self.make
    }

    fn get_model(&self) -> &str {
        &self.model
    }
}

fn main() {
    let sedan = Sedan {
        make: "Citroën".to_string(),
        model: "C3".to_string(),
    };

    let rental = Rental {
        car: Box::new(sedan),
        customer: "John Doe".to_string(),
        rental_days: 7,
    };

    let customer = Customer {
        name: "John Doe".to_string(),
        rentals: vec![rental],
    };

    println!("Customer {} rented a {} {} for {} days.",
             customer.name,
             customer.rentals[1].car.get_make(),
             customer.rentals[1].car.get_model(),
             customer.rentals[1].rental_days);
}
Enter fullscreen mode Exit fullscreen mode

Output:

$ cargo run
Customer Kurmanjan Datka rented a Citroën C3 for 7 days.
Enter fullscreen mode Exit fullscreen mode

In this Rust example, we define a Car trait that any car type must implement. We then create a Rental struct that contains a boxed Car trait object, allowing for any type that implements Car to be rented. This demonstrates composition by combining different traits and structs to create a more complex system.

Python Example

In Python, we can achieve a similar effect using classes and inheritance. Python's dynamic typing makes it easy to compose objects from different classes.

from typing import List

# Here we declare classes to use them as types
# later to make our code more readable
class Customer:
    pass

class Rental:
    pass

class Car:
    def __init__(self, make: str, model: str):
        self.make = make
        self.model = model

    def get_make(self) -> str:
        return self.make

    def get_model(self) -> str:
        return self.model


class Rental:
    def __init__(self, car: Car, customer: Customer, rental_days: int):
        self.car = car
        self.customer = customer
        self.rental_days = rental_days


class Customer:
    def __init__(self, name: str):
        self.name = name
        self.rentals: List[Rental] = []

    def add_rental(self, rental: Rental):
        self.rentals.append(rental)


class Sedan(Car):
    pass


sedan = Sedan("Citroën", "C3")
customer = Customer("Rita Levi-Montalcini")
rental = Rental(sedan, customer, 7)
customer.add_rental(rental)

print(
    f"Customer {customer.name} rented a {customer.rentals[0].car.get_make()} {
        customer.rentals[0].car.get_model()} for {customer.rentals[0].rental_days} days."
)
Enter fullscreen mode Exit fullscreen mode

Output:

$ python main.py 
Customer Rita Levi-Montalcini rented a Citroën C3 for 7 days.
Enter fullscreen mode Exit fullscreen mode

In the Python example, we define a Car class with methods to get the make and model. We then create a Rental class that takes a Car instance, a customer name, and the number of rental days. The Customer class contains a list of rentals. By using inheritance (e.g., Sedan inherits from Car), we can easily compose different types of cars and rentals.

Both examples demonstrate how composition can be used to build complex systems by combining simpler, reusable components.

So what are the differences?

The difference in composition between Rust and Python lies in how they organize and utilize objects and data structures, as well as in memory management and type mechanisms. While Python supports inheritance, allowing classes to inherit from other classes, Rust does not support inheritance in the same way. Instead, Rust encourages composition through traits and struct composition. This leads to a more modular and flexible design, where components can be combined in various ways to achieve the desired functionality. Python's inheritance model can lead to more tightly coupled designs, whereas Rust's composition model promotes more decoupled and reusable components. Both options have their pros and cons depending on where you use them.
In summary, while both languages support principles of composition, they do so differently, taking into account their unique features in type typing, memory management, and software architecture.

These articles may be useful

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
💖 💪 🙅 🚩
antonov_mike
Antonov Mike

Posted on May 12, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related