- In this section we are going to dig a bit deeper
into some trait techniques with Rust.
In particular for a trait,
we'll see how a trait can define default methods.
Methods that are already implemented that can be overridden
but don't have to be overridden.
We'll also see how a trait can define associated constants.
You can define constants in a trait.
You can even override those constants
with different values in a structure.
We'll see that as well
and we'll see how a structure can implement multiple traits.
A structure can implement multiple traits.
It's like multiple inheritance
of interfaces in other languages, right?
Okay, so first of all, a trait can define default methods.
In other words, a method that has a default implementation.
And this means that they can be overridden
in a structure if desired, but they don't have to be.
So here's an example.
You just basically give a body to a method.
Here's a trait.
I've got a trait called Log.
It has an abstract method.
Remember, everything's public by the way.
It has an abstract method, which must be overridden
in a structure, and then it has another method
log_verbose that has a default implementation.
So you can override it in a structure
but you don't have to.
It'll fall back on this implementation.
And the log_verbose method
by default will print the current timestamp
and then notice what it does here.
It calls self.log.
It'll call the log method
that you will have implemented in your structure.
So some structure will have implemented this log trait
and that structure will defined a log method.
And this is calling the log method.
It's kind of self messaging.
It's effectively calling the log method
which you will have overridden in your subclass effectively.
So a method that you must implement log
and a method that you can implement or you can just rely
in this default implementation if you like.
Okay, next.
Then a trait can also define associated constants,
constant associated with the interface, okay?
You can override them in structures if you want to
but you don't have to if you don't want to.
So here's an example.
It's just like a magic number associated with that trait.
So I've got a trait called log and it defines a constant
and associated constant associated with this trait.
Log timestamp, it's a Boolean.
It's gonna basically tell the trait whether or not
to display the timestamp when it's writing log messages.
So initially I've set the log timestamp flag
to be false like so, right?
So to access an associate constant, like display timestamp
in a trait method, you'd use this syntax.
Self::Constant_Name, self is kind of
like a type alias for the structure
in which you are operating in currently, okay?
It's like an alias for the current structure.
So here's an example.
My log trait defines an associated constant log timestamp
sets it to false initially, my log_verbose method
which will output the current object in a verbose format.
First of all, it looks at the log timestamp field
to see if it's true or false.
If it's true, then it prints the timestamp.
And in any case,
it calls the log method to output the regular log function.
So Self is an alias
for the type which will have implemented the structure.
You don't say log here, you don't use your trait type.
What you use is Self is like an alias
for whichever structure has implemented this trait
that's the name of the structure that will be replaced here.
Okay, a structure can implement multiple traits.
You define a separate implementation block
for each trait that you want to implement.
Quite straightforward, and we've kind of seen something
like this already in the previous example.
Here's my structure, employee with data.
Here's the regular implementation of employee, you know
the regular methods like new, and here's the implementation
of the print trait for employee.
So whatever methods the print trait requires
will be implemented in this block.
And here's the implementation block
for the log trait for employee.
So I actually quite like this, invest in a normal,
in a other object oriented language like Java and so on.
You would have one class that defies all the methods,
the methods that implement one interface
plus the methods for another interface
all kind of mixed up in the same class definition.
And in Rust, you have a separate block per slice.
So here's the kind of basic implementation for employee.
Here's the implementation
of the print interface trait for employee.
And here's the implementation
of the log trait for employee.
So actually nicely partitioned into sections.
So we need to see an example.
We'll go into the lesson 12 traits project.
The files we're going to look at in here
obviously we'll start with main.rs.
Then we'll have a look at the trait defined in log.rs,
and we have the structure implements defined in employee.rs,
and then we'll see how they use that code
and the actual demo function, demo_trait_techniques, okay?
And then obviously we'll run the project
once we've dissected the code.
So let's have a look.
Okay, so from the top then main.rs
we're going to run demo_trait_techniques.
And we're gonna look at the trait will be in log rs here.
And so it's public, so we can access the treat elsewhere.
Remember, every member in a treat is implicitly public.
You can't say public.
It is, it has an associated constant log timestamp, false.
I can override that
if I want to in a structure, bear that in mind.
I've got an abstract method.
A structure must implement that.
So the employee structure
will have to implement the log method.
I've got a log_verbose method
which is kind of like an adapter on top of the log method.
You often see this.
There'll be a method that must be overridden
and some kind of wrapper method, which will underneath
will actually call log eventually,
but it provides some kind of wrapper functionality
as well around it.
And that wrapper functionality has a default implementation.
It's like a standard wrapper.
So if you call the log_verbose method
then by default it'll print like a banner at the top
and a banner at the bottom,
depending on the value of log timestamp, which is false here
but could be overridden in the subclass.
So basically whatever structure we are looking at
have a look at the log timestamp flag.
And if it's true, then also I'll put the timestamp,
also called the original log method, okay?
So whatever the original log method would've been done
that's still gonna be called, but with this beautiful banner
that sits around it.
So that's my log trait.
A constant that I can override in my subclass if I want to.
A method that I must implement
and a method that I can't implement, but typically won't.
That's the general idea.
When a trait has a default method implementation
it means you are unlikely to need to implement
this method yourself.
Here's the default implementation
that I've already thought of.
You're probably just gonna use this,
you can override it if you want to.
Most of the time you're probably not going to need to.
Right, so the structure which implements log is employee
and that's all of my structures
remember are in my structs.
So employee.rs, let's have a look.
So the data for employee is same as before.
An employee has a name and a salary and a full-time status.
The regular implementation for employee,
you can give the employee a pay rise
and you can create a new instance
using the associated function new.
Then we have the implementation of the print trait.
We saw that earlier.
It just implemented the print method to print out
the employee's details and also,
and separately the implementation of the log trait.
I've chosen to override the log timestamp constant, okay?
Because remember in my trait for log,
the log timestamp was initially false, okay.
I could've just left it like that.
I would've inherited that.
I've actually overridden it in my subclass.
In my structure if you like,
I've overridden log timestamp to make it true.
Okay, so you can redefine it in the subclass,
in the structure as being true.
In the interface or the trait,
it said I had to implement the log method.
So I have,
because obviously only the employee structure
knows what it means to log the data for an employee.
So that must be overridden.
And it is, here it is.
So here's the implementation of the log method.
It logs the employee's name, salary, and fault,
it just outputs them to the screen.
I haven't chosen to implement the log_verbose method
so I just inherit the default implementation
from the trait and usually that's sufficient.
Okay, so my employee structure will inherit that function
for free.
So let's just see how to input,
how to actually make use of my employee.
So that's my employee structure
with the interfaces that it implements
and the data that it has.
Let's see how to use it in my code here.
Demo_trait_techniques, okay?
So create a new employee Mary, give Mary a pay rise.
Invoke functionality defined in the log trait,
invoke the log method and invoke log_verbose
with the heading and with the timestamp.
Oh, and by the way, you can access the employee timestamp
because if I go back to square one in my log trait
remember that whenever you define anything in a trait
it is implicitly public, okay?
So this here is public
and I can access that in my external code.
And I have, okay, so I said, by the way
the value of the employee log timestamp is
and that'll output the value of log timestamp.
And as you remember in the employee structure,
I set that to be true, okay?
So in my implementation of the log trait
I redefined that to be true.
I just mentioned one more time.
When you implement a trait in a structure
all of these fields here in members are also public,
traits are all about public interface.
So you don't say pub
and you don't say pub here they are implicitly public
and can therefore be accessed in your client code, right?
So with a relatively high degree of confidence
we should run this code and hope cargo run.
Okay, so in demo_trait_techniques
remember I called the log method, first of all
which just output Mary's incremented salary
and her full-time status.
And then I called log_verbose and log_verbose printed
the banner.
I printed the timestamp because the Boolean flag was true.
And then they called the log method
and then it printed the end banner like so.
Great, so final recap then.
In a trait, you can define constants
which can be overridden.
You can define methods that must be overridden
and default methods that can be overridden
but don't need to be and typically aren't.
And you can also, obviously in a structure,
you can implement as many interfaces as you like or traits.
And each trait implementation is clearly delineated
easy for you to say, easily delineated
as a separate block like so.
So it's quite elegant, I think.