Successful Object-Oriented Programming (OOP) in Rust: Mastering Five Critical Concepts From Python
Obect-oriented programming, the definition of OOP remains a contentious topic, with numerous computer languages vying for acceptance.
Rust is not one of them.
I was dishearted when I read, from several prestigious sources, that Rust was not object-oriented.
Luckily, I found the object-oriented behaviors I used in Python; I could get almost all of them in Rust. The one I could not get, inheritance, I get close enough in Rust.
I will repeat it.
Strap in and prepare for my journey so far into Rust and object-oriented programming!
OOP in Python
Python is an object-oriented programming language, which means it supports a variety of object-oriented behaviors — five of which I detail for Python and then Rust.
Classes and objects: Python
Python allows you to define classes, which are user-defined types that can have attributes and methods. Objects are instances of classes that can hold specific values of their attributes.
Here is an example of an Object-Oriented Programming (OOP) “stubby” pattern for a 3D Vector in linear coordinates implemented in Python:
Note: The above “stubby” pattern, which is my irrelevant label, exists because a more complete solution of the class would require introducing the OOP overloading and polymorphism behaviors too early.
Python does not require explicit type annotations or operator traits, but I do it as a matter of discipline.
I use type hints to clarify the expected types of function arguments and return values.
Somebody will write a Python compiler that uses type annotations as strong typing.
Note: Seems that a group has taken a crack at creating a compiler for Python.
The is often ignored because of the method. The method is used to specify how the vector should be represented as a string when it is displayed without using
The output from evaluating the above Python statements is:
The class appears to have no attribute (method) ‘dot.’ I will fix that in the section Python: Overloading.
Inheritance: Python
Python supports inheritance, which allows you to define new classes that inherit attributes and methods from a parent class. You can create a hierarchy of classes with shared behavior.
But does inheritance benefit an aspect of the Python interpreter, or does it benefit the programmer?
Inheritance is a powerful feature of Python that is used to improve the modularity and reusability of your code.
You decide.
“Too much syntactic sugar can result in syntactic diabetes.” — Anonymous
I have never seen a hierarchy with over four tiers. Maybe you have.
Here’s an example of how you can define a child class that inherits from the parent class.
I can create objects like we are creating objects. The the class inherits all of the methods and attributes of the class.
It is easy to inherit in Python, but we need to override some of the inherited methods, such as and Also, there are some methods we should add, such as
The the class inherits from the class by specifying as the parent class in the class definition.
The method of takes , , and as arguments and computes the , , and coordinates of the vector in Cartesian coordinates using the standard conversion equations. It then calls the method of the parent class used to initialize the , , and coordinates of the vector.
The the method is overridden to return a string representation of the instance using its polar coordinates instead of its Cartesian coordinates.
The method is called a convenience method that allows you to create a instance directly from polar coordinates without having to convert them manually first.
The Python statements below evaluate as follows:
Again, It appears that the class has no attribute(method) . I will fix that in the next section Python: Overloading.
Overloading: Python
Python supports operator overloading, which allows you to define custom behavior for built-in operators like , , , and .
class.
Now that we have the method implemented, the instances should evaluate successfully.
Yeah!
Double yeah!
Polymorphism: Python
Polymorphism in Python allows you to use a standard interface for different classes, which makes it easier to swap or combine different implementations.
In the following example, and classes can demonstrate polymorphism by implementing a standard method, such as info(), which returns a string representation of the vector in their respective coordinate systems.
Note: IMHO, the above is a horrible abuse of Python’s that hobbles maintenance. Wat a reviewer has asked me to do, was to go find the file the class is in and add the method. Sigh! They are right. I can think of only one good reason to use
Encapsulation: Python
Encapsulation in Python means hiding the internal details of a class and exposing only the necessary information and functionalities to the users. In our example, the class can demonstrate encapsulation by using private attributes and exposing only the required methods to interact with the objects.
In the example below, the class demonstrates encapsulation of the coordinates (x, y, z) by hiding the internal details of the class and exposing only the necessary information and functionalities.
Note: I used setattr again! This is not a code review. This is a blog and this is less confusing to the reader. IMHO.
OOP in Rust
I am switching modes from (my attempt at a) Pythonista to (my attempt at a) Rustic.
Note: I seriously doubt that Rustic is the correct name of the Rust clan. Set me straight in the comment section.
I used and showed Rust code in a Jupyter Notebook. I did not use You can find out how to set up the Jupyter Notebook for Rust here:
Optimizing My Rust Development Environment
We were commanded by the powers that be for our team to be the “guinea pig” that migrates to Rust. My approach entailed…
Rust Structs (similar to Python Classes) and Rust Traits
Rust’s struct type is similar to a class in object-oriented programming as it contains data and methods.
That is not quite the full truth , but before I get into that, an equivalent Rust struct of the Python class is:
Let us evaluate the above code and run it.
Anybody that knows Rust should know that what follows is illegal in a Rust environment.
Using proper terminology is a new Type in Rust. Whereas Python is a new class.
Rust is a strongly-typed functional language, and we already see a big difference.
The keyword is used to define new types in Rust. In this case, the keyword is used to define a new type called . The type has three fields: , , and . The , , and fields are all of type .
The code defines type has new methods (, , ,, and ) using the keyword.
Besides , I need something called a trait.
An aside on Rust traits
The following is a trait we used for :
Like Python, the method is the trait for the struct (type) in Rust. The trait provides a way to format and display a type in a human-readable form.
When I first started coding the above code, it took me a long time to figure out I needed a module, not an existing type.
Yup, is not a type of Rust or any language I am aware of.
is a module that provides types and traits for formatting and displaying values in Rust.
Now I must figure out the difference between a module and a crate.
This may be why they’re different.
The module is part of the Rust standard library and provides functionality to format and display values in various ways.
The above code should have been a clue. I missed it.
The following is another trait we used for :
This Rust implementation enables division operation for references to structs, where the divisor is an f64 value. Rust has a built-in trait used for division operations between two values using the / operator.
The implementation begins with the keyword, followed by the type parameters.
The syntax indicates that we are implementing the trait for the type, which means that the divisor in the division operation will be an f64 value.
The line specifies that the output of the division operation will be a struct.
The implementation then defines the method, which refers to a struct and an value as arguments. A new struct is created inside the method, with each field (x, y, and z) being divided by the f64 scalar value.
Can you figure out why we need this trait?
Another aside on Rust’s traits because they are essential
In Rust, a trait is like a blueprint that defines a bunch of methods that any type can implement — whether it’s a struct, an enum, or even a primitive type like an integer or a float.
By grossly bending the OOP concept, I create Python classes using Rust structs and traits. With traits, I will later accomplish overloading and polymorphism.
Traits are a powerful way to add functionality to your Rust code. They’re like interfaces in other languages, but they’re more flexible. You can use them to write generic code that works with any type that implements the trait. You can implement a trait for any type, even if it’s not in the same crate as the trait.
Using traits, we can define shared functionality across different types without writing separate functions for each.
Additionally, traits can be used to define operator overloading. We already had a sneak peek at overloading in the code above. We saw how the Div<> trait was implemented to allow the division of a struct by a floating-point scalar value or a Again, I will give more in detail in the section below, Rust: Overloading.
Inheritance: Rust
Inheritance is not a first-class concept in Rust, unlike in some other object-oriented languages like Python and Java.
The #[derive] attribute is used to derive traits’ implementations for a struct automatically. In this case, the #[derive(Debug)] attribute is used to implement the Debug traits for the struct automatically.
The Debug trait provides methods for printing the value of a struct in a human-readable format.
Using the #[derive] attribute can make your code more concise and easier to read.
Is this a form of inheritance?
Is there inheritance behavior in the following code?
By inheriting using the resulting code becomes much more succinct:
The #[derive] attribute is a powerful tool that can help you write more concise and easier-to-read Rust code.
Inheritance and Traits in Rust
Traits are promised to be a powerful feature of Rust that can be used to improve the modularity and reusability of our code.
The same is true for Python inheritance.
For Rust, I can use traits and composition for OOP inheritance. I have to figure it out first.
Note: There are other ways, four of which are called “ deriving traits”, “composition”, “wrapping” , or “boxing” a type, which I will look at later, but not in this article.
Overloading: Rust
Rust supports operator overloading, which allows you to define custom behavior for built-in operators like , , , and . Rust requires you to define operator methods for any new vector, matrix, tensor, or n-D type.
One of the methods is wrong. Please find it and fix it.
Polymorphism: Rust
Polymorphism in Rust refers to the ability of a type to take on multiple forms. In Rust, polymorphism is achieved through the use of traits. A trait is a collection of methods that types can implement, allowing them to share common behavior.
Polymorphism in Rust can be static or dynamic.
Dynamic Polymorphism: Rust
Dynamic anything in Rust surprises me.
It turns out I was using dynamic polymorphism in the Overloading: Rust section above.
The and traits were used to define the and methods for the struct. These methods take two instances as arguments and return a new instance.
The and traits are generic traits. This means that they can be used with any type that implements them.
In the case of the struct, the and traits are implemented by the struct itself. The and methods are used with any two instances, regardless of their specific values.
Here is a small part of the type implementation:
Dynamic polymorphism is accomplished through the use of trait objects. Trait objects are created by specifying a trait as a type, allowing a variable to hold any value that implements that trait. This allows for more flexibility at runtime, as the exact type of the object can be determined dynamically.
Static polymorphism: Rust
Static polymorphism, also known as compile-time polymorphism, is achieved through generics. Generic types allow code to be written in a way that can handle multiple types, making the code more reusable.
The following code adds the second type and the operators Add, Sub, Mul, and Div.
The above output shows that the second type and the operators Add, Sub, Mul, and Div accomplish static polymorphism.
Encapsulation: Rust
Encapsulation is a crucial principle of object-oriented programming. It involves hiding the internal implementation details of a class or struct and exposing only a public interface for interacting with its data. This makes the code more modular and easier to maintain.
In the following code, the struct encapsulates its implementation details by making its data members (x, y, and z) private. This means that these members are not accessible from outside the struct. The only way to access them is through the public methods of the struct.
The function is used to create new instances of the struct and initialize its private data members. The function provides read-only access to the private data members of the struct by returning a tuple containing the x, y, and z values. The function provides a way to modify the private data members of the struct from the outside world, but only through the public interface provided by the struct.
Overall, by encapsulating the implementation details of the struct, the code reduces the complexity and improves the maintainability of the overall system. This is because other parts of the system can interact with the struct using its public interface without worrying about the underlying implementation details.
OOP in Rust: Review
Rust is a strongly-typed language. Strongly-typed languages are like the code reviewer I used to know. Code reviewers, good ones, check up on you and ensure you’re doing everything correctly. They might be annoying sometimes, but they always seek your best interests.
Strong-typed languages, i.e., Rust, can be more challenging to learn than weakly-typed languages, i.e., Python, but Rust is much faster. However, the syntax, using:;{}[]*/\ and some others, is like programming in regex, only with more rules. I need three hands!
Rust is a multi-paradigm programming language that supports procedural, functional programming, and, with some imagination, actual object-oriented programming (OOP) styles. While Rust does not have all the traditional OOP features that some other languages have, it offers several common behaviors in OOP.
Several key object-oriented behaviors in Rust allow you to define and work with custom types similar to other object-oriented languages.
You can define structs in Rust, similar to classes in other object-oriented languages. Structs can have data members and methods, just like classes.
Rust has a feature called traits, which defines a set of methods that can be implemented by any type that wants to provide that behavior. This is similar to interfaces in other object-oriented languages, where you define a contract that a type must follow to provide specific behavior.
Rust allows you to define implementations, which define how a particular struct or type implements a trait. This is similar to defining methods for a class in other object-oriented languages.
Rust supports generics, which allow you to define a type or function that can work with different types. This is similar to template classes and functions in C++, where you can define a general implementation that can be used with different types.
Rust supports dynamic method dispatch, which allows you to call a method on a struct or type through a trait object. This is similar to virtual function calls in C++, where you can call a method on an object without knowing its exact type at compile time.
These five key object-oriented behaviors in Rust provide a powerful and flexible way to define custom types and behavior, similar to other object-oriented languages.
I like Rust because:
It does not have a Python GIL. Rust can easily use modern chipsets with multiple cores.
It is more memory safe than C and C++.
It is 30+ times faster than Python.
Remember you get the OOP and all associated code by cloning the GitHub repo at https://github.com/bcottman/OOP/ .
Resources
The Rust Programming Language
The first step is to install Rust. We’ll download Rust through rustup, a command line tool for managing Rust versions…
Rust Design Patterns
If you are interested in contributing to this book, check out the contribution guidelines. In software development, we…
The Rust Programming Language
Object-oriented programming (OOP) is a way of modeling programs. Objects as a programmatic concept were introduced in…
Is Rust an Object-Oriented Programming Language?
Python-based compiler achieves orders-of-magnitude speedups
In 2018, the Economist published an in-depth piece on the programming language Python. “In the past 12 months,” the…
Optimizing My Rust Development Environment
We were commanded by the powers that be for our team to be the “guinea pig” that migrates to Rust. My approach entailed…
Code safely!