Composition in Rust and Python

RMAG news

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);
}

Output:

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

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(
fCustomer {customer.name} rented a {customer.rentals[0].car.get_make()} {
customer.rentals[0].car.get_model()} for {customer.rentals[0].rental_days} days.
)

Output:

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

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

Writing Python like it’s Rust

Inheritance and Composition: A Python OOP Guide
[Python App: Rental Car Cost Estimator
](https://www.youtube.com/watch?v=LcsEf9IdATA)

design-patterns/strategy

Rust takes the composition over inheritance approach
Design patterns strategy

Image created by Bing and edited by me

Other articles about the similarities and differences between Rust and Python

Python Classes vs. Rust Traits
Python classes vs Rust structures
Polymorphism in Rust and Python
Abstraction in Rust and Python
Encapsulation in Rust and Python
Composition in Rust and Python