99 problems to understand Rust — Part 1 - 17/01/2024
what could you learn by building a CLI tool to find GCD of integers?
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
[
](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--------------------------------)
·
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:
- Anatomy of Rust program
- Declarative style programming
- Basic syntax of Rust programs + Recursion
- Working with runtime environment
- JSON deserialization with serde
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.
- Most of 99 problems
- Common sorting, searching algorithms
- Spicy math
- Backend stuff with Frameworks and libraries
That should do it. And the rules are:
- Embrace the language as much as possible (use standard library)
- Functional for core logic
- Procedural for external environment interactions
- F*ck Clean Code, get the job done first
- God No OOP (except Rust’s traits which are static polymorphism)
- Scavenge the internet after 3 tries
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:
- Find G.C.D. of given 2 positive integers (hard coded)
- takes input from command line as text and then array of inputs
- input as JSON string — let’s learn some (de)serialization
- how to use third-party library for better CLI tools
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…
- function definition
fn <name of the function>(arg: <type of arg>)
- then how to indicate the return type
...) -> <Return type>
- in rust, lines are expressions by default
- but becomes statement when
;
is prepended - rust types: Rust types Explained like you are 5 by ChatGPT
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:
- define the schema as a Struct
- decorate schema with attributes needed
- 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: