Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lecture 15 - Review: everything before Midterm 1

A clipboard with ticked items

Welcome to Review Day!

You've learned a lot in just a few weeks! Today we'll:

  • Review key concepts you need to master for the midterm
  • Practice with interactive questions
  • Clarify what you need to know vs. what's just context
  • Build confidence for the exam

Reminders about the exam

  • Friday during your usual class time
  • No reference sheets or calculators
  • Two exam versions and set (but not assigned) seating

And two things to keep in mind when this feels hard

  • Corrections. About two weeks after the exam, in discussion, you can redo specific questions and earn back up to half your lost points.
  • A strong final counts for more. If your final beats your midterm average, we reweight automatically and use whichever calculation is better for you.

Shell/Terminal Commands (Lecture 2)

For the midterm, you should recognize and recall:

  • pwd - where am I?
  • ls - what's here?
  • ls -la - more info and hidden files
  • mkdir folder_name - make a folder
  • cd folder_name - move into a folder
  • cd .. - move up to a parent folder
  • cd ~ - return to the home directory
  • rm filename - delete a file
  • rm -rf folder_name - delete a folder and everything in it, no questions asked
  • touch filename - make an empty file
  • cat filename - print a whole file to the screen
  • nano filename - edit a file without leaving the terminal
  • cp file.txt backup.txt - copy a file
  • mv old_name new_name - rename or move a file
  • echo "text" > file.txt - write text to a file, replacing what was there
  • echo "text" >> file.txt - add text to the end of a file

You DON'T need to: Memorize complex command flags, pipes, file permissions, or shell scripting

Git Commands (Lecture 4)

For the midterm, you should recognize and recall:

  • git clone - get a repository, pasting in the HTTPS or SSH link
  • git init - start tracking the folder you are standing in
  • git status - see what's changed
  • git diff - see exactly what changed since the last commit
  • git add . - stage all recent changes
  • git commit -m "my commit message" - create a commit with staged changes
  • git push - send what's on my machine to GitHub
  • git pull - get changes from GitHub to my machine

You DON'T need to: type merge, revert, reset, checkout, or pull request commands from memory

You DO need the ideas: what a branch is, what merging does, and what a merge conflict is, including roughly how you sorted out the one in Project 1

Cargo Commands (Lecture 5)

For the midterm, you should recognize and recall:

  • cargo new project_name - create project
  • cargo run - compile and run
  • cargo run --release - compile and run with optimizations (slower to compile, faster to run)
  • cargo build - just compile without running
  • cargo check - just check for errors without compiling
  • cargo test - run tests

You DON'T need to know: Cargo.toml syntax, how Cargo.lock works, or advanced cargo features

You DO need to read a compiler error: find the file and line it names, read what it says it expected and what it found, and use the suggestion. Remember that the fix does not always belong on the line the error points at.

Quick Questions: Tools

Question 1

Name the command that:

  • a) shows your current location on your machine
  • b) compiles your code without running it


Answer. a) pwd. b) cargo build. (cargo check looks for errors but never produces a program you could run.)

Question 2

What's the correct order for the basic Git workflow?

  • A) add -> commit -> push
  • B) commit -> add -> push
  • C) push -> add -> commit
  • D) add -> push -> commit

Answer. A. add, then commit, then push. Staging comes first, the commit packages what is staged, and the push sends it to GitHub.

Compilers vs Interpreters (Lecture 3)

Key Concepts

  • Compiled languages (like Rust): Code is transformed into machine code before running
  • Interpreted languages (like Python): Code is executed line-by-line at runtime
  • The compiler checks your code for errors and translates it into machine code
  • The machine code is directly executed by your computer - it isn't Rust anymore!
  • A compiler error means your code failed to translate into machine code
  • A runtime error means your machine code crashed while running

Rust prevents many runtime errors by being strict at compile time!

Variables and Types (Lecture 6)

Key Concepts

  • Defining variables: let x = 5;
  • Mutability: Variables are immutable by default, use let mut to allow them to change
  • Shadowing: let x = x + 1; creates a new x value without mut and lets you change types. A variable that's shadowed inside a scope ({}) is visible again after the scope ends.
  • Basic types: i32, f64, bool, char, &str, String
  • Rough variable sizes: Eg. i32 takes up 32-bits of space and its largest positive value is about half of u32's largest value
  • Type annotations: Rust infers types (let x = 5) or you can specify them (let x: i32 = 5)
  • Tuples: Creating (let x = (2,"hi")), accessing (let y = x.0 + 1), destructuring (let (a,b) = x)
  • Constants: Eg. const MY_CONST: i32 = 5, always immutable, must have explicit types, written into machine code at compile-time
  • Two's complement: how a signed integer holds a negative number. Flip every bit, add one. A leading 1 means negative, so as an i8, 1111 1111 is -1 and not 255

What's Not Important

  • Calculating exact variable sizes and max values
  • Complex string manipulation details

String vs &str - You're not responsible for it, but let's refresh

Quick explanation

  • String = a string = owned text data (like a text file you own)
  • &str = a string slice = borrowed text data (like looking at someone else's text)
  • A string literal like "hello" is a &str (you don't own it, it's baked into your program)
  • To convert from an &str to a String, use "hello".to_string() or String::from("hello")
  • To convert from a String to an &str, use &my_string (to create a "reference")

Don't stress! You can do most things with either one, and I will not make you do anything crazy with these / penalize you for misusing these on the midterm.

Quick Questions: Rust basics

Question 3

What happens with this code?

#![allow(unused)]
fn main() {
let x = 5;
x = 10;
println!("{}", x);
}
  • A) Prints 5
  • B) Prints 10
  • C) Compiler error
  • D) Runtime error

Answer. C, compiler error. x is immutable. let mut x = 5; fixes it.

Question 4

What's the type of x after this code?

#![allow(unused)]
fn main() {
let x = 5;
let x = x as f64;
let x = x > 3.0;
}
  • A) i32
  • B) f64
  • C) bool
  • D) Compiler error

Answer. C, bool. Each let x shadows the one before it and is allowed to change the type: i32, then f64, then the result of a comparison.

Question 5

How do you access the second element of tuple t = (1, 2, 3)?

  • A) t[1]
  • B) t.1
  • C) t.2
  • D) t(2)

Answer. B, t.1. Tuples use a dot and a number. Square brackets are for arrays and vectors.

Functions (Lecture 7)

Key Concepts

  • Function signature: fn name(param1: type1, param2: type2) -> return_type, returned value must match return_type
  • Expressions and statements: Expressions reduce to values (no semicolon), statements take actions (end with semicolon)
  • Returning with return or an expression: Ending a function with return x; and x are equivalent
  • {} blocks are scopes and expressions: They reduce to the value of the last expression inside them
  • Unit type: Functions without a return type return ()
  • Best practices: Keep functions small and single-purpose, name them with verbs

What's Not Important

  • Ownership/borrowing mechanics (we'll cover this after the midterm)
  • Advanced function patterns

Quick Questions: Functions

Question 6

What is the value of mystery(x)?

#![allow(unused)]
fn main() {
fn mystery(x: i32) -> i32 {
    x + 5;
}
let x = 1;
mystery(x)
}
  • A) 6
  • B) i32
  • C) ()
  • D) Compiler error

And if the return type were dropped, fn mystery(x: i32), what would change?



Answer. D, compiler error. The semicolon after x + 5 turns the body into a statement, so the function hands back () while its signature promises i32. Drop the semicolon, or write return x + 5;.

If -> i32 were dropped, it would compile, because () is then exactly what the signature says. mystery(x) would be ().

Question 7

Which is a correct function signature for a function that takes two integers and returns their sum?



Answer. fn add(a: i32, b: i32) -> i32. The names are up to you, and so is which integer type. Every parameter needs a type, and the return type comes after ->.

Control Flow and Arrays (Lecture 8)

Key Concepts

  • Ranges: 1..5 vs 1..=5
  • Arrays: Creating ([5,6] vs [5;6]), accessing (x[i]), 0-indexing
  • If/else: how to write if / else blocks with correct syntax
  • Loop types: for, while, loop - how and when to use each
  • break and continue: For controlling loop flow
  • Basic enumerating for (i, val) in x.iter().enumerate()

What's Not Important

  • Compact notation (let x = if y ... or let y = loop {...)
  • Enumerating over a string array with for (i, &item) in x.iter().enumerate()
  • Labeled loops, breaking out of an outer loop

Quick Questions: Control Flow & Arrays

Question 8

  • a) What's the difference between 1..5 and 1..=5?
  • b) How do you get both the index and the value when looping over an array?


Answer. a) 1..5 is 1, 2, 3, 4. 1..=5 also includes 5. b) for (i, val) in x.iter().enumerate().

Question 9

What does this print?

#![allow(unused)]
fn main() {
for i in 0..3 {
    if i == 1 { continue; }
    println!("{}", i);
}
}


Answer. 0, then 2. When i is 1, continue skips the println! and starts the next pass.

Comparing Programs (Lecture 9)

