Convert "sl" Source Code from C to Rust

Return to "Draw A Train".

Alternative Ways to Read the Train Image

We'll look at three different methods of getting the train (or any) image into our program, and discuss the pros and cons of each. You're welcome to try any of this coding (perhaps in a new temporary project), but it's not part of our "sl" project; it's just looking at possible ways we want to move forward.

Pre-Define the Train as a Vector of String Vectors

This is the method we've been using. For completeness, we'll show this method, abbreviated, here. The "main()" function calls two subfunctions; the first one, "get_d51()", reads the vector and assigns (or binds) the variable name "image_vecvec" to the vector, and then that variable (or binding) is fed to the second subfunction which displays the image to the screen.

Pre-Defined Vector of String Vectors - main.rs
fn main() {
    let image_vecvec = get_d51();
    draw_image(image_vecvec);
} // end of main()



fn draw_image(image_vecvec: Vec<Vec<String>>) {
    for each_vec in image_vecvec {
        for each_line in each_vec {
            println!("{}", each_line);
        }
    }
} // end of draw_image()



fn get_d51() -> Vec<Vec<String>> {
// The first three frames below use double-backslashes for an escaped backslash, rather than the "raw" mode method,
// so you can compare it against the "raw" mode method used with the last three frames.
  let d51: Vec<Vec<String>> = vec![
    vec![
        "      ====        ________                ___________ ".to_string(),
        "  _D _|  |_______/        \\__I_I_____===__|_________| ".to_string(),
        "   |(_)---  |   H\\________/ |   |        =|___ ___|   ".to_string(),
        "   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        "  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        "  | ________|___H__/__|_____/[][]~\\_______|       |   ".to_string(),
        "  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        "__/ =| o |=-~~\\  /~~\\  /~~\\  /~~\\ ____Y___________|__ ".to_string(),
        " |/-=|___|=    ||    ||    ||    |_____/~\\___/        ".to_string(),
        "  \\_/      \\O=====O=====O=====O_/      \\_/            ".to_string()
      ],
        
      vec![
        "      ====        ________                ___________ ".to_string(),
        "  _D _|  |_______/        \\__I_I_____===__|_________| ".to_string(),
        "   |(_)---  |   H\\________/ |   |        =|___ ___|   ".to_string(),
        "   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        "  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        "  | ________|___H__/__|_____/[][]~\\_______|       |   ".to_string(),
        "  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        "__/ =| o |=-~~\\  /~~\\  /~~\\  /~~\\ ____Y___________|__ ".to_string(),
        " |/-=|___|=O=====O=====O=====O   |_____/~\\___/        ".to_string(),
        "  \\_/      \\__/  \\__/  \\__/  \\__/      \\_/            ".to_string()
      ],
  
      vec![
        "      ====        ________                ___________ ".to_string(),
        "  _D _|  |_______/        \\__I_I_____===__|_________| ".to_string(),
        "   |(_)---  |   H\\________/ |   |        =|___ ___|   ".to_string(),
        "   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        "  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        "  | ________|___H__/__|_____/[][]~\\_______|       |   ".to_string(),
        "  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        "__/ =| o |=-O=====O=====O=====O \\ ____Y___________|__ ".to_string(),
        " |/-=|___|=    ||    ||    ||    |_____/~\\___/        ".to_string(),
        "  \\_/      \\__/  \\__/  \\__/  \\__/      \\_/            ".to_string()
      ],
  
      vec![
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~O=====O=====O=====O\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=    ||    ||    ||    |_____/~\___/        ".to_string(),
        r"  \_/      \__/  \__/  \__/  \__/      \_/            ".to_string()
      ],
  
      vec![
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=   O=====O=====O=====O|_____/~\___/        ".to_string(),
        r"  \_/      \__/  \__/  \__/  \__/      \_/            ".to_string()
      ],
  
      vec![
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=    ||    ||    ||    |_____/~\___/        ".to_string(),
        r"  \_/      \_O=====O=====O=====O/      \_/            ".to_string()
      ],
    ];

    return d51;
} // end of get_d51()

Running this program should display all six of the image frames.

The biggest advantage of this method is that it makes it easier to program the rest of the program, because the image frames are pre-defined in the packaging in which we want them.

The biggest disadvantage is that it puts the burden on the ASCII-artist to get the packaging just right. As long as no future ASCII-artist wants to add a new image, this may not be much of a disadvantage. But if a future artist wants to add an image, say, a flying cow, that artist will need to make sure all the T's are crossed and all the "I"s are dotted.

Define the Train as a constant and Convert it To a Vector of String Vectors

This is probably the compromise method, as it's considerably easier on the ASCII-artist to provide a working image, but it requires more work on the part of our program to prep it for use by the rest of the program. It looks like this:

