- In a typical Rust program,
you're gonna have a lot of structure types.
For example, in our simple demo project,
we've got a structure called Point, Point3D,
three-dimensional point, and Employee,
and we could have hundreds of others.
So it's common to define each structure
in a separate module, in a separate file.
Basically, that gives you readability and maintainability.
Each file is quite small
because it justifies one structure type.
It's easier to read, easier to maintain.
Also, it works easier if you've got a group of developers.
Each developer can be working on a different file
at the same time.
So, generally, a good idea.
You don't have to do it this way.
It would be allowable to put all your structures
into a single file, but then, it'll start to get massive.
You're gonna have potentially hundreds of structures
all in a single file.
It's gonna get difficult to deal with.
Okay, so this is the approach we're gonna take.
We're gonna put each of these structure types
into a separate file or separate module
and we're gonna discuss how that actually works
in a Rust application.
We'll have a file called point.rs,
which defines a Point structure,
and point3d.rs, which defines our Point3D structure,
and an Employee structure defined an employee.rs.
So this is entirely optional.
It's up to you how you organize your structures into files.
It's not like Java
where you have to have one public class per file.
Rust lets you organize it as you like.
So that's what we're going to do anyway.
We're gonna have a separate structure per file,
and we need to see how that works in Rust.
The way Rust works with multi file projects
is not entirely intuitive.
It's something that most Rust developers
scratch their heads over initially trying to understand it.
So I'm gonna explain how this actually works.
How does it work if you want to have lots and lots of files
in an application?
Right, so what we've done
is we've organized our project into modules.
From the top, at the top of our project,
at the top of our crate, our crate root directory,
in main.rs, I've declared a module called mytypes, okay?
So as we know, when we declared a module called mytypes,
what the compiler then looks for
is a file called mytypes.rs.
And up until now,
what we've been seeing is inside that file,
we can define our structures in here, like that.
So we can define our Point structure, our Point3D structure,
and the Employee structure all in mytypes.rs.
But like I was saying,
that's gonna start to get quite unwieldy.
If you are gonna put all your structures into a single file
and you've got like a hundred structures,
that's never gonna scale up.
So there's a different technique.
When you input the module
and you have a file called mytypes.rs,
rather than defining all the structures in here,
what you can do is you can push 'em down
into another submodule.
So you have two levels of module.
From the crate, from the top of the crate,
you have a module called mytypes
which looks for a file called mytypes.rs.
But then it pushes those structure definitions down
into second division modules, if you like.
I've got a second-level module which are made public,
so I can access elsewhere in the application,
called point, point3d, and employee.
So where do these files live?
Point, point3d, and employee.
And this is how it works.
If you have a module called mytypes,
the compiler will look for mytypes.rs.
And if you declare submodules in there,
it'll look for a folder,
the same name as your model name, like that, okay?
So you have a a file called mytypes.rs
which introduces submodules,
and those submodules are in a directory
also called mytypes.
And in here, I define my point, my point3d,
and my employee structure.
So this is a namespace hierarchy as well.
These structures, if you wanted to access them
in your application as we go to do shortly, you'd specify,
you'd use the crate keyword,
which basically takes you to the root namespace.
There's a hierarchy of modules or namespaces in Rust,
and the crate keyword takes you to the root level
in your hierarchy.
So it takes you to the crate.
And then you'd say ::mytypes, okay?
And that kind of takes you to here
and to here as well, mytypes.rs and the mytypes folder.
And then you'd say ::point, for example,
because that's the name of a submodule.
Mytypes is one module
and it has a submodule called point, okay?
So that effectively takes you down to that file.
And then in there, we've got a structure
called Point with a capital P.
So you'd say ::Point.
All right, so that's how it works.
You have a top-level module if you like, called mytypes,
which introduces a sub-level module called point.
And in there, you define the Point structure.
So from the top of the crate, you go to mytypes,
and then you go to point, and in point,
you'll find the Point structure defined like so.
So this isn't probably what you expected,
but it is the way to do it.
Define a module at the top.
In that module file, declare submodules.
And those submodules are basically files
in the mytypes directory.
So you have a mytypes file,
and each submodules are defined in a mytypes folder.
So it's a nesting arrangement like so.
So that's what we've done in the project.
So let's take a closer look at these structures,
Point, Point3D, and Employee.
Here is my Point structure,
and it's defined in the mytypes folder
in a file called point.rs, as just discussed.
And as you notice, I've defined the structure as public,
so it's visible in other modules.
And I've defined the fields as public as well,
so they're also visible in other modules, okay?
So everything's visible.
We'll improve on this later on.
Having public data in the structure
isn't necessarily particularly object oriented.
So what we'll do later on is we'll revert
to making that data private
and then we'll kind of define getters and setters,
like you might do in other languages.
But for now,
we'll just make everything public for simplicity.
Right, so as well as having data in a structure,
we also have functionality.
So here is my structure data definition as we just saw.
And here is the implementation of functionality
for that structure.
Now, a couple of things to notice here.
The implementation block itself isn't public.
You don't declare the implementation block as public.
Rather, what you do is you declare individual methods
as being public on an as needed basis.
So I've decided to make my print method public
and the move_by method public,
but I've chosen to keep the log method private, okay?
Private means it's only visible inside this file, okay?
So it's kind of like a private method in a class
in other languages.
So in my main code, when I create a Point object,
I'll be able to invoke the print and the move_by
in my client code, but I won't be able to invoke log
because it's not public, okay?
So the structure itself is public,
the fields can be public,
and then the individual methods can be public or not,
depending on what you need to do.
Right, so here is my usage of the Point structure.
So to simplify syntax,
I'm going to introduce or import the Point structure
into scope with a use statement.
So from the top level of my crate,
go into the mytypes module,
which define a submodule called point,
which contained a Point class or Point structure.
Once I've done that, I can then use Point here
to create a Point object to invoke its public move_by method
and to invoke its public to_string method.
Right, so we can access the point type here
because we defined it as public,
we can access the public members in the Point structure,
the data potentially and the methods.
Right, so time to show all this in the actual demo project.
So we'll go back and do lesson_11_structs_functionality.
The files we're going to look at, we'll go,
we look at the top level,
everything really begins at main.rs.
So that'll introduce our submodule called mytypes.
And when we look in mytypes.rs.
It introduces three sub-submodules,
point, point3, and employee.
In particular, we're going to look at the mytypes/point.rs,
which defines the Point structure,
and then we're going to use it
in our code demo_modular_code.rs, and then we run it.
So let's take a look.
So from the top, main.rs,
it defines a module called mytypes.
So immediately, but while I'm at it,
lemme just uncomment the code
that's actually going to be running here while I'm here.
Right, so when you define the module,
when you define the mytypes module,
the compiler will look for a file called mytypes,
and here it is.
And in there, we could have defined the structures there,
all of them, but then it would've been massive.
So instead, I've defined three second division modules,
employee, point, and point3d.
And because I'm in a file called mytypes,
it will look for a folder also called mytypes.
And in there,
it'll expect to find employee, point, and point3d.
And it does, okay?
So in particular, it's the point structure we're looking at.
So let's look at the point module.
And in here, I've defined a Point structure,
which is public and public fields as well.
And then we have the implementation of a Point.
I don't think really there's much here
that we haven't already seen.
So let's just make sure.
Oh, there is one thing actually.
The fact that some of the methods are public,
the print method is public, the to_string is public,
meaning we can access it in a separate file.
The reset function is public.
Oh, and the reset function calls log.
Log, what I've done,
basically, every time I change the value of a point,
I log that change to the console.
So I've got a private log method.
It's private by default, not public, in other words.
And it gets the current time in UTC,
Universal Coordinated Time, and that's an object,
formats it as a string containing just the time.
So that's the timestamp as a string.
And what this log method does
is it outputs a message at a given timestamp.
So the message gets displayed here,
and then it calls self.to_string.
So you are allowed to invoke methods on the same object.
But whenever you access something on your current object,
you've got to say self.
So a Java developer might say this,
a C++ developer might say that,
in those languages you could just say to_string,
and they would implicitly think you were calling
the to_string function on yourself.
But in Rust you have to,
when you are accessing one of your own fields or members,
you have to say self, okay?
So this message, this print string will output a message
passed as a parameter.
The second parameter or the second output
would be the current state of the Point object.
And then it'll also output the timestamp like so, okay?
So it's like a quite a handy log method.
So my reset function, it calls self.log,
and then it resets the point.
And remember, again, when we call self.log,
we can't just say log.
We have to say on the current object, invoke the log method.
And also when you move the point,
it also writes a log message to the console,
and then it moves the point by the delta x and delta y.
So my print, the to_string, the reset,
and the move_by functions are public
and the log method is private.
And my user code here, this is quite straightforward,
I bring the Point structure into scope
so I can use that name directly.
I create a mutable Point, I move it and print it,
I reset it and print it.
If I wanted to, instead of having a use statement here,
I could have used that full syntax here.
And then I wouldn't read the use statement, okay?
And that would actually be fine.
Go to the root level namespace in my application,
go to the mytypes module,
go to the mytypes point submodule.
So it's kind of looking here now.
And in there you, it'll find the Point structure.
Okay, so that would also work,
but getting quite difficult to write, doesn't it?
So let's just revert back to as it was.
Let's have an import statement,
makes our code a lot more short and snappy,
like a baby crocodile.
So I think we're ready to run the application.
Let's run it and see.
After all this, I really hope it works.
Cargo run.
The output that we see.
It's interesting actually.
When you run an application,
the amount of effort we've put in under the covers,
behind the scenes,
there's actually quite a lot.
Having a submodule and then having a a directory,
which implements that submodule
with the classes or the structures.
We've put a lot of effort
into the modularization of our code.
But in terms of what we actually see,
you don't really see the benefit of it yet.
But obviously, as the application grows in size,
that kind of structure is gonna be beneficial.
So there's the log method, About to move the point.
It was currently 10, 20, nearly 2:00 PM here
on a lovely sunny Saturday afternoon here in Swansea.
And then it moved it.
About to reset the point.
That's the current value at that timestamp,
and there's the value of the point afterwards.
Okay, so covered quite a lot of ground in this section.
We talked about how in your main code,
you can define a top level module.
And then in that top level module,
you can define second-level modules.
Those implemented.
If you have a module called mytypes,
the submodules would be in the directory
called mytypes, okay?
And the file names will be the same as the module names.
When it says pub mod employee,
it'll import that code in employee.rs,
and in point.rs, and in point3.rs.
Okay, so now we've set up a good structure going forward.