How to quickly customize a collection type? A programmer familiar with some object-oriented languages might write it like this.
Inherit some built-in type (if it exists) and extend on top of that built-in type. But for programmers familiar with Python, this is not a good idea.
Types of ducks
When a bird is seen walking like a duck, swimming like a duck, and quacking like a duck, then the bird can be called a duck.
A simple example.
See, this custom
Collection class doesn’t inherit from Python’s built-in
Iterable type, but behaves as if it were some built-in iterable type.
These double underscore methods are generally called magic methods, and are not supposed to be called by the user; they are treated by the interpreter as a “protocol” that allows your custom class to have features like indexing, slicing, etc., regardless of whether it inherits from a standard library type, as long as it implements the corresponding protocol .
Python doesn’t care about whether our custom
Collection type is a subclass of a collection type, but whether it has the “ability” to be a collection.
The following example shows that
Python doesn’t even require that the interface be implemented when defining the class.
By dynamically registering the
__iter__ method of the
Statement class directly at runtime (this is called a monkey patch by the community and I personally don’t like it in real projects), you can call the
iter function on this custom type and even
isinstance(s, Iterable) returns a
I gave an example in a sharing session if there is such an inheritance chain in the system.
If a new flying object needs to be introduced in this system, but it represents a seagull, how to do it? Inherit the existing aircraft base class directly? If we do that we will get a steel seagull with fuel quantity information, a unique new species. Or we could extract another common abstract base class that has only the parts common to aircraft and seagulls.
But if we think in terms of duck types and add a new type, why must we be sure that it is a subclass of some type? Whether it’s a helicopter or a seagull, all they need in common is the ability to fly.
Django is one of the popular web frameworks in Python, and views can be defined in
Django like this.
Instead of defining a comprehensive parent class,
Django defines multiple
Mixin classes, which are usually translated into Chinese as mixin classes, each of which is responsible for a part of the functionality, for example
JSONResponseMixin is responsible for
JSON responses, which is somewhat similar to
interface (interface). The difference is that
CSharp doesn’t support multiple inheritance, but allows multiple interfaces, while
Mixin in Python is just a default convention that everyone follows, and can be written this way because Python supports multiple inheritance, and a child class can inherit from multiple parents. The
Mixin should not affect the functionality of the subclass itself; it should abstract a generic function for extending the subclass, which itself cannot normally be instantiated.
Such code formally inherits from multiple parent classes, but in practical terms it is more like a combination of functionality from different mixin classes. For example, the above code combines the functionality of a
JSON response with a template response, returning different types of responses depending on the request. Mixing duck calls, shapes, and flight patterns gives you a custom “duck”, depending on what functionality you need.
Object-Orientation in ## Rust
Now it’s the turn of Rust, a programming language that supports multiple paradigms, including the object-oriented paradigm. But first, what exactly is object-oriented? To borrow from the official tutorial The Book: If you follow the GOF description of object-oriented Patterns), object-oriented programs consist of objects that package together data and the processes that manipulate it, then
Rust undoubtedly supports object orientation, with
Rust organizing data by
struct and binding methods to them via
However, some programmers may object to this statement, as some people believe that only forms with encapsulation, inheritance, and polymorphism count as object-oriented, and Rust doesn’t even have
class, just as some people believe that JS and Python don’t quite count as object-oriented languages either.
Encapsulation, Inheritance, Polymorphism
These three words are really deep, and chances are that every software engineer has heard of them, so here’s a discussion of these three features in Rust.
The main role of encapsulation, in my opinion, is to isolate different levels of abstraction, where the bottom developer is responsible for the implementation details, and the developer at the top is only concerned with the exposed interface. For example, for
Python, we know that it has an interface that lets us get the number of elements inside it without having to go into the internal implementation details, which are the responsibility of the standard library developers. If we encapsulate a minimum stack on top of this object, we can get the minimum value in the list via the
min method, and we are responsible for encapsulating this interface, whether we maintain a separate stack to hold the minimum value or traverse the entire list when we call the interface is an internal detail that users of this type do not need to know.
Of course, for some languages, mechanisms are provided to force properties to be hidden from external callers, and Rust has the pub keyword to restrict accessibility .
foo field is not identified by the
pub keyword, it is a private field and cannot be accessed directly.
Next is inheritance, and there is no inheritance in Rust . It is not possible to implement a child structure that inherits from a parent structure. Inheritance has two main purposes, one is to reuse code, where the child class automatically gets the properties and methods of the parent class, but code reuse does not necessarily require inheritance; the other is for polymorphism, where a child type can be used where the parent type is needed.
This makes it seem a bit odd to compare the concepts of polymorphism and inheritance. Inheritance becomes a way to implement polymorphism, which is a bit broader.
Since Rust doesn’t have inheritance, how are the two functions inherited above (mainly the latter) going to be implemented in Rust? How would polymorphism be implemented?
Rust can abstract shared behavior through
trait. Take the example of an airplane, where all kinds of airplanes, and seagulls, can fly, but the specific way they fly is a little different.
impl trait for struct/enum allows a function to be abstracted and implemented for different types, and in contrast to Python’s Mixin, trait can also be combined to implement multiple traits for a single type. Like Mixin and C# interfaces, traits can have default implementations.
The core idea of
trait is combination,
trait is an abstraction of behavior, different objects can have similar behavior, objects are a combination of data and behavior. In the previous
Django example, although syntactically it is multiple inheritance, isn’t it also essentially a combination? Compositions are better suited than inheritance to indicate that an object has a certain function or feature, rather than is a certain kind.
Look at the duck type. When a place needs a duck that quacks, as long as we provide an object with the quack of a duck, isn’t that just polymorphism? So how is polymorphism represented in Rust’s type system?
The following code will compile through.
Of course, Rust’s enumeration is a and type on the type system, where
Status::Failed are of the same type (
Status), often referred to as
variants, and look at another code example.
The code can be compiled, where I make use of generics, where the custom function takes a parameter of type
T, which is qualified as: the type that implements the trait Fly, which is called trait bounds.
The code has been slightly modified.
Debug trait is implemented here for both structs via the
derive macro. By implementing this trait, you can print out the name of the structure itself. To add this trait to the type qualification of the generic method, the syntax
T: trait1 + trait2 can be used to qualify that a type must implement multiple traits.
The printed result is.
Writing code with only one generic function and Rust actually creating separate functions for each different type after compilation is an approach called static distribution which has the disadvantage of making the compiled size larger. Another approach, called dynamic distribution, puts the type determination at runtime, which takes up less space but introduces more runtime overhead.
The code is not changed much, and dynamic distribution can be achieved by
& borrowing or
Box smart pointer wrapper type, and to add
Off topic: Generic polymorphism is not just for trait bounds, see reference and other sources.
This is a statically typed “duck type” belonging to Rust,
generic_func needs objects that can fly, not caring whether they are airplanes or seagulls, and not caring whether they have a common parent class.
The main purpose of this article is to show how object-oriented programming can be implemented in a Rust way. Rust is not completely unique, and the Python example is listed to illustrate this point. In addition, object-oriented is not the same as encapsulation, inheritance, polymorphism, inheritance and polymorphism can not even be considered parallel concepts.
As for the detailed usage of generic and
trait in Rust, it is limited to space, and related materials such as the official documentation are very detailed, so I won’t elaborate.