A constant That is Then Converted to a Vector of String Vectors - main.rs
fn main() {
    let image_vecvec = get_d51();
    draw_image(image_vecvec);
} // end of main()



fn draw_image(image_vecvec: Vec<Vec<String>>) {
    for each_vec in image_vecvec {
        for each_line in each_vec {
            println!("{}", each_line);
        }
    }
} // end of draw_image()



fn get_d51() -> Vec<Vec<String>> {
    // Our string is stored in the constant "D51".

    //The first character in the string, after the 'r"', is a newline; lose it.
    let image_string = &D51[1..D51.len()];

    // Create new blank vector of vector of Strings.
    let mut outer_vec: Vec<Vec<String>> = Vec::new();

    // This keeps track of which frame/inner vector we're processing.
    let mut inner_vec_num: usize = 0;

    // Create first inner vector in the outer vector.
    outer_vec.push(Vec::new());

    for mut each_line in image_string.lines() {
        if each_line != "" {
            // If a blank line is not found ...
            // ... remove single-quote mark at end of each line,
            if &each_line[each_line.len() - 1..] == "'" {
                each_line = &each_line[0..each_line.len() - 1];
            }
            // ... and then add the string to the current inner vector.
            outer_vec[inner_vec_num].push(each_line.to_string());
        } else {
            // If a blank line is found...
            // ... we're done with the current vector, so create a new inner vector...
            outer_vec.push(Vec::new());
            // ... and increase the count of vectors by one.            
            inner_vec_num += 1;
        }
    }
    // Return the finished vector of String vectors.
    outer_vec
} // end of get_d51()


// Notice I put single-quotes at the end of every other frame, just to eyeball where is the end of the line. With our code here, they are not necessary.
// But it'd be more consistent to include them (or not), on all the frames; this just demonstrates that our program can handle it either way.
// Also notice there must be a blank line between frames (not even white-space allowed on these lines). If you need a "blank" line in the frame itself,
// include some white-space on the line. The first line of the first frame determines the length of the image, so if you wanted that line to be a blank
// line, it needs to be filled with spaces to the full length of the image.
//
// Note that unlike the vector method used previously, which has ten Strings per frame, times six frames, for a total of 60 Strings, this method has
// only one string (of the &str type, rather than the String type), which is prefixed by a single "r" to cause the compiler to read the one string
// in "raw" mode, so we don't have to escape our backslashes. Our program then chops this one string up into the 60 Strings needed to convert it into
// the vector of vectors of Strings which we need.
const D51: &str = r"
      ====        ________                ___________
  _D _|  |_______/        \__I_I_____===__|_________|
   |(_)---  |   H\________/ |   |        =|___ ___|  
   /     |  |   H  |  |     |   |         ||_| |_||  
  |      |  |   H  |__--------------------| [___] |  
  | ________|___H__/__|_____/[][]~\_______|       |  
  |/ |   |-----------I_____I [][] []  D   |=======|__
__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__
 |/-=|___|=    ||    ||    ||    |_____/~\___/       
  \_/      \O=====O=====O=====O_/      \_/           

      ====        ________                ___________'
  _D _|  |_______/        \__I_I_____===__|_________|'
   |(_)---  |   H\________/ |   |        =|___ ___|  '
   /     |  |   H  |  |     |   |         ||_| |_||  '
  |      |  |   H  |__--------------------| [___] |  '
  | ________|___H__/__|_____/[][]~\_______|       |  '
  |/ |   |-----------I_____I [][] []  D   |=======|__'
__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__'
 |/-=|___|=O=====O=====O=====O   |_____/~\___/       '
  \_/      \__/  \__/  \__/  \__/      \_/           '

      ====        ________                ___________
  _D _|  |_______/        \__I_I_____===__|_________|
   |(_)---  |   H\________/ |   |        =|___ ___|  
   /     |  |   H  |  |     |   |         ||_| |_||  
  |      |  |   H  |__--------------------| [___] |  
  | ________|___H__/__|_____/[][]~\_______|       |  
  |/ |   |-----------I_____I [][] []  D   |=======|__
__/ =| o |=-O=====O=====O=====O \ ____Y___________|__
 |/-=|___|=    ||    ||    ||    |_____/~\___/       
  \_/      \__/  \__/  \__/  \__/      \_/           

      ====        ________                ___________'
  _D _|  |_______/        \__I_I_____===__|_________|'
   |(_)---  |   H\________/ |   |        =|___ ___|  '
   /     |  |   H  |  |     |   |         ||_| |_||  '
  |      |  |   H  |__--------------------| [___] |  '
  | ________|___H__/__|_____/[][]~\_______|       |  '
  |/ |   |-----------I_____I [][] []  D   |=======|__'
