Abstraction in Rust and Python. Simple examples

Antonov Mike - Feb 27 - - Dev Community

Disclaimer

The article contains code samples, but not theory and does not pretend to explain the theoretical basis of polymorphism in depth, nor does it explain why and when polymorphism should be used.
The article demonstrates how to implement abstraction in Rust and Python, but does not explain practical applications of polymorphism in real projects.

Abstraction

Abstraction is a fundamental concept in both Rust and Python, allowing for the encapsulation of complex implementations behind simpler interfaces. This makes code more modular, easier to maintain, and helps in reducing the complexity of the system. Let's explore how abstraction works in both languages with a simple example.

In Rust

we can use traits to define a contract for what a class should do without specifying how it does it. This is a form of abstraction. Here's a basic example:

// Define a trait named `Printer`
trait Printer {
    // Declare a method `print_data` that takes a string slice and returns nothing
    fn print_data(&self, data: &str);
}

// Implement the `Printer` trait for a struct named `DataPrinter`
struct DataPrinter;

impl Printer for DataPrinter {
    fn print_data(&self, data: &str) {
        println!("Data: {}", data);
    }
}

fn main() {
    let printer = DataPrinter;
    printer.print_data("Hello, Rust!");
}
Enter fullscreen mode Exit fullscreen mode

In this Rust example, Printer is an abstraction that defines a print_data method. The DataPrinter struct implements this trait, providing the actual implementation of print_data. The main function doesn't need to know how print_data is implemented; it only needs to know that DataPrinter is a Printer. The print_data method in the Printer trait is declared but does nothing. This method is then implemented in the DataPrinter struct to print the data. This setup allows for the possibility of other structs implementing the Printer trait with their own implementations of print_data, or they could use the default (no-op) implementation if they don't need to print anything.

In Python

it's more about encapsulation and duck typing because Python is a dynamically typed language. Here's how you might achieve a similar effect to the Rust example:

class Printer:
    def print_data(self, data):
        raise NotImplementedError("Subclasses must implement this method")

class DataPrinter(Printer):
    def print_data(self, data):
        print(f"Data: {data}")

def main():
    printer = DataPrinter()
    printer.print_data("Hello, Python!")

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

In this Python example, Printer is an abstract base class (ABC) that defines a print_data method. The DataPrinter class inherits from Printer and provides an implementation for print_data. The main function uses a DataPrinter instance without needing to know the details of how print_data is implemented.

Summary

Both Rust and Python support abstraction, allowing you to hide the complexity of an implementation behind a simpler interface. Rust achieves this through traits, which are a way to define shared behavior across types, while Python uses abstract base classes (ABCs) and duck typing to achieve a similar effect. The key takeaway is that both languages allow you to define a contract for what a class should do without specifying how it does it, making your code more modular and easier to maintain.

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
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .