3 min read

Rust Book: Chapter 3 - Control flow

💡
I recently picked up Rust, and content of this page is meant to be my personal study notes.

Learned about variables, scalar and compound data types, functions, comments, if expressions, and loops.

Variables

Variables are immutable by default in rust. Once a variable is bound to a value, you can't change it, unless you explicitly declare your intent ahead of time using the mut keyword at the time of the variable declaration.

fn main() {
    let x = 5; //x is immutable
    x = 6; // this line causes program to fail to compile
    println!("value of x is: {x}")
}

Constants

Constants are always immutable, declared using the keyword const, and the type of the value must be annotated.

const SECONDS_IN_DAY: u32 = 60 * 60 * 24;

Shadowing

Rust allows to declare a new variable with the same name as a previously declared variable. When we do this, we say that the second has shadowed the first.

fn main() {
    let x = 5;

    let x = x + 1; //x shadows the previous value of x

    println!("The value of x is: {x}");
}

Data Types

There are 2 data types in rust: scalar and compound. A scalar type represents a single value. Rust has 4 primary scalar types: integers, floating-point numbers, Booleans, characters.

Integer types

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

Each signed variant can store numbers from -(2n-1) to 2n-1 - 1 inclusive, with n being the number of bits that variant uses. Unsigned variants can store numbers from 0 to 2n-1. isize and usize types depend on the architecture of the computer the program runs on: 64 bits if running 64-bit architecture or 32 if 32-bit architecture.

Integer literals can be written in Decimal, Hex, Octal, Binary, Byte (u8 only)

Floating-Point Types

f32 and f64 are Rust's floating-point types. The default is f64. All floating points are signed.

The Boolean Type

Boolean type in Rust is specified using `bool`.

fn main() {
    let t = true;
    let f: bool = false; //explicit type annotation
}

Compound Types

Tuples and Arrays are Rust's compound types.

A tuple is a general way of grouping together a number of values with a variety of types into one compound type. Tuples have fixed length: once created, cannot shrink or grow in size. We create a tuple by writing a comma-separated list of values inside parenthesis. Each position in a tuple has a type, and the types of the different values in the tuple don't have to be the same.

💡
A tuple without any values has a special name, unit. The value and its corresponding type are both written () and represent an empty value or an empty return type.
fn main() {
    let tup: (i32,, f64, bool) = (500,, 6.4, true);
}

Array

Arrays have fixed length in Rust, and contain elements of the same type.

let a: [i32;5] = [1,2,3,4,5];

Arrays can also be initialised by specifying the initial value, followed by a semicolon

let a = [3;5]; // a will contain values like so: [3, 3, 3, 3, 3]

Practice

At the end of the Chapter, There are 3 practice programs to test knowledge:

  1. Convert temperatures between Fahrenheit and Celsius.
  2. Generate the nth Fibonacci number.
  3. Print the lyrics to the Christmas carol “The Twelve Days of Christmas,” taking advantage of the repetition in the song.

I plan to attach all 3 here this week. But since I have written the first, here you go 😊

Convert Temperatures between Fahrenheit and Celsius

/// Program to convert temperatures between Fahrenheit and Celsius
use std::io;

fn main() {
    let target_scale: u8 = request_target_scale();
    let reading = request_temperature_to_convert();
    match target_scale {
        1 => handle_celsius_to_fahrenheit_conversion(reading),
        2 => handle_fahrenheit_to_celsius_conversion(reading),
        _ => handle_unknown_temperature_scale(),
    }
}

fn request_target_scale() -> u8 {
    println!("Enter 1 for (celsius -> fahrenheit), or 2 for (fahrenheit to celsius):");

    let mut target_scale = String::new();

    io::stdin()
        .read_line(&mut target_scale)
        .expect("Invalid input");

    let target_scale: u8 = target_scale.trim().parse().expect("Input not a Number");
    target_scale
}

fn request_temperature_to_convert() -> f32 {
    println!("Enter a temperature value to be converted:");

    let mut reading = String::new();

    io::stdin().read_line(&mut reading).expect("Invalid Input");

    let reading: f32 = reading.trim().parse::<f32>().expect("Input not a Number");
    reading
}

fn celsius_to_fahrenheit(reading: f32) -> f32 {
    ((9.0 / 5.0) * reading) + 32.0
}

fn handle_celsius_to_fahrenheit_conversion(reading: f32) {
    println!(
        "{reading}°C is {converted:.2} °F",
        converted = celsius_to_fahrenheit(reading)
    )
}

fn handle_fahrenheit_to_celsius_conversion(reading: f32) {
    println!(
        "{reading}°F is {converted:.2}°C",
        converted = fahrenheit_to_celsius(reading)
    )
}

fn handle_unknown_temperature_scale() {
    println!("Unsupported unit. Cannot make conversion")
}

fn fahrenheit_to_celsius(reading: f32) -> f32 {
    (reading - 32.0) * (5.0 / 9.0)
}