Lecture 15 - Review: everything before Midterm 1
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 filesmkdir folder_name- make a foldercd folder_name- move into a foldercd ..- move up to a parent foldercd ~- return to the home directoryrm filename- delete a filerm -rf folder_name- delete a folder and everything in it, no questions askedtouch filename- make an empty filecat filename- print a whole file to the screennano filename- edit a file without leaving the terminalcp file.txt backup.txt- copy a filemv old_name new_name- rename or move a fileecho "text" > file.txt- write text to a file, replacing what was thereecho "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 linkgit init- start tracking the folder you are standing ingit status- see what's changedgit diff- see exactly what changed since the last commitgit add .- stage all recent changesgit commit -m "my commit message"- create a commit with staged changesgit push- send what's on my machine to GitHubgit 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 projectcargo run- compile and runcargo run --release- compile and run with optimizations (slower to compile, faster to run)cargo build- just compile without runningcargo check- just check for errors without compilingcargo 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 mutto allow them to change - Shadowing:
let x = x + 1;creates a newxvalue withoutmutand 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.
i32takes up 32-bits of space and its largest positive value is about half ofu32'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
1means negative, so as ani8,1111 1111is-1and not255
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()orString::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 matchreturn_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;andxare 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..5vs1..=5 - Arrays: Creating (
[5,6]vs[5;6]), accessing (x[i]), 0-indexing - If/else: how to write
if / elseblocks with correct syntax - Loop types:
for,while,loop- how and when to use each breakandcontinue: For controlling loop flow- Basic enumerating
for (i, val) in x.iter().enumerate()
What's Not Important
- Compact notation (
let x = if y ...orlet 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..5and1..=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)isO(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
mutapplies to the whole struct - Tuple structs for when the fields don't need names
implblocks: writing a method and calling it with a dot&selfvs&mut self: read the struct, or change it. Plainselftakes 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 midtermpub: 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.xis 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
matchexpressions: syntax by hand, needs to be exhaustive, how to use a catch-all (_)Option<T>: HasSome(value)andNone, 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. ForOptionspecifically, alsounwrapandexpect - Pattern guards: an arm with a condition on it,
Some(x) if x > 40 => ...
What you should be able to write
- A
matchon 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 letnotation- 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)
42and42 - C)
xandx - D)
yandy
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>: HasOk(value)andErr(error), for when something failed and you can say why- Choosing between
Option,Result, andpanic!: nothing is a normal answer, it failed and here is why, or carrying on makes no sense panic!vsResult: Panic when unrecoverable, Result when recoverable- Error propagation: Passing errors up with
matchor? unwrap()andexpect(): 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_stringorfs::writeexactly, and you can look them up. You should be able to read them and say what you have to do with theResulteach 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 inletstatements - B) You can't multiply by 2 inside
Ok() - C) The error types don't match:
ParseIntErrorvsString - D)
Okdoesn't match theResulttype
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.