- As applications get larger, and Rust is no exception,
it becomes important to start thinking about
how you're gonna organize your application
into separate files and separate data types.
So things to consider.
How are you gonna split your application
into different files or modules in Rust?
What data types do we need in the application
in order to, you know, congeal data cohesively
and do proper structures?
Are there any additional libraries, or crates,
as Rust would say, that we need to add into our application?
So let's take a look at our application
with those things in mind.
We're going to take a bit of a closer look now
at the source files in the application.
So we're gonna have a look at main.rs first.
We've already had a quick look at that.
Then we'll have a look at types.rs,
where we define structures that we need
to represent the data in our application,
like visit, for example, to Europe.
Then we'll have a look at util.
I'm not gonna go into the details yet,
I'm just gonna discuss the broad intent.
Util.rs defines some miscellaneous utility functions,
getting stuff in the keyboard, handling dates and times,
miscellaneous utility, things like that.
File handling in fh.rs.
And then the menu.rs,
which displays our menu
and keeps on redisplaying the menu
until the user chooses to quit.
Okay, so we'll have a look at all of these files.
I will also have a quick look at the toml file
to see what other crates our application requires.
Okay, so here we are then.
In main.rs first of all,
we have already seen this in the last code walkthrough.
So it defines four modules,
which are defined in separate files,
types.rs and it defines util.rs
and module and main.
So let's have a look first of all, in types,
that's the first module that we define.
It defines common structures and maybe other types,
like enums and traits that are used in application.
Okay, so here we are.
Interesting, lots of different details here.
Obviously we're gonna dive into the details later on.
So I'm just gonna give you the big picture now.
So you've got this kind of image of where we're heading to.
A visit.
The visit structure contains information
about a visit to Europe,
is public so I can use it in other files.
It defines the data for a visit, the start date
the end date, and the description.
I've used a class or structure called naive date, okay?
Naive date is defined in the chrono crate.
Naive date represents,
it's not that naive really.
You'd think with a name like naive date
you wouldn't wanna use it because it's naive,
but it's naive in the sense
that it doesn't have a time zone.
Okay, but apart from that, it's perfectly usable.
So if you're not interested in time zones
in your date handling, use naive date.
It's by far the simplest way.
So the visit structure represents, as read from the file,
a visit to Europe.
What date did you visit?
What was the end date, or the day after you came back,
and the description of the visit?
And then I've got some functionality
associated with the visit structure as well, okay?
And we'll have a look at these functions later on,
but you can see that it has,
it's quite a simple structure really.
So we'll have a look at the details of that in due course.
Oh, also the visit structure implements the display trait.
Remember the display trait?
If a structure implements display
then you can output it using the default formatter
in println.
You can say println, curly brackets, visit
and it'll call the display formatting to output.
It's like a two sprint basically for the visit structure.
I've also got another structure called record.
The reason for that would become apparent later on.
It defines some data.
Oh, it automatically supports the eq
and the partial eq traits.
Remember those from before?
That gives you the ability to say less than or greater than,
less than or equal to, greater than or equal to.
So there's implementation
and then it implements the display trait to be displayed.
And it implements a couple of other traits as well.
I've gone to really like the way
that Rust splits trait implementations into separate slices.
If this is Java or C# or C++,
then you would define a single block of code
that contained all of this,
the data and the functions.
And the functions which implement one interface
and the functions which implement another,
all in one massive class.
Whereas in Rust, it's much more obvious.
This is the implementation of the display trait for record
and this is the implementation of the ord trait
and the partial ord trait.
So it's much more segmented, much more discreet.
So it's quite nice, I think.
So obviously we look at the details of that later,
but that's our types module.
Next up we're gonna look at util.
Here's our util module
and it contains some miscellaneous utility functions, okay?
Nothing strategic, just tactically useful
to do some tricky code, hide it into this util module
to parse the command line.
It grabs the parameters in the command line
and it gives me either a string, or nothing,
depending on whether there was a command line or not.
In many cases in the application
I ask the user, please enter a string, okay?
So I've encapsulated that
in a prompt for string function.
There's a message that it displays to the user,
a prompt, please enter something, something, something,
and it returns a string as entered by the user.
There were other times when I asked the user
to enter a date, okay?
So like, what start date would you like to specify?
So it displays a message and it returns a date,
or rather a naive date without time zone,
like the 31st of December.
And then a conversion function.
I pass in a string or a string slice,
and it'll convert it or parse it into a date.
It's like a date parser, really.
You parse in a string and it parses it
and returns the date that it contains.
So I didn't plan on having these functions,
I just started writing the main body of the code
and often just something fiddly
ends up being five lines of code
just to parse a string for a date.
So at that point, it's useful to push it down
into a function to hide it from the main body of the code.
And also potentially be able to reuse that function
elsewhere.
Okay, so that's util.
The next module we're gonna look at is FH, file handling.
So just two functions in here,
notice they're both public, obviously.
There's a function which will read visits from a file.
This is the function.
It gets (indistinct) called at the start of the application.
Remember, in main when the application starts
from the file module fh, it reads the visits from the file
whatever file name it is, like data.text
and it returns a collection of visit objects
as read from the data file.
Okay, so it takes a file name
and it returns a vector of visit,
where visit is defined in the types module in my crate.
Oh, and then at the end of the application
remember if the user chose to save the data
then at the end of the application in my FH module
it'll write those visits to the file.
It takes the name of the file, it takes the visits data,
as a vector, basically will reference to the vector.
It borrows the vector and writes it to a file.
So inside here, it's got code to take that vector,
it doesn't change the vector,
it just writes it to a file.
And we're gonna look at that later in detail.
Right, so the last thing to look at is the menu module.
And the menu module, let's just collapse this down.
I'm gonna go through this as well in more detail.
I just wanna kinda give you the structure for now
so you kinda get a framework in your mind.
So at the moment, the rule with Schengen
is that the number of days that matter is 180.
You need to go back over the previous 180 days
to count the number of visits.
So I defined that as a constant.
I could've just put the number 180 in my code down below,
but it's much better to declare it as a constant.
It's much more readable and potentially maintainable.
So this is the main do menu function.
That's what I call from main.
Basically when the application starts, it says menu do menu.
So in my menu module, the do menu function,
here is the menu that's gonna be displayed, okay?
I'm not sure if I've mentioned r strings before,
Raw strings.
A raw string is basically,
it's a string that can span multiple lines, okay?
So I don't have to kind of end one line
and then display another line, that's just one string.
And in the string I've got soft count returns.
Those were actually displayed
as count returns in the output.
So my application loops forever
until the user says to quit.
So that's a good example of loop.
Loop forever, until the user says, stop.
In my util module,
there's a prompt to string function that we saw just now.
The message that it displays is this menu,
it'll display that menu on the screen, okay?
And it returns the option selected by the user
which will hopefully be one or two or three or four or five.
So I take that selection as a string
and trim any white space,
and then see what option the user has chosen.
I mean, there were lots of different ways
I could have done this.
If the user chose option one, call this function.
Now these functions are actually here in my menu code.
So if the user chose option two, then that function is here.
If the user chose option three, then that option is here.
Okay, so the code to enforce or to enact those options
is actually here in the same module.
If the user chose option four, then a return true
back to the main code, which means save the data.
If I return false, then it returns back to the main code
and it says, don't save the data.
If the user chooses any other option,
I say, "What?
Okay, and that loops forever.
Right, so there we are.
And then obviously at the end,
it'll choose, if you've returned true,
to save the data back to the file.
So we start to get a feel now
for how the code structure works.
The next steps obviously is to dive into the details
of each of those modules,
and have a closer look at the coding techniques
and the library functions
that I've used to achieve the effects.