Failed Algorithm
A Personal Blog by Gage Schaffer

Learning Rust the Hard Way, Part 1 - Recreating the touch Linux Command

Let's jump right in.

Rust is

The whole point is to make durable programs and catch mistakes before they're compiled. It's pretty nifty. My first forays into C were filled with leaks, so this part hits home.

The touch Command

The touch command simple creates a file or updates the "Last Modified" timestamp in the file's metadata. It's an easy command that gets a lot of use. In fact, I find myself missing it when using Windows systems, so let's recreate it.

One important thing to note is that we are skipping the updating of metadata in this version of the program. So, if you touch a file that already exists, we're just going to crash the program. That functionality will come later.

Getting Arguments

The normal touch command takes one argument: the file to be created.

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let args_container_length = args.len() as i32;

First, we grab the arguments using the cool, built-in method for collecting arguments in a Vector. That's this line here:

let args: Vec<String> = env::args().collect(); 

Afterwards, we store the length. Typically, lengths are the datatype usize, and Rust has a strict type checking system, so we'll cast the length is a 32-bit signed int for input validation later.

Validating Input

We want to make sure that the user only puts in one argument. We can do this by comparing the length of args vector to our desired number of arguments.

One thing to note is that one argument is always passed when running a program, which is the file path of the program itself.

This means that, while we want one argument when using our new touch command, we will need to check for the args vector to have a length of 2:

We'll accomplish this by using the Ordering library in the standard library.

use std::env;
use std::cmp::Ordering;

    -snip-

    // Validating input
    match 2.cmp(&args_container_length) {
        Ordering::Less => panic!("Too many arguments. Correct Usage: touch test.txt"),
        Ordering::Greater => panic!("Too Few Arguments. Correct Usage: touch test.txt"),
        Ordering::Equal => (),
    }

We do a match statement, comparing the results of comparing 2 against the args vector. If the number of arguments is too low or too great, we use the panic! macro to crash the program and give the user a simple error message.

Storing the Target File Path for Easier Use

Now, we have our input validated and ready to move on to the actual functionality. We'll store our target file path in a variable for easier use.

let target = &args[1];

We set the target variable to the value located at the 2nd position in the args vector, which should be our file path to be created.

Checking if the File Already Exists

Remember, we are skipping the functionality of touch that updates the file's "Last Updated" piece of metadata, so we need to check if the file already exists.

We can use the built-in Path library to do this. It's simple and elegant.

use std::env;
use std::path::Path;
use std::cmp::Ordering;

    -snip-

// Checks if a file already exists.
    if Path::new(target).exists(){
        panic!("File Already Exists.")
    }

Create a new path using the target variable that we created earlier, and call the exists() function. This function returns a boolean, which, if it's true, we should panic! and crash the program.

Creating the File

This part is a continuation on the if statement we created above. If we find that the file does not exist, let's move onto creating the file.

use std::env;
use std::path::Path;
use std::fs::File;
use std::cmp::Ordering;

    -snip-

    else{
            match File::create(target) {
                Ok(_file) => return,
                Err(e) => panic!("Could not create file. Error: {e:?}"),
            }
        }

We call the create() function from the File library, which returns a Results object that we need to deal with. We use a match statement to do this.

If the Result object returns Ok, we just exit the program with return. If there was an error for some reason, call panic! and print out the error.

Wrapping Up!

That's it! We have created the most basic functionality of the touch command in Rust.

The Full Code

Here's the entire codebase. It's pretty short, all things considered.

use std::env;
use std::path::Path;
use std::fs::File;
use std::cmp::Ordering;

fn main() {
    let args: Vec<String> = env::args().collect();
    let args_container_length = args.len() as i32;

    // Validating input
    match 2.cmp(&args_container_length) {
        Ordering::Less => panic!("Too many arguments. Correct Usage: touch test.txt"),
        Ordering::Greater => panic!("Too Few Arguments. Correct Usage: touch test.txt"),
        Ordering::Equal => (),
    }

    let target = &args[1];

    // Checks if a file already exists.
    if Path::new(target).exists(){
        println!("File Already Exists.")
    }
    else{
        match File::create(target) {
            Ok(_file) => return,
            Err(e) => panic!("Could not create file. Error: {e:?}"),
        }
    }
}