Introduction
First, let’s recall the main principles of OOP:
- Inheritance
- Encapsulation
- Polymorphism
While there are no issues with encapsulation in Rust, developers who have worked with OOP languages like Python might face difficulties implementing the remaining principles. This is especially relevant for those who decided to rewrite a project from a language like Python/Kotlin to Rust.
Inheritance
What is inheritance? Inheritance is a mechanism that allows extending and overriding the functionality of a type. Let’s look at each idea separately.
Extending Functionality
Let’s consider an example in Python.
<span>class</span> <span>Employee</span><span>:</span><span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>None</span><span>:</span><span>self</span><span>.</span><span>name</span> <span>=</span> <span>name</span><span>def</span> <span>say_name</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span><span>print</span><span>(</span><span>self</span><span>.</span><span>name</span><span>)</span><span>class</span> <span>Programmer</span><span>(</span><span>Employee</span><span>):</span><span>pass</span><span>if</span> <span>__name__</span> <span>==</span> <span>"</span><span>__main__</span><span>"</span><span>:</span><span>programmer</span> <span>=</span> <span>Programmer</span><span>(</span><span>"</span><span>Jack</span><span>"</span><span>)</span><span>programmer</span><span>.</span><span>say_name</span><span>()</span><span>class</span> <span>Employee</span><span>:</span> <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>None</span><span>:</span> <span>self</span><span>.</span><span>name</span> <span>=</span> <span>name</span> <span>def</span> <span>say_name</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span> <span>print</span><span>(</span><span>self</span><span>.</span><span>name</span><span>)</span> <span>class</span> <span>Programmer</span><span>(</span><span>Employee</span><span>):</span> <span>pass</span> <span>if</span> <span>__name__</span> <span>==</span> <span>"</span><span>__main__</span><span>"</span><span>:</span> <span>programmer</span> <span>=</span> <span>Programmer</span><span>(</span><span>"</span><span>Jack</span><span>"</span><span>)</span> <span>programmer</span><span>.</span><span>say_name</span><span>()</span>class Employee: def __init__(self, name: str) -> None: self.name = name def say_name(self) -> None: print(self.name) class Programmer(Employee): pass if __name__ == "__main__": programmer = Programmer("Jack") programmer.say_name()
Enter fullscreen mode Exit fullscreen mode
Extension means that the child class will have all the functionality of the parent. However, this is easily addressed with composition. How would it look in Rust:
<span>struct</span> <span>Employee</span> <span>{</span><span>name</span><span>:</span> <span>String</span><span>,</span><span>}</span><span>impl</span> <span>Employee</span> <span>{</span><span>fn</span> <span>say_name</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span><span>println!</span><span>(</span><span>"{}"</span><span>,</span> <span>self</span><span>.name</span><span>);</span><span>}</span><span>}</span><span>struct</span> <span>Programmer</span> <span>{</span><span>employee</span><span>:</span> <span>Employee</span><span>,</span><span>}</span><span>impl</span> <span>Programmer</span> <span>{</span><span>fn</span> <span>say_name</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span><span>self</span><span>.employee</span><span>.say_name</span><span>();</span><span>}</span><span>}</span><span>fn</span> <span>main</span><span>()</span> <span>{</span><span>let</span> <span>programmer</span> <span>=</span> <span>Programmer</span> <span>{</span><span>employee</span><span>:</span> <span>Employee</span> <span>{</span><span>name</span><span>:</span> <span>"Jack"</span><span>.to_string</span><span>(),</span><span>},</span><span>};</span><span>programmer</span><span>.say_name</span><span>();</span><span>}</span><span>struct</span> <span>Employee</span> <span>{</span> <span>name</span><span>:</span> <span>String</span><span>,</span> <span>}</span> <span>impl</span> <span>Employee</span> <span>{</span> <span>fn</span> <span>say_name</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span> <span>println!</span><span>(</span><span>"{}"</span><span>,</span> <span>self</span><span>.name</span><span>);</span> <span>}</span> <span>}</span> <span>struct</span> <span>Programmer</span> <span>{</span> <span>employee</span><span>:</span> <span>Employee</span><span>,</span> <span>}</span> <span>impl</span> <span>Programmer</span> <span>{</span> <span>fn</span> <span>say_name</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span> <span>self</span><span>.employee</span><span>.say_name</span><span>();</span> <span>}</span> <span>}</span> <span>fn</span> <span>main</span><span>()</span> <span>{</span> <span>let</span> <span>programmer</span> <span>=</span> <span>Programmer</span> <span>{</span> <span>employee</span><span>:</span> <span>Employee</span> <span>{</span> <span>name</span><span>:</span> <span>"Jack"</span><span>.to_string</span><span>(),</span> <span>},</span> <span>};</span> <span>programmer</span><span>.say_name</span><span>();</span> <span>}</span>struct Employee { name: String, } impl Employee { fn say_name(&self) { println!("{}", self.name); } } struct Programmer { employee: Employee, } impl Programmer { fn say_name(&self) { self.employee.say_name(); } } fn main() { let programmer = Programmer { employee: Employee { name: "Jack".to_string(), }, }; programmer.say_name(); }
Enter fullscreen mode Exit fullscreen mode
It may seem that there is some code duplication here. However, the key advantage is that the child object doesn’t need to inherit all the functionality from the parent, thus avoiding the creation of superclasses. Moreover, Rust has crates that minimize code duplication.
Overriding Functionality
Let’s look at another example in Python
<span>from</span> <span>abc</span> <span>import</span> <span>ABC</span><span>,</span> <span>abstractmethod</span><span>class</span> <span>Employee</span><span>(</span><span>ABC</span><span>):</span><span>@abstractmethod</span><span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span><span>pass</span><span>class</span> <span>Programmer</span><span>(</span><span>Employee</span><span>):</span><span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span><span>print</span><span>(</span><span>"</span><span>*coding...*</span><span>"</span><span>)</span><span>class</span> <span>ProductManager</span><span>(</span><span>Employee</span><span>):</span><span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span><span>print</span><span>(</span><span>"</span><span>*doing nothing*</span><span>"</span><span>)</span><span>if</span> <span>__name__</span> <span>==</span> <span>"</span><span>__main__</span><span>"</span><span>:</span><span>programmer</span> <span>=</span> <span>Programmer</span><span>()</span><span>product_manager</span> <span>=</span> <span>ProductManager</span><span>()</span><span>programmer</span><span>.</span><span>work</span><span>()</span><span>product_manager</span><span>.</span><span>work</span><span>()</span><span>from</span> <span>abc</span> <span>import</span> <span>ABC</span><span>,</span> <span>abstractmethod</span> <span>class</span> <span>Employee</span><span>(</span><span>ABC</span><span>):</span> <span>@abstractmethod</span> <span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span> <span>pass</span> <span>class</span> <span>Programmer</span><span>(</span><span>Employee</span><span>):</span> <span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span> <span>print</span><span>(</span><span>"</span><span>*coding...*</span><span>"</span><span>)</span> <span>class</span> <span>ProductManager</span><span>(</span><span>Employee</span><span>):</span> <span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span> <span>print</span><span>(</span><span>"</span><span>*doing nothing*</span><span>"</span><span>)</span> <span>if</span> <span>__name__</span> <span>==</span> <span>"</span><span>__main__</span><span>"</span><span>:</span> <span>programmer</span> <span>=</span> <span>Programmer</span><span>()</span> <span>product_manager</span> <span>=</span> <span>ProductManager</span><span>()</span> <span>programmer</span><span>.</span><span>work</span><span>()</span> <span>product_manager</span><span>.</span><span>work</span><span>()</span>from abc import ABC, abstractmethod class Employee(ABC): @abstractmethod def work(self) -> None: pass class Programmer(Employee): def work(self) -> None: print("*coding...*") class ProductManager(Employee): def work(self) -> None: print("*doing nothing*") if __name__ == "__main__": programmer = Programmer() product_manager = ProductManager() programmer.work() product_manager.work()
Enter fullscreen mode Exit fullscreen mode
I intentionally made the Employee class abstract. This is one of the interesting features in Python, allowing you to create something akin to interfaces for child classes.
In Rust, this is solved more elegantly with traits. Let’s see how it would look in Rust:
<span>struct</span> <span>Programmer</span> <span>{}</span><span>struct</span> <span>ProductManager</span> <span>{}</span><span>trait</span> <span>Employee</span> <span>{</span><span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>);</span><span>}</span><span>impl</span> <span>Employee</span> <span>for</span> <span>Programmer</span> <span>{</span><span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span><span>println!</span><span>(</span><span>"*coding...*"</span><span>);</span><span>}</span><span>}</span><span>impl</span> <span>Employee</span> <span>for</span> <span>ProductManager</span> <span>{</span><span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span><span>println!</span><span>(</span><span>"*doing nothing*"</span><span>);</span><span>}</span><span>}</span><span>fn</span> <span>main</span><span>()</span> <span>{</span><span>let</span> <span>programmer</span> <span>=</span> <span>Programmer</span> <span>{};</span><span>let</span> <span>product_manager</span> <span>=</span> <span>ProductManager</span> <span>{};</span><span>programmer</span><span>.work</span><span>();</span><span>product_manager</span><span>.work</span><span>();</span><span>}</span><span>struct</span> <span>Programmer</span> <span>{}</span> <span>struct</span> <span>ProductManager</span> <span>{}</span> <span>trait</span> <span>Employee</span> <span>{</span> <span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>);</span> <span>}</span> <span>impl</span> <span>Employee</span> <span>for</span> <span>Programmer</span> <span>{</span> <span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span> <span>println!</span><span>(</span><span>"*coding...*"</span><span>);</span> <span>}</span> <span>}</span> <span>impl</span> <span>Employee</span> <span>for</span> <span>ProductManager</span> <span>{</span> <span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span> <span>println!</span><span>(</span><span>"*doing nothing*"</span><span>);</span> <span>}</span> <span>}</span> <span>fn</span> <span>main</span><span>()</span> <span>{</span> <span>let</span> <span>programmer</span> <span>=</span> <span>Programmer</span> <span>{};</span> <span>let</span> <span>product_manager</span> <span>=</span> <span>ProductManager</span> <span>{};</span> <span>programmer</span><span>.work</span><span>();</span> <span>product_manager</span><span>.work</span><span>();</span> <span>}</span>struct Programmer {} struct ProductManager {} trait Employee { fn work(&self); } impl Employee for Programmer { fn work(&self) { println!("*coding...*"); } } impl Employee for ProductManager { fn work(&self) { println!("*doing nothing*"); } } fn main() { let programmer = Programmer {}; let product_manager = ProductManager {}; programmer.work(); product_manager.work(); }
Enter fullscreen mode Exit fullscreen mode
Additionally, traits can have default implementations for functions to avoid code duplication if the same behavior is needed for multiple structures.
Defining functionality through interfaces is also more elegant because there’s no need to inherit all the functionality again. You can assign functionality “pointwise” to different structures, and a single structure can implement multiple different traits.
Polymorphism
Let’s first define what polymorphism is. Polymorphism is a mechanism where a function or structure can work with different data types that implement a common interface.
In Python, this is done quite easily, just specify the base class as the type of the object (although given that you can pass anything as an argument, you might not even need to do this ):
<span>from</span> <span>abc</span> <span>import</span> <span>ABC</span><span>,</span> <span>abstractmethod</span><span>class</span> <span>Employee</span><span>(</span><span>ABC</span><span>):</span><span>@abstractmethod</span><span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span><span>pass</span><span>class</span> <span>Programmer</span><span>(</span><span>Employee</span><span>):</span><span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span><span>print</span><span>(</span><span>"</span><span>*coding...*</span><span>"</span><span>)</span><span>class</span> <span>ProductManager</span><span>(</span><span>Employee</span><span>):</span><span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span><span>print</span><span>(</span><span>"</span><span>*doing nothing*</span><span>"</span><span>)</span><span>def</span> <span>make_work</span><span>(</span><span>worker</span><span>:</span> <span>Employee</span><span>):</span><span>worker</span><span>.</span><span>work</span><span>()</span><span>if</span> <span>__name__</span> <span>==</span> <span>"</span><span>__main__</span><span>"</span><span>:</span><span>programmer</span> <span>=</span> <span>Programmer</span><span>()</span><span>product_manager</span> <span>=</span> <span>ProductManager</span><span>()</span><span>make_work</span><span>(</span><span>programmer</span><span>)</span><span>make_work</span><span>(</span><span>product_manager</span><span>)</span><span>from</span> <span>abc</span> <span>import</span> <span>ABC</span><span>,</span> <span>abstractmethod</span> <span>class</span> <span>Employee</span><span>(</span><span>ABC</span><span>):</span> <span>@abstractmethod</span> <span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span> <span>pass</span> <span>class</span> <span>Programmer</span><span>(</span><span>Employee</span><span>):</span> <span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span> <span>print</span><span>(</span><span>"</span><span>*coding...*</span><span>"</span><span>)</span> <span>class</span> <span>ProductManager</span><span>(</span><span>Employee</span><span>):</span> <span>def</span> <span>work</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>None</span><span>:</span> <span>print</span><span>(</span><span>"</span><span>*doing nothing*</span><span>"</span><span>)</span> <span>def</span> <span>make_work</span><span>(</span><span>worker</span><span>:</span> <span>Employee</span><span>):</span> <span>worker</span><span>.</span><span>work</span><span>()</span> <span>if</span> <span>__name__</span> <span>==</span> <span>"</span><span>__main__</span><span>"</span><span>:</span> <span>programmer</span> <span>=</span> <span>Programmer</span><span>()</span> <span>product_manager</span> <span>=</span> <span>ProductManager</span><span>()</span> <span>make_work</span><span>(</span><span>programmer</span><span>)</span> <span>make_work</span><span>(</span><span>product_manager</span><span>)</span>from abc import ABC, abstractmethod class Employee(ABC): @abstractmethod def work(self) -> None: pass class Programmer(Employee): def work(self) -> None: print("*coding...*") class ProductManager(Employee): def work(self) -> None: print("*doing nothing*") def make_work(worker: Employee): worker.work() if __name__ == "__main__": programmer = Programmer() product_manager = ProductManager() make_work(programmer) make_work(product_manager)
Enter fullscreen mode Exit fullscreen mode
In Rust, there are several ways to achieve this. I will talk about the two most commonly used methods.
For virtual calls in Rust, there is a special mechanism using the dyn
keyword. How would this code look in Rust:
<span>struct</span> <span>Programmer</span> <span>{}</span><span>struct</span> <span>ProductManager</span> <span>{}</span><span>trait</span> <span>Employee</span> <span>{</span><span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>);</span><span>}</span><span>impl</span> <span>Employee</span> <span>for</span> <span>Programmer</span> <span>{</span><span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span><span>println!</span><span>(</span><span>"*coding...*"</span><span>);</span><span>}</span><span>}</span><span>impl</span> <span>Employee</span> <span>for</span> <span>ProductManager</span> <span>{</span><span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span><span>println!</span><span>(</span><span>"*doing nothing*"</span><span>);</span><span>}</span><span>}</span><span>fn</span> <span>make_work</span><span>(</span><span>worker</span><span>:</span> <span>Box</span><span><</span><span>dyn</span> <span>Employee</span><span>></span><span>)</span> <span>{</span><span>worker</span><span>.work</span><span>();</span><span>}</span><span>fn</span> <span>main</span><span>()</span> <span>{</span><span>let</span> <span>programmer</span> <span>=</span> <span>Programmer</span> <span>{};</span><span>let</span> <span>product_manager</span> <span>=</span> <span>ProductManager</span> <span>{};</span><span>make_work</span><span>(</span><span>Box</span><span>::</span><span>new</span><span>(</span><span>programmer</span><span>));</span><span>make_work</span><span>(</span><span>Box</span><span>::</span><span>new</span><span>(</span><span>product_manager</span><span>));</span><span>}</span><span>struct</span> <span>Programmer</span> <span>{}</span> <span>struct</span> <span>ProductManager</span> <span>{}</span> <span>trait</span> <span>Employee</span> <span>{</span> <span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>);</span> <span>}</span> <span>impl</span> <span>Employee</span> <span>for</span> <span>Programmer</span> <span>{</span> <span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span> <span>println!</span><span>(</span><span>"*coding...*"</span><span>);</span> <span>}</span> <span>}</span> <span>impl</span> <span>Employee</span> <span>for</span> <span>ProductManager</span> <span>{</span> <span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span> <span>println!</span><span>(</span><span>"*doing nothing*"</span><span>);</span> <span>}</span> <span>}</span> <span>fn</span> <span>make_work</span><span>(</span><span>worker</span><span>:</span> <span>Box</span><span><</span><span>dyn</span> <span>Employee</span><span>></span><span>)</span> <span>{</span> <span>worker</span><span>.work</span><span>();</span> <span>}</span> <span>fn</span> <span>main</span><span>()</span> <span>{</span> <span>let</span> <span>programmer</span> <span>=</span> <span>Programmer</span> <span>{};</span> <span>let</span> <span>product_manager</span> <span>=</span> <span>ProductManager</span> <span>{};</span> <span>make_work</span><span>(</span><span>Box</span><span>::</span><span>new</span><span>(</span><span>programmer</span><span>));</span> <span>make_work</span><span>(</span><span>Box</span><span>::</span><span>new</span><span>(</span><span>product_manager</span><span>));</span> <span>}</span>struct Programmer {} struct ProductManager {} trait Employee { fn work(&self); } impl Employee for Programmer { fn work(&self) { println!("*coding...*"); } } impl Employee for ProductManager { fn work(&self) { println!("*doing nothing*"); } } fn make_work(worker: Box<dyn Employee>) { worker.work(); } fn main() { let programmer = Programmer {}; let product_manager = ProductManager {}; make_work(Box::new(programmer)); make_work(Box::new(product_manager)); }
Enter fullscreen mode Exit fullscreen mode
But this is not the only way. By avoiding pointer indirection, we can achieve better performance. To avoid this, we can use enum
.
Performance is higher in this case as it uses simple pattern matching instead of pointer indirection. Let’s look at an example:
<span>enum</span> <span>Employee</span> <span>{</span><span>Programmer</span><span>,</span><span>ProductManager</span><span>,</span><span>}</span><span>impl</span> <span>Employee</span> <span>{</span><span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span><span>match</span> <span>*</span><span>self</span> <span>{</span><span>Employee</span><span>::</span><span>Programmer</span> <span>=></span> <span>println!</span><span>(</span><span>"*coding...*"</span><span>),</span><span>Employee</span><span>::</span><span>ProductManager</span> <span>=></span> <span>println!</span><span>(</span><span>"*doing nothing*"</span><span>),</span><span>}</span><span>}</span><span>}</span><span>fn</span> <span>make_work</span><span>(</span><span>worker</span><span>:</span> <span>Employee</span><span>)</span> <span>{</span><span>worker</span><span>.work</span><span>();</span><span>}</span><span>fn</span> <span>main</span><span>()</span> <span>{</span><span>let</span> <span>programmer</span> <span>=</span> <span>Employee</span><span>::</span><span>Programmer</span><span>;</span><span>let</span> <span>product_manager</span> <span>=</span> <span>Employee</span><span>::</span><span>ProductManager</span><span>;</span><span>make_work</span><span>(</span><span>programmer</span><span>);</span><span>make_work</span><span>(</span><span>product_manager</span><span>);</span><span>}</span><span>enum</span> <span>Employee</span> <span>{</span> <span>Programmer</span><span>,</span> <span>ProductManager</span><span>,</span> <span>}</span> <span>impl</span> <span>Employee</span> <span>{</span> <span>fn</span> <span>work</span><span>(</span><span>&</span><span>self</span><span>)</span> <span>{</span> <span>match</span> <span>*</span><span>self</span> <span>{</span> <span>Employee</span><span>::</span><span>Programmer</span> <span>=></span> <span>println!</span><span>(</span><span>"*coding...*"</span><span>),</span> <span>Employee</span><span>::</span><span>ProductManager</span> <span>=></span> <span>println!</span><span>(</span><span>"*doing nothing*"</span><span>),</span> <span>}</span> <span>}</span> <span>}</span> <span>fn</span> <span>make_work</span><span>(</span><span>worker</span><span>:</span> <span>Employee</span><span>)</span> <span>{</span> <span>worker</span><span>.work</span><span>();</span> <span>}</span> <span>fn</span> <span>main</span><span>()</span> <span>{</span> <span>let</span> <span>programmer</span> <span>=</span> <span>Employee</span><span>::</span><span>Programmer</span><span>;</span> <span>let</span> <span>product_manager</span> <span>=</span> <span>Employee</span><span>::</span><span>ProductManager</span><span>;</span> <span>make_work</span><span>(</span><span>programmer</span><span>);</span> <span>make_work</span><span>(</span><span>product_manager</span><span>);</span> <span>}</span>enum Employee { Programmer, ProductManager, } impl Employee { fn work(&self) { match *self { Employee::Programmer => println!("*coding...*"), Employee::ProductManager => println!("*doing nothing*"), } } } fn make_work(worker: Employee) { worker.work(); } fn main() { let programmer = Employee::Programmer; let product_manager = Employee::ProductManager; make_work(programmer); make_work(product_manager); }
Enter fullscreen mode Exit fullscreen mode
Conclusion
Rust has all the necessary tools to rewrite an old project that was written using OOP principles. Moreover, if you use traits correctly, the result will be much more elegant. Additionally, inheritance in OOP can be a bad pattern as child classes often inherit attributes and methods that they don’t use.
暂无评论内容