- A Trait in Rust is similar to an interface
in other object-oriented languages.
When you define a trait,
the trait can define abstract methods.
Methods that must be overridden by a structure.
You can also define default methods.
So these methods can be overridden,
but they don't have to be.
They have a default implementation to fall back on.
Many object-oriented languages are providing
this kind of default method mechanism these days.
A trait can also define constants, okay?
And constants can be overridden.
And in a structure,
we'll have a look at all these details later on.
First of all, here's a simple example of a trait.
I've find a trait called Print.
The trait name is usually has a capital letter
and it can have methods.
This trait has a single method called print
which must be implemented by a structure
to print the object that you specify.
So, couple of things to note.
All members in a trade are implicitly public,
you can't actually use the pub key with here.
It's public by default and it must be public.
Right, well, here's an example.
This looks a bit different from what you might expect.
I've got a structure called Employee.
I've defined the data of the structure here.
An Employee has a name, and a salary, and a full-time state.
I've defined the regular implementation for an employee,
like payrise and two string and such like.
But your implementation also has a separate section
as well as the regular methods for employee.
You've also got the bit which implements
the print interface for employee.
All right?
So, for each interface of each trait that you implement,
you have a separate block like this.
This is the implementation of the Print trait for Employee.
In other words, here is the print function,
if the Employee implements the Print trait
then here's the print function
and self here will be the employee.
So, it'll print out the employee's name,
and the employee's salary
and the employee's full-time status.
Okay?
So rather than put in the print function in here,
which is what you might've expected,
you put it into an explicit block.
This is explicitly the implementation block
where we implement that trait for this employee structure.
Right? So you can imagine that by the time
you defined all your traits
and then all the structures which implement those traits,
you're gonna have quite a lot of files involved.
So you have to think carefully about
how you're gonna organize that into modules
in your vast project.
So, what we've decided to do in this demo
is to have separate modules.
I've got a module called mytraits,
which we define all the traits.
I've got a separate module called mystructs,
which will define all the structures
which implement those traits.
So there'll be two separate directory structures,
a directory structure where all my traits live,
and a separate directory structure
where all my structures live which implement those traits.
Kind of made sense to me.
So, this is how it's going to look diagrammatically.
So, in my project at the top level,
I have my crate root directory,
and in there I've got main.rs.
And in main.rs, I define a module called mytraits.
Okay? And when you define a module called mytraits,
you must then have a file called mytraits.rs,
and I could have defined mytraits directly in here
or I can push it down into a subdirectory.
Remember, we saw this in the previous lesson,
you can either define everything in one file
or you can push it into a subdirectory
and define it as a submodule.
So, in mytraits.rs, I define a submodule called print,
which therefore implies a file called print.rs
in the mytraits folder.
So, let me just remind you how this works.
In a file which represents a module,
if you define a submodule,
then if the file is called mytraits.rs,
it will look in a subfolder with the same name, mytraits.
And in there, it'll look for a file called print.rs.
And in there, I define the Print trait,
I could also have other traits defined as well.
For example, I could define a public module called loggable,
and I could have a loggable.rs,
which defines my loggable trait.
Okay? So I'll define one trait per file,
and I'll have lots of files organized into a subfolder.
So, mytraits.rs and mytraits the folder
will define all of my traits.
And then back up at the top level,
I've also declared a module called mystructs.
Okay? And I have a file called mystructs.rs
and a folder called mystructs.
And in mystructs.rs, I define submodules,
for example, employee, okay?
So, if you in mystructs.rs
and you define a module called employee,
then the compiler will look in the folder called mystructs,
for a file called employee.rs
where you define your employee structure.
So, you have to be quite careful, you have to think,
you have to plan basically your directory structure,
and organize your work into separate modules
and submodules, and give it some thought
because it's quite different from the way
application structures work in other languages.
So, how about using those structures and those traits
in your client code?
You're gonna have to have some used statements
to import names into scope.
It's gonna look something like this,
in some code in your application,
you would say like, go to the top level in my crate
input into scope the mytraits module,
which has a print submodule,
which defines a trait called Print.
And also, from the top level in my crate, in my project
there's a module called mystructs,
which defines a submodule called employee.
And in that submodule, there's a structure called Employee
with a capital.
Remember that modules as well as being a directory structure
are also kind of like namespaces in rust as well.
So anyway, I've introduced my Employee structure.
I can create an Employee, I can call payrise,
I can also call the print function
because obviously you might implements the print interface
or the Print trait.
Just one word of caution,
if you invoke a method which is defined in a trait,
then you must also import that trait.
So, if I call the print function,
the print function was defined in this trait here,
you have to import the Print trait
in order to invoke a method defined in that trait.
If I'd forgotten to import the Print trait here,
then you get an error message when you try
to invoke a method defined in that trait.
Okay? So that's a bit surprising maybe,
but you'll soon get used to that as well.
Right, so in the example for this chapter,
this is the project lesson12_traits.
And if you look in that project
we'll have a look at there in a moment.
So, everything starts at the root level in main.rs
and in there I define submodules
called mytraits and mystructs.
So, we'll have a look at mytraits.rs
and we'll see that it defines a submodule called print
which defines my Print trait.
And then we also define a submodule
called mystructs/employee,
which defines the employee structure,
employee implements print,
and then we'll have the demo code
which actually makes use of these types.
And then we run the application as usual.
Okay, so here we are then.
From the top level notice, I've got a mystructs folder,
I'm gonna have lots of different structures.
You can see now why I've organized it into subdirectories
because there's a lot of stuff here now.
I'm gonna have several traits as well.
We're gonna look at over the next two demos.
Right. So from the top main,
I introduce a module called mytraits.
So, the compiler will look in mytraits.rs
and it'd look for a submodule called log,
and a submodule called print, and a submodule collection.
And it'll looked for those in the mytraits folder.
So, in the mytraits folder, it look for a log.rs,
a print.rs, and a collections.rs,
and there they are.
Print.rs, is the one of interest for us at the moment.
In there, we define our public Print trait
with the print method to be implemented.
Okay, back to main.
I've also defined a module called mystructs, okay?
And mystructs is here and it defines
various different submodules, employee, point, and so on.
Employee is the first submodule and it look for,
if we're currently in mystructs.rs,
it look in the mystructs folder
for a file called employee.rs.
So in mystructs, we have employee.rs
and in here we define our employee structure
and it implements, let's see what's going on here.
So, there's another interface or another trait called log.
We'll have a look at that later.
Ignore that for now.
Here is the data structure for my employee.
An employee has a name, and a salary,
and a full-time status.
Okay? So that's just the data.
And then we have the regular functionality,
nothing to do with traits.
Just the kind of basic functionality for an employee.
An employee has a method to give the employee a payrise,
a handsome payrise, I hope.
Oh, also it has an associated function
to create a new employee with a name,
and a salary, and a full-time status.
So we've seen this kind of thing before.
As well as that, this is the implementation
of the Print trait for Employee.
Just the Print trait here.
It's like a slicing mechanism,
altogether you define the data for the employee.
Its regular implementation, how it implements one trait,
and how it implements another trait.
It's all very piecemeal step by step.
And I actually quite like that in rust.
It's quite clear to see this is just
how it implements the Print trait.
So, to implement the Print trait,
remember, the Print trait was here
and it says if you implement the Print trait,
you must have a print function.
So, we do, if we implement the Print trait,
then we must have a print function.
Here is the print function.
It's implicitly public.
When you implement a trait,
the function is implicitly public.
You don't have to say pub anymore.
The whole thing about interfaces is public interface.
It is implicitly public
and it is an instance method
you would say employee1.print
and it'll print the person's name, how much they earn,
and their full-time status.
Okay, so finally then back up to main.
Let's see how to actually use all this.
Demo trait essentials.
That's the code we're going to run.
And it's here.
So we import from the top level namespace,
we import the mytraits module
which defined a submodule called print
which defined a Print trait.
Okay? So from the top level, go into the mytraits module
which defined a submodule called print,
which is here.
And in there, it defined the Print trait.
So, import that into scope
and then also import the employee structure into scope
and then just use it.
So we create an employee, we invoke its regular method,
and then we invoke its print related function here.
Okay? So what we do now is run and hope.
(keyboard typing)
Okay? I have a high level of confidence
that this is gonna work.
So what it's doing at the moment,
it's just importing various dependencies
from the Cargo.toml.
I've imported the chrono dependency.
We've seen that a few times,
it also inputs other dependencies.
So those account be being compiled at the moment
and when it's downloaded all of those
it'll then actually run the code, create the employee,
give the employee a rather meager payrise.
I think that's probably not enough money, is it really?
And then print the details afterwards.
So after that payrise, John now earns 200 units
and he's not a full-timer.
Right. So that's a simple example of traits.
You define the trait in one file
and then you implement it in a structure.
You have an implementation block for each trait
that you want to implement.
You have a separate implementation block for that trait.