hi@methebe.com

99 problems to understand Rust — Part 1 - 17/01/2024

what could you learn by building a CLI tool to find GCD of integers?

Site under construction! Migrated content might be displayed ugly!

medium-to-markdown@0.0.3 convert node index.js https://medium.com/stackademic/99-problems-to-understand-rust-part-1-53c6899b460e

99 problems to understand Rust — Part 1

[Enigma Bits

](https://medium.com/@birnadin?source=post_page-----53c6899b460e--------------------------------)[![Stackademic](https://miro.medium.com/v2/resize:fill:48:48/1*U-kjsW7IZUobnoy1gAp1UQ.png)

](https://blog.stackademic.com/?source=post_page-----53c6899b460e--------------------------------)

Enigma Bits

·

Follow

Published in[

Stackademic

](https://blog.stackademic.com/?source=post_page-----53c6899b460e--------------------------------)·6 min read·Jan 17, 2024

2

Listen

Share

from unsplash.com

At the end you should be exposed to:

I recently wanted to learn systems engineering, especially rust because coming March I’d be starting my college degree (Embedded Programming major). Though I wanted to bootstrap some software aspects to have a smooth year.

  1. Most of 99 problems
  2. Common sorting, searching algorithms
  3. Spicy math
  4. Backend stuff with Frameworks and libraries

That should do it. And the rules are:

With that being said, let’s dive in.

Finding Great Common Divisor (G.C.D.)

I am starting with this as this will teach us the very basics of a language than any other practical use case. My goal is:

Before you click away as you are bored by the math part, here’s what I have to say from 4 years of freelancing…

“Knowing some Linear Algebra, Statistics, Probability, Sets, Matrices and Calculus will get you ahead of competition and serious bugs” — Birnadin E.

G.C.D. Maximum of integer set which can divide both inputs without any remainder. This is enough for us as we will focus on a particular algorithm, try the brute force method on your own — particularly we will equip Euclidian’s Algorithm.

Above was imperative. I love functional programming so let’s go declarative; recursion to be precise. Following is the final rust function I came up with 👇

fn gcd(m: u64, n: u64) -> u64 {  
    if n == 0 {  
        return m;  
    }  
  
    let r = m % n;  
    gcd(n, r)  
}

Lot to unpack. First things first, rust introduces Ref-counting so to mitigate this headache I chose recursion instead of mutable states, which are for later part of the series.

Rust Functions

Reusable snippet of code. Dead simple. Line by Line analysis…

fn gcd(m: u64, n: u64) -> u64 {

Above line declares a function named gcd. The function takes two parameters, m and n, both of which are unsigned 64-bit integers (u64). The function returns a u64 integer as well.

if n == 0 {  
  return m  
}

👆 is our exit case of recursion (essentially loop). Then the crux…

let r = m % n;

Why this works is beyond scope of learning rust. For that please ask Euclid. Then after we reinvoke the gcd function with new remainder. You should extract…

Should you note that functions are what rust programs in essence. How does an entire program looks like?

fn gcd(m: u64, n: u64) -> u64 {  
    if n == 0 {  
        return m;  
    }  
  
    let r = m % n;  
    gcd(n, r)  
}  
  
fn main() {  
  // let's find this  
  let g = gcd(10,8);  
    
  // then print the result to the stdout  
  println!("{}",g);  
}

there is more to learn about the println!(...) line. Save it up for later. Issuing cargo run will at least print a number. That is enough as long as it is not 2.

it is free of charge by the way

Command line arguments

Unlike C, which rust directly competes against, Rust takes a modern approach to access runtime environment. Rust standard library exposes ::env module for us to work with.

let numbers: Vec<u64> = env::args()  
        .skip(1)  
        .map(|a| u64::from\_str(&a).expect("parsing failed"))  
        .collect();

env::args returns iterator OS passed onto our binary. We skip(1) (one iteration) since traditionally first argument is the name of the binary from the terminal shell.

Then we convert the strings of arguments into u64 typed integers. .map is a function defined onto iterator that will execute the function given to each element of the iterator. Here we pass in a closure that takes an input and does the job.

The job being parsing u64::from_str(<str to decode>); however parsing can be failed. So it returns Result type. What again?

Result. To mitigate the billion $ mistake rust introduces Result type. It is an enumeration with 2 possible values of Ok(T) and Err(E). We can use a utility function expect which would return the enclosing data if the enumeration is Ok or panic with the message given otherwise.

panic at runtime when string is passed as argument

In above picture you can see the message in 3rd line prepended on ParseIntError when I gave the binary invalid argument.

Vector. Next thing you would notice in previous code block is collect method being called. And if you have notice after variable name I annotated Vec<u64>. This converts the iterator into variable length array, Vector. In python this is just list but here we should specify the length of an array at compile time, in case we couldn’t Vec is the answer (mostly).

Putting all Together

All assembled main...

fn main() {  
    // parse the commandline arguments  
    let numbers: Vec<u64> = env::args()  
        .skip(1)  
        .map(|a| u64::from\_str(&a).expect("parsing failed"))  
        .collect();  
  
    // wrong usage, instruct the user  
    if numbers.is\_empty() {  
        eprintln!("Usage: rust-101 arg0 arg1 arg2...");  
        std::process::exit(1);  
    }  
  
    // find GCD of given commandline arguments in order given  
    let d = numbers.into\_iter().reduce(gcd).unwrap\_or(0);  
  
    println!("{}", d);  
}

In case anything above is unclear, let me know in the comments.

JSON in Rust

As of now our input is like 2003 5 19 which is kinda ok but a standardized input format would be good. Let’s go against POSIX philosophy and migrate our text interface to JSON interface. A like the scheme of

{  
  "ns": \[2003, 5, 19, 140\]  
}

With that being said we will use serde library which is widely used and became a standard I guess. Run following terminal shell commands to add third dependencies to our project.

cargo add serde --features derive  
cargo add serde\_json

The workflow with serde:

  1. define the schema as a Struct
  2. decorate schema with attributes needed
  3. use the Struct

Use the What you asked? Struct. Rust appreciates both functional and procedural paradigm. Struct is its answer to CPP’s Class. You can encapsulate different types into a construct and define functions on it.

use serde::Deserialize;  
  
#\[derive(Deserialize)\]  
struct Args {  
    // we name the struct as Args, this is purely cosmetic!  
    // this should resemble the schema;   
    // the type annotation will be useful.  
    ns: Vec<u64>,  
}

Then we can deserialize the string given and carry on.

fn main() {  
    let args: Vec<String> = env::args().skip(1).collect();  
    let args: Args = serde\_json::from\_str(&args.first().expect())  
                        .expect("Input format Invalid");  
    let numbers = args.ns;  
    // ... from here nothing changed  
}

There you have it. If you enjoyed and don’t want to miss the upcoming posts make sure you will be notified. More exciting stuff is coming your way.

Conclusion

In this comprehensive exploration, we delved into the intricate anatomy of a Rust program, unraveling its unique blend of declarative style. Our adventure extended into a crucial aspect of JSON deserialization using the powerful serde library, a vital skill in modern software development.

As we continue to navigate the landscape of Rust programming, I invite you to join me in this journey of discovery and innovation. Follow me for more insights and in-depth explorations into the world of Rust and beyond.

Till next time, this is meTheBE signing off 👋.

Stackademic

Thank you for reading until the end. Before you go: