Functions

The following is a version of the hello world program in the rust documentation. I changed the traditional "hello world" string for something more educational.

fn main() { // declare function, name it, and set arguments
    println!("Cassidy has giant quads"); // use println! macro and give string argument
} // close expression

The program contains three components that have specific roles :

  • The main function
  • The curly brackets
  • The println! macro

When a rust program runs, the main function is the first part that executes.


fn main() { // define main function 
// instructions for the main function go here
} // close curly bracket and complete expression

The rust compiler expects the main function to return closed, regular brackets. In contrast, regular Rust functions allow programmers to define output types and return values. For example, the main function will retun an error if the expression output is not what the compiler expects.


fn main() { // define main function 
6
} // close curly bracket and complete expression

While the Rust main function allows limited output types, regular functions are more flexable. For example, one can define a function called facts that outputs a static string data type, call that function in the main function, and print the output of facts using the println! macro, as seen below:


fn main() { // define main function 
   let truth = facts(); // assign the output of facts to truth using a let statement
   println!("{}", truth) // print the truth using the println! macro
}

// Below is the second function, which we call in the main function

fn facts() -> &'static str { // declare facts function and assign output type
    let quads = "Cassidy has giant quads"; // assign some facts to a variable using a let statement
    quads // make the expression blast out the facts
} // close the expression

Modules are collections of things like functions, structs, traits, impl, blocks, or other modules. Within modules, there is public and private visability. By deafault, modules have private visability, which means that module components are not usable outside of the modules. If needed, one can use the pub keyword to make an item visable outside of the module. The example below makes a function inside of a module visable in the main program.


fn main() {
    // define main function
    let domination = social_science::econ(); // assign the output of public module to variable
    println!("{}", domination); // print using println! macro
}

// Below is a module in which we have a public function that has public access 

mod social_science {
    pub fn econ() -> &'static str { 
        // declare function and set output type. I used a static string becasue I want the output to go to the stack and make the program faster
        let globalization = "Westernization"; // a string to a variable using a let statement
        globalization // pass the variable through the expression
    } // close the expression
}


We can use functions to calculate descriptive statistics For example, we can calculate the mean of an integer array. This code can likely be more generalizable becasue it currently requires arrays that meet the defined type specifications. A vector type might be more generalizable and sacrifice speed and memory-footprint size.


fn main() {
    let weights: [f64; 7] = [61.3, 62.1, 62.1, 61.2, 61.2, 61.3, 62.2]; // array of my weights from the past seven days using float 64 data
    let avg = mean(weights); // assign output of mean function to variable 
    println!("My average weight over the past seven days was {}", avg); // print result and context 
    }
    
// below, I create a mean function 
    
fn mean(values: [f64; 7]) -> f64 {
    let mut sum: f64 = 0.0; // declare initial sum using decimal becasue it is a float type 
    let length = values.len() as f64; // declare length using len() method. Need to change to float to divide the sum by the length later 
    for value in values { // loop through array 
        sum += value; // sum values 
    }; // close expression 
    sum/length // return mean 
} // close function expression 

Data Types

Data primitives are the basis for other data types. Primitives are immutable and typically the lowest level language application of the programming language implimentation.

Rust has two primitive types: scalar and compound.

Compared to compound primitives, scalar primitves are less complex. Scalar data types include signed and unsigned integers, floating point, character, boolean, and unit types.

Signed integers are are whole numbers that can be positive or negative. "Signed" referes to the positive or negative sign. Signed integer types include i8, i16, i32, i64,i128, and isize. The information that proceeds the i in these names referes to the amount of data that these types store. We can find out how much each of these data types stores using the following expression: -(2n - 1) to 2n - 1 -1. The folloing code declares an array of data types, loops through that array, executes the expression from the preceeding sentence, and prints the upper and lower limits of each data type.


extern crate num_traits;
use num_traits::checked_pow;

fn main() { // open main expression
    let bits: [i16; 5] = [8, 16, 32, 64, 128]; // create array of bit sizes using i16 becasue 128 is too large for i8
    let base:u128 = 2; // declare base using unmutable let statement. I used u128 becasue this type is used for the checkedpow expression
    for bit in bits { // for loop that reads through elements in bits array 
        let power:usize = {bit-1} as usize; // declare power using let statement and specifying usize data type
        let res = checked_pow(base, power); // evaluate expression using checked_pow function
        println!("For i{bit}, the lower limit is -{lower} and the upper limit is {upper}", bit=bit, lower=res.unwrap(), upper=res.unwrap() -1); // print result using println! macro
    }; // close for loop expression 
} // close main expression

Control Structures

Control structures allow programers to insturct computers how to behave when faced with certian conditions.

Common control structures include:

  • if statements
  • else statements
  • for loops
  • while loops

We can relate the logic of these control sturtures to situations that we encounter in our lives.

For example: I could be at the grocery store looking at a bag of chips and think to myself: "if I add this bag of chips to my cart, I will likely eat its entire contents before I get home and feel like trash for the rest of the night."

A program that executes that logic could look like this:


fn main(){ // declare main function
    let chips=true; // use let statement to exhibit my lack of self control
    if chips { // declare if statement with boolean condition
        println!("I ate the whole bag of chips and feel like trash") // print result if true
    } // close if expression
} // close main function expression

Else statements tell the computer to do an action if the first condition is not met.

Using the first example, we can create an if-else control structure that runs code when the first condition is not true:


fn main(){ // declare main function
    let chips=false; // use let statement to exhibit my outstanding self control
    if chips { // declare if statement with boolean condition
        println!("I ate the whole bag of chips and feel like trash") // print result if true
    } else { // close if expression
        println!("Good work, idiot. You practiced self control!") // print supportive message
    } // close else expression
} // close main function expression

We can combine let and if statements. These statements might be useful when there are variables in our code that are condition-dependent. For example, after I finish a swim workout, my body looks gorgeous. However, on days when I don't swim, my body looks like a bony meat bag. A program that executes that logic could look like this:


fn main(){ // declare main function
    let swim=true; // declare boolen swim variable
    let gorgeous = if swim {["went for a swim", "gorgeous"]} else {["didn't for a swim", "like a bony meat bag"]}; // boolean let-if statement
    println!("Cassidy {}, and his body looks {}", gorgeous[0], gorgeous[1]); // print result of let-if statement
} // close main expression

The rust compiler expects let-if epressions to produce the same data types. For example, the program below evaluates boolean conditions and outputs what people might call Toronto based on a pre-defined age.


fn main() {
    let age=28; // create variable using let statement and boolean true 
    let toronto = if age <= 26 {"6"} else if age > 26 && age < 86 {"Tdot"} else {"The Big Smoke"}; // declare let-if statement with expressions 
    println!("My age is {age} and I call Toronto {toronto}", age=age.to_string(), toronto=toronto)
}

Below, we change the expression data type for the people who refer to Toronto as "The Six." Becasue we changed the output data type of this expression, the compiler returns an error.


fn main() {
    let age=28; // create integer variable using let statement  
    let toronto = if age <= 26 {6} else if age > 26 && age < 86 {"Tdot"} else {"The Big Smoke"}; // declare let-if statement with expressions 
    println!("My age is {age} and I call Toronto {toronto}", age=age.to_string(), toronto=toronto)
}

Loops allow us to do stuff fast. Rust has three loop types: for, loop, and while.

For loops run for perscribed iterations. In the example below, I created an array filled with events from a rec-league hockey game in which a player on the other team punched me, cowered behind the ref after I confronted him, and his teammate tried to intimidate the smallest player on our team. The for loop runs through the elements in the array, and the println! macro outputs each event with an intenisty integer. The intensity integers increase exponentially.


fn main() {
    let mut intensity = 0; // declare mutable variable using a let statement 
    let problems: [&str; 5] = ["A guy punched me", "The guy who punched me skated away after I confronted him", "The guy who punched me whined to the ref after he skated away", "The guy who punched me was too scared to look at me for the rest of the game", "The 220 lb teammate of the guy who punched me tried to intimidate the smallest player on our team"]; // declare array of problems, and specify data type and length 
    for problem in problems { // for loop that iterates through elements in array 
        if intensity < 2 { // boolean check until multiplication can represent exponential growth
            intensity += 1; // increment intensity 
        } else { // if intensity is greater than 2, the following expression can use multiplication to represent exponential growth
            intensity = intensity * intensity // multiply intensity by itself and redefine variable 
        }
        println!("{problem}. The intensity is now {intensity}", problem=problem, intensity=intensity.to_string()) // print current intensity
    } // close for loop 
} 

