Rust Notes
Welcome to my Rust learning journey! These are comprehensive notes I've taken while working through the Rust Book and exploring Rust concepts.
Table of Contents
Chapter 1: Getting Started
Introduction to Rust
Rust is one of the most beloved programming language out there by developers. It's more about empowerment than just putting it into the category of yet another programming language to learn. We can talk about and I could probably write over five thousands of words why rust is an amazing programming language but that's not what we're here for. So let's get started.
I guess the very first step will always have to be the installation:
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
This commands starts a scripts and start installation of rustup
which later installs the latest version of rust. If you wanna see the version, then you'll need to rusn $ rustc --version
and if you wanna update rustup update
to update.
Let's write some rust to get our hands on:
The most basic step of learning a programming language is to write "hello world"! Let's just do that:
fn main() {
println!("Hello, world!");
}
The fn main () {}
defines a function named main
, and this is a special function because it runs first on every executable rust program. But it has no parameter so it returns nothing, if it had parameters it would have gone inside ()
these parenthesis.
Then the function body is wrapped in {}
these curly braces,and rust requires curly braces all around function bodies. The body of the main
function holds println!("Hello, world!");
.
This little line does all the work to print the text "Hello, world!" to the screen. The println!
calls a rust macro, if it had called a function instead then we would write println
without the !
.
Rust macros are a powerful metaprogramming feature which lets you write code that generates other code at compile time. We will explore more on macros later.
To compile the rust code you can write rustc [main.rs](http://main.rs)
, here main.rs
is the file name.
Cargo?
Cargo is a built in system and package manager in rust. Cargo does a lot of tasks such as building your code, downloading necessary librariesa dn building those. So far we'v written simple rust program without any dependancies but if we had some dependancies then we would've used cargo. You can run cargo --version
to see cargo version.
Let's create a new project with cargo
by running:
cargo new hello_rust
we'll see something like this on the terminal:
Creating binary (application) `hello_rust` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
now lets go into the new direcory cd hello rust
, the file structure would look something like this.
HELLO_RUST/
├── src/
│ └── (source files)
├── target/
│ └── (compiled binaries and build artifacts)
├── .gitignore
├── Cargo.lock
└── Cargo.toml
Now let's try to understand what's so different than th old hello world program we wrote before.
Let's build the project and check:
cargo build
Compiling hello_rust v0.1.0 (/Rust_Projects/hello_rust)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s
This creates an executable file in target/debug/hello_rust
rather than in your current directory. Because the default build is debug build and cargo puts the binary in the directory named debug. If you cargo run
it compiles and execute and shows the result. It's simpler cause you dont need to remember the binary path to run the executable and get result.
cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/hello_rust`
Hello, world!
There's also another command cargo check
which checks your code and make sure that it compiles but doesn't really produce any executables.
Chapter 2: Programming a Guessing Game
Guessing Game?
Let's program the simple guessing game from the Rust Book.
The very first version:
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
It just takes an input from user and processes that. Let's break it down step by step use std::io;
this bring io
from rust standard library, this is for input/outputs.
Then the another new concept let mut guess = String::new();
, now over here let
creates a new variable named guess
and mut
makes the variable mutable or changeable, it's important because by default rust variables are immutable. String::new()
creates a new, empty String object.
Now io::stdin()
calls the stdin()
funcation from io
module which then returns a handle to the standard input stream like keyboard input.
.read_line()
is a method that belongs to the standard input system and it reads a line of texts from the user mostly everything until user inputs Enter from their keyboard. Now &
or this ampersand means we are creating a reference variable. Which means we are lending the mut guess
variable to rust temporarily and this is called borrowing
in rust. Because the function can use the variable but doesn't take ownership of it.
And over here .expect()
handles potential errors if reading doesn't work.
But this one is too simple, let's make the guessing game a bit more challenging:
use std::cmp::Ordering;
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
use std::cmp::Ordering;
brings the Ordering
enum into scope so that we can use this for comparing numbers or the result of comparing two values.
And rand::Rng;
provides methods to generate random numbers.
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
Over here secret_number
is immutable variable as we don't need to change it. And rand::thread_rng()
creates a thread-local random number generator and .gen_range(1..=100);
suggests the range which is 1-100.
loop {}
continues until conditions are met or explicitly broken so this also allows multiple guesses.
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
There's multiple fun things happening over here. We are creating a new variable called guess
that's a different type u32
than the original string because we can't compare a string with the number and we need a number to compare the results. This is called shadowing.
Then with guess.trim().parse()
, here guess.trim()
removes the whitespace so like user typed "42\n" but with this it will be "42" and .parse
converts it to a number.
Then we have match
which just handles both of the outcomes Ok(num) => num
if parsing worked, use the number and Err(_) => continue
- if parsing failed, restart the loop.
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
Over here cmp()
method compares two numbers and returns an Ordering
enum. It handles three possible comparison outcomes. Ordering::Less
when guess is smaller than the secret number, then there's also greater one and equal so when it matches and becomes equal then it breaks the loop.
Finally we just finished our guessing game.