Plan C - Redesign Our Program As We Convert Debian's "sl" Toy from the C Language to Rust
Click Here if you would like to see a complete code listing to this point.
Click here to return to Plan B of the "Convert 'sl' in Debian from the C Language to Rust" project, at the "Add C51 Train" step.
When I started this project, I thought I'd pretty much just duplicate the structure of the C-version of "sl", as found in the Debian ecosystem. Then I thought it'd be good to package all the coding/data bits belonging to any one image, say, the D51 train, into its own "module". I'm calling that revised plan, "Plan A". By the time I got to the "Add C51 Train" step, I realized that was going to create a lot of code duplication, and I started following a different plan, which I'm now calling "Plan B". But it still had a lot of complexity, and when I went back through the tutorial to clean it up, and got to that same point, I decided I wanted to try to make it simpler/better. That plan is this route, Plan C.
Since Plan B provides a lot of learning potential for someone learning Rust, I decided to leave it in place, even though it has not reached maturation. You can follow that path by clicking on the link above. But I'm hoping/planning that this Plan C willl produce a better product.
Let's start by dividing the "d51.rs" file into two pieces, one for the ASCII-art image bits, and one for the drawing code. Let's rename the file:
$ mv src/d51.rs src/drawing.rs
Then create a new file named "d51.txt", and copy the entire function named "get_d51()" out of "drawing.rs" and into the newly-created "d51.txt". You should now no longer have a "d51.rs" file, and you should have the following two new files:
You should also have "main.rs" and "parsing.rs", as listed here (same link as at the top of this page).
Let's clean up "d51.txt" first. Edit the file to look like this:
Much simpler. That's probably the best way of creating the ASCII-art image; it has the quotation marks so that the author can see the boundaries of his/her drawing, but it has no other clutter, except for the blank lines separating the six image frames.
Each one of these six image frames are slightly different in the wheel area, so that as each frame is displayed on the screen one after the other in rapid succession, it will create the illusion that the wheels are rotating. You should already understand this process from the first attempt we made at drawing the D51 train, in the Plan A path mentioned earlier.
But the problem with the above "d51.txt" file is that it requires the "d51.txt" file (and all relevant "[image].txt" files) to travel around with the "sl" executable, and if the "[image].txt" file can't be read for any reason, the program fails.
It would be better if we could incorporate the image data into the program in some way during compilation so that the separate text file[s] didn't have to remain available to and travel around with the compiled executable. That's why we originally put the data into a vector of vectors of Strings, accessible via the "get_d51()" function. But that original method added a bit of complexity to the process, especially for the ASCII-artist trying to get all the bits in place within his/her ASCII-art drawing vector.
However, we can use a compromise method, which adds a little complexity to the ASCII-art drawing, but not as much as with the vector of String vectors we used with the D51. With just a plain .txt file like we have now, we'd have to carry the .txt files around with us. With the vector method, the artist has to deal with the complexities of the vector structure. With this compromise method, of we turn the raw imge data into a constant value, there's a little complexity that the ASCII-artist must deal with , bu tnot much, and then the data will already be in the program, and not need to be separate files we have to keep close to the executable. This additional complexity is pretty simple, and looks like the following. Notice the following points:
We've changed the name of the file back to "d51.rs" (not "d51.txt").
The text in the blue highlight is optional, depending on whether or not the author of the image wants to add those comments.
All the double-quotes have been converted to single-quotes, and all of the image has been wrapped up in a single pair of double-quotes, making the whole six patterns one long, single string.
This string is preceded by an "r", which tells the compiler that this is "raw" text, which means we don't have to escape the backslashes with backslashes (no more "\\" like in our original vector of vector of Strings drawing). The single-quotes, like the double-quotes before, are not even necessary; they're just handy for letting the author see the boundaries of his/her drawing.
Note that there's a hidden newline/line-feed/carriage-return/line-break just after the double-quote following the "r", which will be interpreted by the program as a blank line. We'll have to write the program in such a way as to strip out that blank line.
Constant names should always be upper-case - "D51", not "d51".
The constant must be public to be found by other parts of the program.
The constant is a string slice (a &str type), not a String. In fact a const can not be a String-type. (More technically, it's a static string slice (so the declaration actually looks like const D51:&'static str =), but as that's implied for a const, we don't have to explicitly declare it; a static &str is a normal &str except that it lasts for the lifetime of the program, not just for whatever particular sub-piece of the program in which is is initially declared.)
Each frame of the image is still separated by a blank line. If we don't get rid of the blank line just after the double-quote following the "r", the program will interpret that as a separater between frames, and generate a new empty, and unwanted, vector of an empty String at the start of our vector of String vectors.
The const statement, like most Rust statements, ends with a semi-colon.
In that Plan A path, we followed the basic outline of the C-version of the "sl" program, and created what in Rust-speak is known as a vector of Strings within a vector, or a vector of vector of Strings, or a vector of String vectors, based on the ASCII-artists creating artwork that is pre-configured as such type of variable. This time, we'll do that again, but instead of having the ASCII-artist create the vector of String vectors format, we'll have the artist create this simpler constant which holds the data as one long &str string, and let the program do the necessary conversion.
If we were to look inside of our new constant, we'd see this, representing the first three lines of the "d51.text" file:
Let's setup a test-print in "main.rs" to see if we're on the right track.
If cargo run displays the six frames, and it should, then we're on the right track.
You'll notice that the single-quote marks get included in the string (which we'll want to strip out, but we wanted them in the "D51" constant so that the ASCII-artist could see the boundaries of his/her artwork), and there is an unseen newline character ("\n") at the end of each line, which create the line-breaks at the end of each line in the "D51" const. There's also an unseen newline at the very beginning of this string, which will be intepreted by the program as an unwanted empty frame if we don't deal with it; we'll deal with it shortly.
To see the string in a non-formatted way, use the debug formatter:
Run the program now and you'll basically see the one long string that is the constant, including the "\n" newlines that represent line-breaks.
Let's re-enable the parsing and make sure that part works:
If you specify a kind that is not the "d51', say, with something like cargo run -- --kind=c51, you'll get the "You entered..." message. But if you specify the "d51" or you specify no kind, or a kind we haven't specified in our program, then you'll see the six frames. Everything seems to be working so far.
Notice, though, that since the "parse_opts()" function is only returning the kind, "main()" doesn't yet know the image it needs to draw; it has to have some sort of test, such as the "if" statement we're currently using, or a "match" statement similar to what we already have in "parse_opts()", to determine which image to draw. So if an ASCII-artist were to create a new ASCII-art image, that artist would need to make changes to two match sections, in two different parts of the program. That increases the risk of human-error, and duplicates code. So rather than returning only the kind from "parse_opts()", let's take advantage of that function's match section to also return the image string as well. We'll tackle that task in the next lesson, Getting the Correct Image From the Specified Kind.