Loop loops run until they're told to stop. For example, a program could contain a boolean-if statement that checks for the number of iterations the program has completed. If the number of iterations triggers a true response from the if-expression, the programmer can use a break statement to end the loop.


fn main() { // create main function 
   let mut loops: u8 = 0; // assign mutable, unsiged, eight-bit integer to variable using let statement
   loop { // open loop expression
    if loops < 10 { // boolean check if current loop is less than ten 
        loops+=1; // increment loop number 
        println!("Loop number {}", loops); // print number of loops 
    } else { // boolean else
        break // break out of loop if current iteration is >= 10 
    }; // close else expression 
   }; // close loop expression 
} // close main function 

While loops run while a boolean condition is true. For example a while loop may run until an element in an array is found.


fn main() { // open main expression 
    let characteristics: [&str; 4] = ["Saquon Barkley", "Cassidy MacDonald", "got dem quads",  "got that dog in him"]; // declare array of static strings
    let mut i: usize = 0; // declare mutable index counter using usize type 
    while characteristics[i] != "got dem quads" { // while loop that iterates through elements in array 
       println!("{} {}", characteristics[i], characteristics[2]); // print the facts 
       println!("{} {}", characteristics[i], characteristics[3]); // print the facts 
       i+=1; // increment counter
        }; // close while loop 
    } // close main expression

Break and continue statements allow us to control loops. Break statements end loops and continue makes the program move on to the next iteration. For example, if you were playing monopoly with a resonable person and they caught you cheating, you may let them pass go before continuing the game. An un reasonable person, in contrast, may throw a temper tantrum and break the game. A program that demonstrates that scenario follows:


fn main() {
    // open main expression
    let cheating: [bool; 7] = [true, false, true, false, true, false, true]; // declare array of events
    let reasonable: [bool; 7] = [true, false, false, false, true, false, true]; // declare array of events
    let mut i: usize = 0; // declare index pointer
    for cheat in cheating { // open for loop that iterates through boolean array
        if reasonable[i] & cheat { // boolean if statement that checks if the players are reasonable and cheating
            println!("Pass go"); // print outcome
            i += 1; // increment counter 
            continue; // continue 
        } else if !reasonable[i] & cheat { // boolean if that checks if the players are not reasonable and cheating
            println!("Break that shit, King Kong"); // print outcome
            break; // Asshole broke the game
        }; // close expression
        i += 1; // increment counter if both conditions are not true
    } // close loop expression
} // close main expression 


Break and control statements can also be associated with specific loops. For example, you could use this if you had two arrays of names and to check if there were name duplicates.


fn main() {
    // open main expression
    let names_1: [&str; 4] = ["Fred", "Cocco", "Sally-Anne", "Ron"]; // declare array of names
     let names_2: [&str; 4] = ["Judy", "George", "Cocco", "Jen"]; // declare array of names
    'list_1: for name_1 in names_1 { // open for loop that iterates through names_1 array
        for name_2 in names_2 { // open second for loop that iterates through second array 
            if name_2 == name_1 { // boolean check to see if there are duplicate names
                println!("{name_1} is in array one and {name_2} is in array two", name_1=name_1, name_2=name_2); // print result if match 
                break 'list_1 // break out of outer loop
            } // close if expression
        } // close inner loop expression 
    } // close outer loop expression 
} // close main expression 


Memory Allocation

Efficency is a primary concern in Rust's memory-allocation logic. Rust stores information using two types of memory: the stack and the heap.

One may visulize the stack as pancakes that we sequentially weigh, stack, and unstack with our older brother. Further, we can imagine that our older brother's contamination OCD compels him to put us in a headlock each time we try to take pancakes from locations on the stack other than the top.