My first Rust program

Hello and thanks for checking out this post.

This is my first program in Rust and is simply me experimenting with the language and getting to understand it a little.

Why Rust?

So this is a fascination that goes back several years when I first heard the name and I was getting back into tech, unfortunately at the time I lacked the knowledge around tech and computers I have now to understand what Rust was all about and merely liked the cool name and Logo.

However I'm coming back into it with the same curiosity but with several years development experience and expertise, it's now that it really piques my curiosity in what it's trying to achieve. Apart from the little technical thing, it's been consistently rated the most loved language on the stackoverflow developer survey, so what's the hype all about?

Now I'm intriuged and want to know what I might be able to do with this language.

What is Rust?

Rust is a "systems" programing language, it is designed to be inherintely secure and memory safe.

One description that I like is it's like C/C++ with guide rails.

Now I'm no C/C++ wizkid, the last C++ I wrote was probably some simple Arduino loop, however prior to that it was years ago when I was relearning how to code, C++ is a language that grants "low down" access to the nuts and bolts of the hardware running a piece of software. For this reason it can be written to be specific and fast and is the staple language used in triple A gaming, but one of it's biggest drawbacks is this low level access means the programmer is required to think about things like memory management, and if done incorreclty can lead to a wealth of headaches.

It's this that Rust aims to solve, speed and low lever access but wth better memory practices including the need for a "GC" (garbage collector) which is a small piece of code responsible for cleaning up the use of memory by the software.

Rust aims to do way with that, it has no GC and instead works on the principle of "ownership" (often cited as the hardest learning curve in Rust, so if my explanation is wrong then please let me know) which is a concept in which declared variables only exist aslong as the variable is in scope.

Variables can borrow values, but once the owner goes out of scope, the value is disposed and is no longer accessible to the rest of the software.

It's this concept that means there is no need for a GC as all variables and therefore memory allocation is handled as soon as a variable goes out of scope. I believe from some podcasts that this also lends itself to better performance as there is no GC latency.

My first program

Necessity is the mother of invention.

In this case it was the excuse I needed to use Rust. My beer blog was starting to load slow on the front end, and I had realised that I was loading images at the full massive resolution in JPEG format, which in turn meant that loading the list took a while and I was unecissarily punishing my audience with large images.

I needed to resize them and I also believed that a PNG was better on the web.

Hence I started to look at image manipulation in Rust.

The problem was two small problems, File IO and image manipulation, after some google I came up with these two posts

So the result?


use image;
use image::GenericImageView;
use std::error::Error;
use glob::glob;

fn main() {
    let mut done = false;

    while !done {
        println!("Shrinking the files... \n");
        resize_images_in_directory();
        done = true;
    }

    println!("Complete");
}

fn resize_images_in_directory() ->  Result<(), Box<dyn Error>> {

    for entry in glob("./test_images/input/*.jpg")? {
        
        let file = format!(".\\{}", entry?.display());

        let split_dir = file.split('\\');
        
        let vec: Vec<&str> = split_dir.collect();

        let file_split = vec[3].split('.');

        let file_vec: Vec<&str> =  file_split.collect();

        let file_name = file_vec[0];

        let img = image::open(&file)?;

        let (width, height) = img.dimensions();

        let small_img = img.resize(width/10, height/10, image::imageops::FilterType::Lanczos3);

        let save_file_name = format!(".\\test_images\\output\\{}.png", file_name);

        small_img.save(save_file_name)?;
    }

    Ok(())
}

What's going on

So I'm going to attempt to explain my rough understanding through a couple of interesting things.

First up the line let mut done = false; the let mut is the important part, here we are declaring a variable that is mutable i.e can be changed later in the software, this is another key part of Rust, in that variables are inherently immutable unless stated explicitly.

Next we will look at the function resize_images_in_directory here the first thing to note is the -> Result<(), Box<dyn Error>> this essentially describes the two possible return types of the function a standard "empty" result or the "Boxed" dynamic error, I won't pretend to know enough about Rust at this point to go into depth on this, but I do know that Rust does not support null as it avoids a null reference exception, instead Rust returns a given type.

There is more on this concept and it's use with Rust's Options here.

Currently in my stage of learning Rust I simply accept I have to return Ok if it's void, along with a possible error in the Box<dyn Error> call.

That part of the code in iteslf is interesting, Boxing in Rust is placing the data into the heap as opposed to on the stack (which Rust does by default) here the Boxing takes place on a dynamic error (it could be a variety of Error types, the specific one obsiouly not being known at compile time) and the call to Box simply returns a pointer to the heap at run time, that once accessed disposes of the data from the heap, creating that memory safety in return.

phew

So there is a lot that goes on in those first few lines.

The rest of the function is building up some data for saving the same file names as well as opening up new image data objects.

A couple of interesting can be seen in the following

let file = format!(".\\{}", entry?.display()); is when the function attempts to read the file. At this point it should have an applicable file, but given the nature it might not, so entry has the ? after it, which returns either the Ok and therefore allows access to the display method, or it doesn't and it throws (panics in Rust) error back up.

The format! is also intersting, it's what is known as a macro in Rust which can be throught of as predefined shorthand methods that do certain things, in this case, return a string in the needed format.

The next interesting line (IMO) is let vec: Vec<&str> = split_dir.collect(); the left hand delaration let vec: is interesting. We aren't assigning vec to a given result or type the : is special in rust as it denotes an annotated varaible declaration, which specifies the type expected in the let statement.

In other words, the variable vec will assume the type of Vec<&str> which is returned from the split_dir.collect() method.

The only other notable piece of code is the line let img = image::open(&file)?; here we are calling the static open method from the image type. It's simply creating a new instace of an image type with all the properties associated from the file obejct being passed in. Or, an image with the meta associated to the file reference that came before.

Which allows for the final steps of resizing the image, along with the save on the small_img variable.

Conclusion

So to conclude I hope this program gives some indication on some of the inner workings of Rust, it's certainly not a deep dive, but shows some of the languages' capability.

I loved writing this, medling two differnet examples together to suit my needs and the learning along the way, I'm sure it could be done much more efficiently, but it works for now.

I'm looking forward to the next step / adventure!