__/ =| o |=-~O=====O=====O=====O\ ____Y___________|__'
 |/-=|___|=    ||    ||    ||    |_____/~\___/       '
  \_/      \__/  \__/  \__/  \__/      \_/           '

      ====        ________                ___________
  _D _|  |_______/        \__I_I_____===__|_________|
   |(_)---  |   H\________/ |   |        =|___ ___|  
   /     |  |   H  |  |     |   |         ||_| |_||  
  |      |  |   H  |__--------------------| [___] |  
  | ________|___H__/__|_____/[][]~\_______|       |  
  |/ |   |-----------I_____I [][] []  D   |=======|__
__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__
 |/-=|___|=   O=====O=====O=====O|_____/~\___/       
  \_/      \__/  \__/  \__/  \__/      \_/           

      ====        ________                ___________'
  _D _|  |_______/        \__I_I_____===__|_________|'
   |(_)---  |   H\________/ |   |        =|___ ___|  '
   /     |  |   H  |  |     |   |         ||_| |_||  '
  |      |  |   H  |__--------------------| [___] |  '
  | ________|___H__/__|_____/[][]~\_______|       |  '
  |/ |   |-----------I_____I [][] []  D   |=======|__'
__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__'
 |/-=|___|=    ||    ||    ||    |_____/~\___/       '
  \_/      \_O=====O=====O=====O/      \_/           '
";

Notice that the constant definition is just "out in the open" of the "main.rs" file, not part of any function. This makes it "globally" accessible to everything in the "main.rs" file.

Note that the name of a constant should be in upper-case ("D51" vs "d51").

Note also that the string is a slice rather than a String.

Note that there's an "r" just before the opening quote of the string, which means the characters are to be read in "raw" mode, meaning that a backslash will be read as a backslash rather than an introduction to a control character. In other words, the text sequence of "\n" would be read as a backslash followed by a lower-case "n", instead of being read as a newline character.

