Section three, applying multithreading
features to your project. In this video,
we will talk about working effectively
with organized library modules. It's time
to refactor our code, we're going to
organize it nicely into library modules,
so that in the next video we can start
running our code in multiple threads. The
first thing I'll do is open the project
Explorer and create the files that we're
going to have in our library. So first we
need our lib dot RS and export all the
modules that we're going to create. I'm
going to make four modules, args for
argument parsing, read for our reading of
data, stats for our outputting of
statistics, and write for a writing of
data. The last thing that we need at the
top level of our library is the constant,
so I'll paste the constant in. Now let's
add a module file for each of our
modules, each of the files will be the
name of the module plus dot RS. Okay I
skip to all the files being created. Now
let's go look at Lib dot RS the modules
resolved now. They're all empty
so let's go and get something put them
in them, let's fill out args to our s
first. We need the argument parsing
imports, and then let's make us struct, I
don't have to do this we could return a
tuple of the values, or we could just
return the raw match object which could
contain anything, but I don't really want
to keep changing the return value of my
function if I change the options, so I'm
gonna collect everything in this struct.
And since I have a struct I might as
well go ahead and make an Associated
function to do the parsing and return
the struct, and then everything's all
tied together neatly. So I'll go back to
main and grab all of the parsing logic
here, and cut and go back over to args
and paste it inside of our function, and
we're nearly there, the logic really
doesn't have to change, we do need to
return our struct with the information.
So if that in file out file and silent,
but in file and out file are supposed to
be strings so let's convert them to
strings right here,
we'll just call to string on the
borrowed string slices, let's close the
project view so you can see everything.
And let's go back and recap, so we've got
our imports, so we've got a struct to
contain our information in these fields,
we've implemented the parsing logic and
an Associated function that returns a
new args. We left all the logic the same
and at the end we returned our args
struct. So now that our args module is
complete we can move on to our read
module. Let's grab some of these imports
from main and copy them, and paste them
over and read, it will clean out the ones
that we don't need in a bit. So put those
here. We also need inside of our own
crate the chunk size, so we know how much
to read. And if we go through these we
don't need a buff writer,
we don't need error kind, and we don't
need right. Now let's go down and make
our read function, let's take in file as
a borrowed string slice, because we're
not going to change it. And since we can
fail to return a result of what a vector
of u8, so that's our vector of bytes, then
let's go back to main and grab our
reader logic, cut that and come back to
the read module paste it into our read
function, clean this up here, and then
let's go back to main and see what other
reads stuff we can get. Okay, creating the
buffer that we're gonna read into, we
need that, let's go paste that in, and
then let's go back again and grab the
actual reading call, and take that put
that in. We'll return an okay with our
vector at the end, but let's figure out
what to do with these breaks up here
first.
Since we're returning a vector we can
return a zero size vector and deal with
the breaking of the loop out in main. For
the error case we can replace the break
with returning the error, so we need to
go and instead of ignoring the error
capture it, and then we can return it and
we have to rewrap it in error, but this
logic is exactly what a question mark
does. So let's simplify this down to just
the question mark,
and we need to also go remove the match
from in front there, now that should work.
Now we can create a vector out of the
populated part of the buffer that has
our bytes in it and return that. Now our
read function is done, so let's go work
on our stats module. First let's go to
main and grab everything that has stats
output in it somewhere, we'll just copy
the whole thing, and then let's go over
to the stats module and put it in a
function, let's call it stats.
What are we need we're gonna need silent
which is a boolean,
we're gonna need num read which is a you
size, and then we're gonna need the total
bytes, now this needs to be a mutable
reference to sum total bytes outside of
the function, and then last we need a
boolean to indicate whether this is the
very last statistics output when we need
a new line. Now let's go ahead and paste
the bits from main, and then let's delete
the part from the writer that we don't
need, and then let's fix the indentation
here on the second block, and comment it
out for the moment, we'll come back to it
in a second. For now let's go up to the
top of the function we need to
dereference the mutable value through
the total bytes reference, so that we can
increment it by number red that's our
actual statistics collection, and then we
can reimplement that logic that we had
in that last block, where if this
is the very last time through, then
we want an extra print line and
that's the only difference in that last
block, so we can get rid of it now, okay.
So our arguments are silent, num read,
total bytes, and last. We take our total
bytes dereference sum incremented by num
read, if we're not silent we print out our
progress, and then if this is the last
time we print out an extra newline. Now
we can go back to main and clear out the
stats parts, so this top block and this
bottom block, and we don't increment
total bytes here anymore.
So we're ready to move on, so we've got
this part here let's copy this and go
work on the write module. I'll make some
room for the imports that we're gonna
have and then start our function. So
write is going to take an out file, and a
buffer, which is going to be a slice of
bytes, and then our return value is going
to be a result again, but this
we're gonna be boolean, I'll show you
what that's for. So let's paste this in
and then go back to Main and delete that
and grab the writer code, will cut that,
go paste that over at the top of our
function, and then let's go back to main,
and grab these imports, cut those and go
paste those at the top of our module.
We'll clean those up in a little bit, but
first let's talk about what we're
returning from this function, so a result
of bool means something.
After we create the writer and then do
the writing we return some success with
a boolean, so let's say that true means
keep going. So here's our ok true, that
means in our error case we'll put false,
meaning stop cleanly, something happened
that's not an error we want to crash for,
but we do want to notice it and stop
cleanly by returning in okay false. Now
let's go and clean up the imports, so we
don't need buff reader, we do need error
kind, read we don't need, and then
resultant write are all good.
Which means all we have left is main so
let's do our import of standard I/O
result because we need to return that
from main, let's clean this up, and it's
finally time to go and import all of our
new modules that we wrote. We need to use
pipe viewer, we can't use crate here
because the binary in the library are
separate crates. I'll follow the
convention of accessing functions
through their modules, and we're finally
ready to call that associated function
and get an args struct. Now we can go
down into our main loop. What are we
going to do? First we're gonna make a
buffer, we're gonna get it from our read,
we're gonna match on that because it
returns a result, a result of what? Well
of a buffer a vector of something, so you
remember that special case. So if it's
empty let's add a guard here for the
empty case, then we want to break out of
the loop, otherwise if X isn't empty then
we want to keep it, and if there's an
error we don't really care what it is,
we just want to break out of the loop
because we're done. And
after reading we collect statistics and
output progress, so we call our stats
function, we pass it all the arguments, so
silent and length, the mutable reference
to our total bytes and false, because
this is not the end. And then we can
write out our bytes, so we call our write
function, we give it our out file
reference, we give it a reference to our
buffer, and then since it returns a
result we can use the question mark, and
if that succeeds so we have a boolean. So
if it is not keep going then we want to
break, and then finally outside the loop
we're gonna call stats one more time,
I'll give it silent, we don't have a
buffer so put zero, the same total bytes,
and then we'll put true, because this is
the last stats, and then we're done, so we
exit with an okay. Alright let's try
things out, first let's build things just
make sure, uh-oh we got a problem,
oh so we tried to use num read and write,
though we don't have num read and write,
what do we have, well we have this buffer
here, and we already know that it's sized
exactly correctly, so let's take off the
slicing and just give it the whole thing.
So let's save that and go back and build
it again so run Cargo build and great
this time it worked.
Let's clear the screen and echo Apple
into Cargo run, and then output that to a
text file, okay let's do that got six
that's good that looks right so far,
let's look and see if the file really
exists, okay still good, six bytes, does it
have the right contents? Yes, all right,
our refactoring jobs looking pretty good
but let's be sure let's echo another
string here, and just run it to Cargo run.
Okay that looks good like we expect the
double output there, so what if we take
yes and let's pipe that to Cargo run, and
then pipe it up to head, let's give a ten
million, and then redirect our standard
out so that we don't have all the Y's, so
we just get our progress, and fantastic. I
think we're good. In the next video,
we will separate our input statistics
and output logic into separate threads.