Fundamentals 12 min read

How Python’s ABCs Translate to Rust Traits: A Side‑by‑Side Guide

This article compares Python’s abstract base classes with Rust’s traits, showing how familiar Python concepts map to Rust syntax through detailed code examples, covering method binding, trait implementation, operator overloading, and testing, helping developers transition between the two languages.

Architecture Development Notes
Architecture Development Notes
Architecture Development Notes
How Python’s ABCs Translate to Rust Traits: A Side‑by‑Side Guide

In life and programming, there are similarities that become apparent when we pay special attention. Rust and Python are now chasing each other; if you know one, learning the other becomes easier. Python developers entering Rust often get confused by Rust’s traits, their implementation and usage. This article aims to resolve that confusion.

If you are reading this, you are likely learning Rust. The explanation focuses on comparing the two languages and how to achieve the same functionality with different syntax. The syntax itself is not detailed; better resources exist. For comparison and learning, the article provides Python and Rust code examples.

As the saying goes, the best way to learn a human language is through conversation; the best way to learn a programming language is through typing.

Python’s ABC and Rust’s Trait

In Python, a class with the self keyword binds a group of functions as methods of the class, allowing instances to use those methods and forming the API.

Rust uses trait and the impl keyword to bind functions as methods of structs or enums. A trait is an object that contains method definitions and can be seen as a Python ABC class without an __init__() method or decorator.

Traits can be compared to ABC classes: when a class inherits an ABC, the new class must implement all methods. Similarly, Rust’s derive keyword lets an object inherit methods of specific traits.

Python Example

<code># Abstract base class (similar to Rust trait)
class Animal(ABC):
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    @abstractmethod
    def make_sound(self) -> str:
        pass

    def describe(self) -> str:
        return f"This is an animal that makes the sound: {self.make_sound()}"

# Concrete class inheriting the abstract base class also gets the "describe" method
class Dog(Animal):
    def __init__(self, name: str, age: int):
        super().__init__(name, age)

    def make_sound(self) -> str:
        return f"Woof! My name is {self.name}"</code>

Rust Example

<code>// Basic trait (similar to Python ABC)
trait Animal {
    fn make_sound(&self) -> String;
    fn describe(&self) -> String {
        format!(
            "This is an animal that makes the sound: {}",
            self.make_sound()
        )
    }
}

// Struct definition using the "derive" keyword
#[derive(Debug, Clone)] // similar to Python's __repr__ and clone functionality
struct Dog {
    name: String,
    age: u8,
}

// Implementation of the Animal trait for Dog (similar to inheritance)
impl Animal for Dog {
    fn make_sound(&self) -> String {
        format!("Woof! My name is {}", self.name)
    }
}</code>

Through the above examples, you should understand how to bind functions as methods of Rust objects. In Rust, programmers can define two main object types: structs and enums.

Ways to bind functions as object methods in Rust:

impl struct_name

Use #[derive(Trait-name)] in the struct definition

impl Trait-name for struct_name

The following code snippet shows all the above method‑binding options.

<code>#[derive(Debug, Clone)]
struct Dog {
    name: String,
    age: u8,
}

// Dog-specific method implementation block (similar to Python __init__)
impl Dog {
    // Constructor (similar to Python __init__)
    fn new(name: String, age: u8) -> Self {
        Dog { name, age }
    }
}

// Display trait implementation (similar to Python __str__)
impl fmt::Display for Dog {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Dog {} is {} years old", self.name, self.age)
    }
}

// Animal trait implementation
impl Animal for Dog {
    fn make_sound(&self) -> String {
        format!("Woof! My name is {}", self.name)
    }
}</code>

Additional Trait Features

In Rust, traits can serve as constraints for parameters or return values, ensuring that passed values satisfy the required behavior. The compiler checks these constraints to guarantee correct types.

Python Dunder Methods and Rust Standard Trait Overloading

In Python, everything is an object with capabilities granted by attached methods. The same analogy applies to Rust structs/enums: implementing a function, a trait, or deriving a trait gives the type special abilities.

Some methods are not called by their names but via operator overloading.

Python Example

The __add__ method is overloaded by the + operator, allowing custom behavior when + is used with instances.

Rust Example

You can overload the + operator by implementing the Add trait from the std::ops module.

<code>struct Dog {
    name: String,
    age: u8,
}

// PartialEq trait implementation (similar to Python __eq__)
impl PartialEq for Dog {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name && self.age == other.age
    }
}

// Add trait implementation (similar to Python __add__)
impl std::ops::Add for Dog {
    type Output = u8;
    fn add(self, other: Self) -> Self::Output {
        self.age + other.age
    }
}</code>

Python Example

<code>class Dog:
    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, type(self)):
            return NotImplemented
        return self.name == other.name and self.age == other.age

    def __add__(self, other: "Dog") -> int:
        if not isinstance(other, type(self)):
            return NotImplemented
        return self.age + other.age</code>

By now you should see that Rust is not a mere copy of Python; every language borrows concepts from others.

Python and Rust Testing

Both languages support writing tests for functions and methods. In Rust, assert is a macro; tests can reside in the same file or be split into separate modules.

Rust Example

<code>#[derive(Debug, Clone)]
struct Dog {
    name: String,
    age: u8,
}

impl fmt::Display for Dog {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Dog {} is {} years old", self.name, self.age)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_display_trait() {
        let dog = Dog::new("Buddy".to_owned(), 3);
        assert_eq!(dog.to_string(), "Dog Buddy is 3 years old");
    }
}</code>

Python Example

<code>class Dog(Animal):
    def make_sound(self) -> str:
        return f"Woof! My name is {self.name}"

# Functions must start with test_ to be discovered by the test runner
def test_animal_makesound():
    dog = Dog("Buddy", 3)
    cat = Cat("Whiskers", 4)
    assert dog.make_sound() == "Woof! My name is Buddy"
    assert cat.make_sound() == "Meow! My name is Whiskers"
</code>

Conclusion

The methods shown in Rust can be implemented with equivalent functionality in Python. Rust’s memory‑safety guarantees and type constraints make it easier to understand function inputs and outputs.

Practicing the same code in different languages is an excellent way to deepen your understanding of each language’s mechanics.

This article hopes to inspire you to explore multiple languages, experiment, and solve challenges across them.

PythonRustprogramming languagesComparisonABCTraits
Architecture Development Notes
Written by

Architecture Development Notes

Focused on architecture design, technology trend analysis, and practical development experience sharing.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.