Key Concepts

  • Timing two programs that do the same thing, and why debug and release give different numbers
  • Counting steps: how many operations a piece of code does, and how that count grows as the input grows
  • Big O notation: describing that growth for time and for space
  • The common classes: O(1), O(log n), O(n), O(n^2), O(2^n), and what each one feels like as n gets big
  • The two rules: drop constants, keep the dominant term. O(3n + 7) is O(n)
  • Reading a loop: one loop over n is O(n), a loop inside a loop is usually O(n^2)

What's Not Important

  • Formal proofs, or the difference between big O, big theta, and big omega
  • Amortized analysis
  • Memorizing complexity numbers you have not derived

Sorting and Recursion (Lecture 10)

Key Concepts

  • Sorting by hand: selection sort and insertion sort, step by step on a small list
  • Finding their Big O from the loops: both are O(n^2), and you should be able to say why
  • Recursion: a base case plus a smaller version of the same problem, and what happens without a base case
  • Merge sort is O(n log n), from the shape of its call tree: log n levels, n work per level

What's Not Important

  • Writing merge sort from scratch
  • Quicksort, heapsort, or sort stability
  • The exact number of swaps or comparisons for a given list

Quick Questions: Complexity & Sorting

Question 10

What is the Big O of this, in terms of n?

#![allow(unused)]
fn main() {
for i in 0..n {
    for j in 0..n {
        println!("{}", i * j);
    }
}
}
  • A) O(1)
  • B) O(n)
  • C) O(n^2)
  • D) O(2^n)

Answer. C, O(n^2). A loop over n, inside a loop over n.

Question 11

A program is O(n^2). You double the size of the input. Roughly how much longer does it take?



Answer. About four times as long. Double the input and n^2 becomes (2n)^2, which is 4n^2.

Question 12

What is missing here, and what happens when you run it?

#![allow(unused)]
fn main() {
fn countdown(n: u32) {
    println!("{}", n);
    countdown(n - 1);
}
}


Answer. There is no base case, so it never stops calling itself. It crashes before it gets far, though: once n reaches 0, n - 1 goes below zero, which a u32 cannot hold, and the program panics with attempt to subtract with overflow. Add if n == 0 { return; } at the top to fix.

Question 13

Merge sort is O(n log n). Where does the log n come from?



Answer. From halving. The list splits in half each time, so it takes about log(n) splits to get down to single elements. That is the number of levels in the call tree, and every level does n work merging back together.

Structs (Lecture 11)

Key Concepts

  • Defining a struct: struct Customer { name: String, age: u32 }, and making one
  • Reading and writing fields with a dot, and that mut applies to the whole struct
  • Tuple structs for when the fields don't need names
  • impl blocks: writing a method and calling it with a dot
  • &self vs &mut self: read the struct, or change it. Plain self takes it with you
  • Constructors: Customer::new(...), and why it is :: and not .
  • Vec: a list that can grow, push, len, and indexing

What's Not Important

  • Deriving anything beyond Debug
  • Generic structs, or lifetimes on a struct
  • Box, trait objects, and anything else that comes after the midterm
  • pub: not on Friday. It comes back properly when we do modules and crates

Quick Questions: Structs

Question 14

What happens with this code?

#![allow(unused)]
fn main() {
struct Point { x: f64, y: f64 }

let p = Point { x: 1.0, y: 2.0 };
p.x = 5.0;
}
  • A) Prints nothing, runs fine
  • B) Compiler error
  • C) Runtime error
  • D) p.x is 1.0 afterwards

Answer. B, compiler error. p is not mut, so none of its fields can be written. let mut p = ... fixes it.

Question 15

What goes in the blank?

#![allow(unused)]
fn main() {
impl Rectangle {
    fn area(____) -> f64 {
        self.width * self.height
    }
}
}


Answer. &self. The method reads self.width and self.height and changes nothing, so it only needs to borrow.

Question 16

Why is it Customer::new("Alice") and not customer.new("Alice")?



Answer. new is an associated function, not a method: notice it takes no self. There is no Customer yet to put on the left of a dot, so you reach through the type itself with ::.

Enums and Pattern Matching (Lecture 12)

Key Concepts

  • Enum definition: Creating custom types with variants
  • Data in variants: Enums can hold data
  • match expressions: syntax by hand, needs to be exhaustive, how to use a catch-all (_)
  • Option<T>: Has Some(value) and None, for when there might be nothing
  • #[derive(Debug)]: For making enums printable
  • #[derive(PartialEq)]: For allowing enums to be compared with == and !=
  • Data extraction: Getting values out of enum variants with match. For Option specifically, also unwrap and expect
  • Pattern guards: an arm with a condition on it, Some(x) if x > 40 => ...

