- Inheritance is a very important feature
in object-oriented program languages.
It's where you define a supertype
that specifies some common behavior and data
and then you define subtypes which inherit that data
and functionality and can extend it.
So that's how you might be familiar
with object orientation in other languages.
But in Rust it's quite different.
Rust doesn't allow structures to inherit from each other.
Instead, in Rust, inheritance is based on traits.
Okay, so you have to implement a trait
rather than inheriting from a structure.
It's all based around traits.
So here's a simple trait
which specifies overridable behavior.
It's a simple print trait.
It can be implemented by any structures that want to.
So here's a point structure
and here's the implementation of the print trait for point.
The point structure implements the print trait.
The employee structure also implements the print trait.
They both have a print function.
They both implement the print function
from the trait defined above.
Okay, so Rust supports the Liskov Substitution Principle.
So what this means is you declare a trait reference variable
and then it can refer
to any object which implements the trait.
It's the principle of substitutability.
Here's an example.
I defined an object reference.
So I've created a point object
and I've created an employee object
and then I can pass the point object.
That's object one or the employee object, that's object two.
I can pass references of those into this function
because this function is effectively polymorphic.
It receives a reference
to anything which implements the print trait.
So you can pass into this function any type
which inherits one or which implements the print interface.
Object one is a point object that implements print.
I can pass a reference to object one into here.
Object two.
Object two is an employee.
Employee also implements print.
I can pass a reference to employee into here.
You'll notice the dyn keyword or the dyn keyword.
That's short for dynamic.
The dyn keyword represents dynamic dispatch.
Okay, it's basically similar to a virtual function in C++.
It triggers dynamic dispatch.
What it means is that when the compiler sees
that you're calling the function
through a dynamically dispatch variable,
the compiler can't decide which function to call
because at compile time it doesn't know what kind
of object it'd be referring to.
It could be referring to any object
which implements the print trait.
So the compiler can't decide what would be
the appropriate print function call.
It leaves that decision until runtime and at runtime
when it knows what kind of objects actually being passed in
then it can make a decision at runtime dynamically
to which function gets called.
So let's reconsider the example where we had an object.
One was a point, object two was an employee.
I could pass either a reference
to a point or a reference to an employee
or in fact a reference to anything which implements print.
I can pass anything like that into this function here.
P could be reference
to anything which implements the print trait.
Okay, so when you pass a trait in like this
you have to qualify it with the dyn keyword.
It's almost like reinstating
into the compiler that you understand
that it's going to be a dynamic dispatch.
Okay, if you omitted the dyn keyword here,
it wouldn't work actually, you get a compiler error.
At runtime, so at compiler time,
when the compiler sees the print function
it doesn't know what kind of object
at compile time has been passed in.
Could have been a point, could have been a employee.
So at compile time it doesn't make the decision
about which function call, but at runtime it looks
at the object you've passed in
and that influences the decision
which function will be called.
So this statement here will actually have different effects.
The first time I call print something
with a point, at runtime, this will be a point object
and it's the point print that would be called.
In the second call I pass in an employee reference.
So at runtime P will refer to an employee
and it knows that it refers to an employee.
So at runtime it'll call the employee statement.
So this is effectively, this is polymorphism
it's effectively like an if statement at runtime.
If the object is a point, then call the point function.
If it's an employee, then call the employee version.
Internally, the way that dynamic dispatch works
and trait interfaces implementation,
it uses a V table mechanism
similar to the way it works in C++.
Basically a bunch of function point is to say
if it's this kind of object call these functions
if it's a different kind of object,
call these functions instead.
Right, well, so we are seeing an example
of how to create a polymorphic collection here.
You can create a collection based on a trait type.
The collection can hold any objects
which implement the trait
and then methods would be called on each instance
depending on what particular type that instance happens
to be dynamically at runtime.
So here vector is a vector of any object
which can implement the print trait and again,
you have to use the dyn keyword here almost to reinforce
to the compiler that you do understand any method calls
will be dynamically dispatched.
I've got a vector which includes a point object, wasn't it?
Object one was a point and object two was an employee.
So when I iterate
through the vector first time around the loop
the object would be a point and dynamically at runtime
the compiler will decide to call the point version of print.
Second time around the loop,
the object would be the employee.
So dynamically at one time
this will call the employee version of print.
Okay, so this is a polymorphic collection.
Right, so, in less than 12 traits
these are the files we're going to look at in the demo.
First of all, main.rs obviously to bootstrap the demo
and then the traits we're gonna look in print.rs.
And then in terms of implementation classes or structures
we'll have a look at the point structure
and the employee structure, both of which implement print.
And then we'll use all that
in our kind of driver code demo inheritance polymorphism.
Okay, so let's take a look.
Okay, right, well here we are then.
So in main let me uncomp the code
for inheritance and polymorphism.
And then in the traits we have the print trait.
Simple enough, it has a simple print function.
And then in terms of structures
well the employee structure implements print.
Okay it implements other things as well.
But that's all we're interested in at the moment.
So the employee structure implements print
and then the point structure,
well here's the point structure.
We can look at all its details
but really all we need to know about
is that it also implements print.
So hopefully now you're getting to like the way
that interfaces or traits are kind of sliced
as individual implementation blocks.
You can quite quickly hone in
on the particular trait implementation for that structure.
Right, okay, so in terms
of driving the demo, demo inheritance polymorphism
this is the code we're going to run.
So we import the trait footprint.
We import the structures for point and employee.
I create a point object and an employee object.
I pass a reference to the point and a reference
to the employee into this function down here.
And this function can receive anything
which implements the print trait.
So this is Liskov Substitution Principle.
You can substitute in here anything
which implements the print rate.
And don't forget the dyn keyword
to indicate dynamic dispatch.
So the first time we call the print something function
we pass in the point,
at one time it'll be a point object that gets printed
and it'll call the point print function correctly.
And then the second time we call print something
it'll be the employee object that gets passed in
and at runtime it'll realize that
and it'll call the employee print.
No need for us to actually make any decisions ourself.
It'll automatically figure out at runtime
what was passed in.
And then as we saw a polymorphic collection
a vector of any image implements that trait
such as the point object and the employee object.
As I iterate through here at runtime, first time through
it'll print out a point.
Second time around the loop, it'll print out an employee.
So let's run this and see.
Okay, so,
cargo run.
Right, so in print something it printed a point first
then an employee, okay.
So here is a point
and here is an employee.
And then printable things in a polymorphic collection.
That's my header.
Printable things in a polymorphic collection.
So it activates round
and it prints out the point and the employee.
So there's the point print function
and there's the employee print function.
Okay?
So this is polymorphism and inheritance in Rust.
You can't, inheritance has nothing to do with structures.
It's all about traits.
A structure can implement a trait in different ways
and it's the trait
that you use as your polymorphic superclass, okay?
You can't pass in substitutable structures.
It only works with traits.
You can pass in anything which implements that trait.
So it's quite different from most other languages.