Finally, the single-quotes at the end of each line are optional; I have demonstrated that by leaving them off on every other frame. They are there only so that the ASCII-artist knows where is the right-edge of the art (because ideally each line will be of equal length with the others; at a minimum, the first line needs to be the longest if the others are not equal to it, because that's the line we will read to get the overall length of the train.

When the compiler ("cargo") is compiling the program, any place it sees the constant "D51", that constant will be "replaced" with the actual contents of the constant.

The function "get_d51()" converts the constant (a string slice) into a vector of String vectors. The first thing it does is it removes the newline after the r"; if that newline is kept, it will be interpreted as a blank line further down in the function, and will begin a new, second, vector, leaving a first, empty vector. The next thing it does is to create a new blank inner vector within the outer vector. It then creates a counter, to keep count of which vector we're working with. The first vector is the zero'th vector. Then the function cycles through each line of the entire constant/string, and if it does not find a blank line, it adds that line to the current vector it's building. If it does see a blank line, it creates a new vector, and then proceeds on to the next cycle to read the next line, and so on.

Read the Train from a Text File and Convert it To a Vector of String Vectors

This method puts the train image definition in a file, named "d51.txt". There can be nothing else in this file; no comments, no function declarations, no use statements, etc. The program reads the entire contents of the file into a single string, and then processes it in a similar manner to how the constant is processed above.

I have also included the contents of a "coalcar.txt" file and a "jack.txt" file. To use these, you can simply change the one line - let image_str = include_str!("d51.txt");, although ideally you'd also change the name of the function and its call, and the comments, etc, to reflect the change.

Image in Separate File

main.rs
fn main() {
    let image_vecvec = get_d51();
    draw_image(image_vecvec);
} // end of main()

fn get_d51() -> Vec<Vec<String>> {
    // Our string is stored in the file "d51.txt".
    let image_str = include_str!("d51.txt");

    //The first character in the string, after the 'r"', is a newline; lose it.
    let image_string = &image_str[1..image_str.len()];

    // Create new blank vector of vector of Strings.
    let mut outer_vec: Vec<Vec<String>> = Vec::new();

    // This keeps track of which frame/inner vector we're processing.
    let mut inner_vec_num: usize = 0;

    // Create first inner vector in the outer vector.
    outer_vec.push(Vec::new());

    for mut each_line in image_string.lines() {
        if each_line != "" {
            // If a blank line is not found ...
            // ... remove single-quote mark at end of each line,
            if &each_line[each_line.len() - 1..] == "'" {
                each_line = &each_line[0..each_line.len() - 1];
            }
            // ... and then add the string to the current inner vector.
            outer_vec[inner_vec_num].push(each_line.to_string());
        } else {
            // If a blank line is found...
            // ... we're done with the current vector, so create a new inner vector...
            outer_vec.push(Vec::new());
            // ... and increase the count of vectors by one.            
            inner_vec_num += 1;
        }
    }
    // Return the finished vector of String vectors.
    outer_vec
} // end of get_d51()


fn draw_image(image_vecvec: Vec<Vec<String>>) {
    for each_vec in image_vecvec {
        for each_line in each_vec {
            println!("{}", each_line);
        }
    }
} // end of draw_image()
  
d51.txt
      ====        ________                ___________
  _D _|  |_______/        \__I_I_____===__|_________|
   |(_)---  |   H\________/ |   |        =|___ ___|  
   /     |  |   H  |  |     |   |         ||_| |_||  
  |      |  |   H  |__--------------------| [___] |  
  | ________|___H__/__|_____/[][]~\_______|       |  
  |/ |   |-----------I_____I [][] []  D   |=======|__
__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__
 |/-=|___|=    ||    ||    ||    |_____/~\___/       
  \_/      \O=====O=====O=====O_/      \_/           

      ====        ________                ___________'
  _D _|  |_______/        \__I_I_____===__|_________|'
   |(_)---  |   H\________/ |   |        =|___ ___|  '
   /     |  |   H  |  |     |   |         ||_| |_||  '
  |      |  |   H  |__--------------------| [___] |  '
  | ________|___H__/__|_____/[][]~\_______|       |  '
  |/ |   |-----------I_____I [][] []  D   |=======|__'
__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__'
 |/-=|___|=O=====O=====O=====O   |_____/~\___/       '
  \_/      \__/  \__/  \__/  \__/      \_/           '

      ====        ________                ___________
  _D _|  |_______/        \__I_I_____===__|_________|
   |(_)---  |   H\________/ |   |        =|___ ___|  
   /     |  |   H  |  |     |   |         ||_| |_||  
  |      |  |   H  |__--------------------| [___] |  
  | ________|___H__/__|_____/[][]~\_______|       |  
  |/ |   |-----------I_____I [][] []  D   |=======|__
__/ =| o |=-O=====O=====O=====O \ ____Y___________|__
 |/-=|___|=    ||    ||    ||    |_____/~\___/       
  \_/      \__/  \__/  \__/  \__/      \_/           

      ====        ________                ___________'
  _D _|  |_______/        \__I_I_____===__|_________|'
   |(_)---  |   H\________/ |   |        =|___ ___|  '
   /     |  |   H  |  |     |   |         ||_| |_||  '
  |      |  |   H  |__--------------------| [___] |  '
  | ________|___H__/__|_____/[][]~\_______|       |  '
  |/ |   |-----------I_____I [][] []  D   |=======|__'
__/ =| o |=-~O=====O=====O=====O\ ____Y___________|__'
 |/-=|___|=    ||    ||    ||    |_____/~\___/       '
  \_/      \__/  \__/  \__/  \__/      \_/           '

      ====        ________                ___________
  _D _|  |_______/        \__I_I_____===__|_________|
   |(_)---  |   H\________/ |   |        =|___ ___|  
   /     |  |   H  |  |     |   |         ||_| |_||  
  |      |  |   H  |__--------------------| [___] |  
  | ________|___H__/__|_____/[][]~\_______|       |  
  |/ |   |-----------I_____I [][] []  D   |=======|__
__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__
 |/-=|___|=   O=====O=====O=====O|_____/~\___/       
  \_/      \__/  \__/  \__/  \__/      \_/           

      ====        ________                ___________'
  _D _|  |_______/        \__I_I_____===__|_________|'
   |(_)---  |   H\________/ |   |        =|___ ___|  '
   /     |  |   H  |  |     |   |         ||_| |_||  '
  |      |  |   H  |__--------------------| [___] |  '
  | ________|___H__/__|_____/[][]~\_______|       |  '
  |/ |   |-----------I_____I [][] []  D   |=======|__'
__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__'
 |/-=|___|=    ||    ||    ||    |_____/~\___/       '
  \_/      \_O=====O=====O=====O/      \_/           '
  
coalcar.txt
                              
                              
                              
    _________________         
   _|                \_____A  
 =|                        |  
 -|                        |  
__|________________________|_ 
|__________________________|_ 
   |_D__D__D_|  |_D__D__D_|   
    \_/   \_/    \_/   \_/    
jack.txt
 \ 0 /   
  \|/    
   |     
  / \    
_/   \_  

         
 __0__   
/  |  \  
  / \    
 _\ /_   

         
   o     
 /\ /\   
 |/ \|   
 _\ /_   

         
 __0__   
/  |  \  
  / \    
 _\ /_   

A disadvantage with this method is that the several separate .txt files have to be distributed with the main executable file, and have to be findable by that executable file. This requires more programming in the program itself, and more care by the program-packager/distributor/installer.

Conclusion

We've looked at three methods for getting the image data into our program:

Each method has its pros and cons, but overall, I think the best solution is the middle option, using constants. Let's put the image constants in a separate file, named "images.rs". This will be somewhat analagous to the "sl.h" file in the original C-version of "sl". Let's Move the Images to a Separate File.