What you should be able to write

  • A match on an enum, covering every variant
  • A guard, which is just a condition on the arm. That is the flexible one

What's Not Important

  • if let notation
  • Writing the other pattern shapes from memory (ranges, tuples, arrays, struct patterns). You should be able to read a variety of them

Quick Questions: Enums & Match

Question 17

What's wrong with this code?

#![allow(unused)]
fn main() {
enum Status {
    Loading,
    Complete,
    Error,
}

match Status::Loading {
    Status::Loading => println!("Loading..."),
    Status::Complete => println!("Done!"),
}
}


Answer. The match is not exhaustive. Status::Error has no arm, so this does not compile. Add an arm for it, or a _ catch-all.

Question 18

If a function's return type is Option<i32> what values can it return (can be more than one)?

  • A) Some(i32)
  • B) Ok
  • C) Ok(i32)
  • D) None
  • E) Err

Answer. A and D. Some(i32) or None. Ok and Err belong to Result, not Option.

Question 19

Which of these can go in the ???? to print Got: 42? More than one works.

#![allow(unused)]
fn main() {
let x = Some(42);
match x {
    Some(????) => println!("Got: {}", ????),
    None => println!("Nothing"),
}
}
  • A) _ and _
  • B) 42 and 42
  • C) x and x
  • D) y and y

Answer. C and D both work.

y is the best answer, but x works too, because inside the arm, x is a new variable holding 42, shadowing the outer x. But it's confusing to read.

A fails because _ cannot be used as a value. B fails for a different reason: Some(42) matches only the number 42, so the match stops being exhaustive.

Question 20

What does #[derive(Debug)] do?



Answer. It lets you print it with {:?}.

Error Handling and File I/O (Lecture 13)

Key Concepts

  • Result<T, E>: Has Ok(value) and Err(error), for when something failed and you can say why
  • Choosing between Option, Result, and panic!: nothing is a normal answer, it failed and here is why, or carrying on makes no sense
  • panic! vs Result: Panic when unrecoverable, Result when recoverable
  • Error propagation: Passing errors up with match or ?
  • unwrap() and expect(): Quick ways to extract values (but they can panic!)
  • The ? operator: Shortcut for "if error, return it; if ok, give me the value". The error type has to match the one your function returns, or be convertible into it

What's Not Important

  • Custom error types, or implementing std::error::Error
  • Box<dyn Error>, and anything else that comes after the midterm
  • Writing file I/O from memory. You do not need to recall fs::read_to_string or fs::write exactly, and you can look them up. You should be able to read them and say what you have to do with the Result each one hands back

Quick Questions: Error Handling

Question 21

Option, Result, or panic!? One for each:

  • a) looking up a name that is not in the list
  • b) reading a file that is not there
  • c) a situation your own code should have made impossible

And when is writing .unwrap() a reasonable thing to do?



Answer. a) Option, since "not in the list" is a normal answer. b) Result, since it failed and you can say why. c) panic!, since carrying on makes no sense.

.unwrap() is reasonable in a test, in a quick script, or when you can show the failure cannot happen.

Question 22

Why won't this code compile?

#![allow(unused)]
fn main() {
fn parse_number(s: &str) -> Result<i32, String> {
    let num = s.parse::<i32>()?;  // parse() returns Result<i32, ParseIntError>
    Ok(num * 2)
}
}
  • A) The ? operator can't be used in let statements
  • B) You can't multiply by 2 inside Ok()
  • C) The error types don't match: ParseIntError vs String
  • D) Ok doesn't match the Result type

Answer. C. ? tries to turn the ParseIntError into a String, and no such conversion exists.

Putting It All Together

What You've Accomplished

In just a few weeks, you've learned:

  • Professional development tools (shell, git, github, cargo)
  • The foundations of a systems programming language
  • Sophisticated pattern matching and error handling techniques

That's a lot.

And if it doesn't feel fluent yet, give it some time. It's like you memorized your first 500 words in a new spoken language but haven't had much practice actually speaking it yet. It feels awkward, and that's normal.

Midterm Strategy

  • Focus on concepts: Understand the "why" behind the syntax and it will be easier to remember
  • Practice with your hands: Literally and figuratively - practice solving problems, and practice on paper
  • Take big problems step-by-step: Understand each line of code before reading the next. And make a plan before you start to hand-code

Questions and Discussion

What is still unclear? This is the last time we are all in a room together before Friday.

Activity time

See Activity 15. Two hand-coding problems, in groups of three.

(You said more hand-coding and more group work!)

One sheet per group, and a different person holds the pen for each problem. The other two say what to write and catch the mistakes.