Week 11: Structuring Code and Data in Rust
main.rs fileThree levels of organization:
Think of it like: Package = project, Crate = building, Modules = rooms
mod// src/main.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {
println!("Adding to waitlist");
}
}
mod serving {
fn take_order() {
println!("Taking order");
}
}
}
fn main() {
front_of_house::hosting::add_to_waitlist();
// front_of_house::serving::take_order(); // ❌ ERROR: private!
}pub keyword to make items publicuse keyword: bringing paths into scopeProject structure:
src/
├── main.rs
├── front_of_house.rs
└── front_of_house/
├── hosting.rs
└── serving.rs
In main.rs:
When organizing code into modules, ask your AI assistant:
Effective prompts: - “How should I organize this Rust project into modules? [describe project]” - “What should be public vs private in this module? [paste code]” - “Explain the difference between ‘use’ and ‘mod’ in Rust” - “Help me split this large main.rs into separate modules”
Pro tip: Ask AI to explain the module tree structure - visualizations help!
structstruct (structure) groups related data into a single, meaningful typestruct User {
username: String,
email: String,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("ada@example.com"),
username: String::from("ada_lovelace"),
active: true,
};
println!("Username: {}", user1.username);
// Make mutable to change fields
let mut user2 = User {
email: String::from("grace@example.com"),
username: String::from("grace_hopper"),
active: true,
};
user2.email = String::from("new_email@example.com");
}impl blocksstruct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Method (takes &self)
fn area(&self) -> u32 {
self.width * self.height
}
// Associated function (no self) - like a static method
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!("Area: {}", rect.area());
let sq = Rectangle::square(10);
}enummatchenum Coin {
Penny,
Nickel,
Dime,
Quarter(String), // Quarter holds state name
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("Quarter from {}!", state);
25
}
}
}
fn main() {
let my_coin = Coin::Quarter(String::from("Alaska"));
println!("Value: {} cents", value_in_cents(my_coin));
}When modeling data, ask your AI assistant:
Effective prompts: - “Should I use a struct or enum for this? [describe data]” - “Help me model a [domain concept] in Rust with structs and enums” - “What fields should this struct have? [describe requirements]” - “Show me how to use match to handle all cases of this enum”
Example: “I need to model a blog post that can be Draft, Published, or Archived. Each state has different data. How should I structure this in Rust?”
nullOption<T>: handling optional valuesenum Option<T> {
Some(T),
None,
}
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("Found: {}", name),
None => println!("User not found"),
}
// Shorthand with if let
if let Some(name) = find_user(2) {
println!("Found: {}", name);
} else {
println!("User not found");
}
}Result<T, E>: recoverable errorsenum Result<T, E> {
Ok(T),
Err(E),
}
use std::fs::File;
use std::io::ErrorKind;
fn open_config() -> Result<File, std::io::Error> {
File::open("config.txt")
}
fn main() {
match open_config() {
Ok(file) => println!("Opened file successfully"),
Err(error) => match error.kind() {
ErrorKind::NotFound => println!("File not found!"),
other => println!("Error opening file: {:?}", other),
},
}
}? operator: error propagationuse std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut file = File::open("username.txt")?; // ❓ propagates error if Err
let mut username = String::new();
file.read_to_string(&mut username)?; // ❓ propagates error if Err
Ok(username) // ✅ returns Ok if success
}
// Without ? operator, this would be:
// match File::open("username.txt") {
// Ok(mut file) => match file.read_to_string(&mut username) {
// Ok(_) => Ok(username),
// Err(e) => Err(e),
// },
// Err(e) => Err(e),
// }When handling errors, ask your AI assistant:
Effective prompts: - “When should I use Option vs Result in Rust?” - “Help me handle this error idiomatically: [paste code]” - “Explain the ? operator and when I can use it” - “Convert this match error handling to use ? operator”
Pro tip: Ask AI to show both verbose (match) and concise (? operator) versions to understand what’s happening under the hood.
Vec<T>Vec<T> (vector) is a growable array - like Python’s listfn main() {
// Creating vectors
let mut v: Vec<i32> = Vec::new();
let mut v2 = vec![1, 2, 3]; // vec! macro for initial values
// Adding elements
v.push(5);
v.push(6);
// Accessing elements
let third = &v2[2]; // Panics if out of bounds
let maybe_third = v2.get(2); // Returns Option<&T>
// Iterating
for i in &v2 {
println!("{}", i);
}
}String vs &strString - Owned, growable, heap-allocated (like Vec<u8>)&str - String slice, borrowed, immutable viewfn main() {
// String - owned
let mut s = String::from("hello");
s.push_str(" world"); // Can modify
// &str - borrowed string slice
let slice: &str = &s[0..5]; // "hello"
// String literals are &str
let literal = "I'm a &str";
// Converting
let s2: String = literal.to_string();
let slice2: &str = &s2;
}HashMap<K, V>: key-value storageuse std::collections::HashMap;
fn main() {
// Creating
let mut scores = HashMap::new();
// Inserting
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// Accessing
let team_name = String::from("Blue");
let score = scores.get(&team_name); // Returns Option<&V>
match score {
Some(&s) => println!("Score: {}", s),
None => println!("Team not found"),
}
// Iterating
for (key, value) in &scores {
println!("{}: {}", key, value);
}
}Vec<T> - Use when: - You need an ordered list - You want to access by index - You’ll add/remove from the end
String - Use when: - You need owned, growable text - Building strings dynamically
HashMap<K, V> - Use when: - You need key-value lookups - Order doesn’t matter - Fast access by key is important
use std::collections::HashMap;
struct Contact {
name: String,
phone: String,
email: Option<String>, // Email is optional
}
fn main() {
let mut phone_book: HashMap<String, Contact> = HashMap::new();
phone_book.insert(
String::from("Alice"),
Contact {
name: String::from("Alice Smith"),
phone: String::from("555-1234"),
email: Some(String::from("alice@example.com")),
}
);
// Look up by name
if let Some(contact) = phone_book.get("Alice") {
println!("Phone: {}", contact.phone);
if let Some(email) = &contact.email {
println!("Email: {}", email);
}
}
}When using collections, ask your AI assistant:
Effective prompts: - “Which Rust collection should I use for [describe use case]?” - “Help me convert this Python list comprehension to Rust: [paste code]” - “Show me how to iterate over a HashMap in Rust” - “How do I handle the Option returned by HashMap.get()?” - “What’s the difference between Vec::push and Vec::append?”
Example: “I have a list of user IDs and need fast lookup. Should I use Vec or HashMap?”
This week’s lab has three parts:
Result from functions that can failOption for optional data? operatorVec<T>HashMap<K, V> for fast lookupsFull instructions: labs/lab11/README.md
What you learned today is used daily in industry:
Interview topics: - “How does Rust handle errors differently than exceptions?” - “When would you use an enum instead of inheritance?” - “Explain Option vs Result”
Official Rust documentation: - The Rust Book - Chapter 7: Modules - The Rust Book - Chapter 5: Structs - The Rust Book - Chapter 6: Enums and Pattern Matching - The Rust Book - Chapter 9: Error Handling - The Rust Book - Chapter 8: Collections
Interactive practice: - Rust by Example - Modules - Rust by Example - Error Handling - Rustlings - Structs Exercises
Remember: - Modules organize code, structs/enums organize data - Option<T> eliminates null pointer errors - Result<T, E> makes error handling explicit - Collections (Vec, String, HashMap) follow ownership rules - Use AI to explore design patterns and learn idioms
Lab 11 due: Sunday, November 16 at 11:59 PM
Office hours: Available on Microsoft Teams - reach out anytime!
IS4010: App Development with AI