Examples: - Python automates memory management and accepts runtime overhead - C exposes manual memory operations and few compile-time protections - Modern C++ often uses RAII and smart pointers, while still permitting low-level operations - Rust uses ownership and borrowing to enforce many memory-safety rules in safe code without a tracing garbage collector
Their values have copy semantics and do not own resources that require custom cleanup
After copying, both variables remain valid
let x =5;let y = x;// Copy happens (not a move)println!("x = {}, y = {}", x, y);// ✅ Both valid!
AI copilot technique: understanding moves
When you encounter move errors, ask your AI assistant for help:
Effective prompts: - “Explain this Rust move error in simple terms: [paste error message]” - “Why does Rust move String but copy i32?” - “How do I use a value after it’s been moved?” - “What’s the difference between Copy and Clone in Rust?”
First step: Read the compiler diagnostic, identify the values and borrows it names, and then ask an AI assistant about the specific rule that remains unclear.
Scope and automatic cleanup
Every variable has a scope: the region of code where it’s valid
# Goal: remove all even numbers from a listitems = [1, 2, 3, 4, 6, 7, 8]for item in items:if item %2==0: items.remove(item) # Modifying the list while iterating!print(items) # Expected: [1, 3, 7]# Actual: [1, 3, 6, 7] ← 6 is never checked!
Python runs to completion: no error, no warning, exit code 0
When 4 is removed at index 2, the list shifts left; the iterator advances to 7, skipping 6 entirely
This is a logic bug: the language permits the operation, so tests must detect the incorrect result
Why the Python loop skips an item
for item in items tracks a hidden index counter: it increments by 1 each step, unaware of any list changes:
error[E0502]: cannot borrow `items` as mutable because it is also borrowed as immutable
--> src/main.rs:6:13
|
4 | for item in &items {
| ------ immutable borrow occurs here
...
6 | items.retain(|x| x != item);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
for item in &items takes an immutable borrow of the whole vector
.retain() needs a mutable borrow of the same vector, at the same time
Rust’s rule: you cannot hold both simultaneously, so this does not compile
The logic bug that ran in Python is rejected by Rust’s borrowing rules before execution
The original owner retains ownership, so the value won’t be dropped
fn calculate_length(s:&String) ->usize{ s.len()}// s goes out of scope, but it doesn't own the String, so nothing is droppedfn main() {let my_string =String::from("hello");let len = calculate_length(&my_string);// Borrow, don't moveprintln!("{} has length {}", my_string, len);// ✅ Still valid!}
A reference’s scope ends at its last use, not at the closing brace
This makes many common patterns “just work”
letmut s =String::from("hello");let r1 =&s;// Immutable borrowlet r2 =&s;// Another immutable borrowprintln!("{}, {}", r1, r2);// r1 and r2 last used here - scope endslet r3 =&mut s;// ✅ OK: no immutable borrows are activeprintln!("{}", r3);
AI copilot technique: debugging borrow-checker errors
The borrow checker is your friend, but it takes practice. Use AI to accelerate your learning:
Effective prompts: - “Explain this borrow checker error: [paste full error message]” - “Why can’t I borrow this variable as mutable? [paste code]” - “How do I fix ‘cannot borrow as mutable more than once’?” - “What’s a dangling reference and how does Rust prevent them?”
Best practice: Read the error message first, understand the rule being violated, then ask AI for deeper insight or alternative solutions.
This is a critical security vulnerability in C/C++ (use-after-free)
Safe Rust prevents references from outliving the values they borrow
fn dangle() ->&String{// ❌ Won't compile!let s =String::from("hello");&s // ERROR: s will be dropped, but we're returning a reference to it}// s goes out of scope and is dropped, but the reference would point to freed memory
The fix: Return the owned value, not a reference:
fn no_dangle() ->String{// ✅ Ownership transfers to callerlet s =String::from("hello"); s // Move ownership out}
Introduction to lifetimes
Lifetimes are Rust’s way of ensuring references are always valid
Every reference has a lifetime - the scope for which it’s valid
Usually, the compiler can infer lifetimes automatically
Sometimes you need to annotate them explicitly with 'a, 'b, etc.
Function returns a reference that came from one of multiple input references
Structs contain references (struct must not outlive the referenced data)
Multiple references with complex relationships where the compiler can’t infer
Good news: In most code, lifetimes are inferred automatically thanks to lifetime elision rules
AI copilot technique: understanding lifetimes
Lifetimes can be confusing. Use AI to build intuition:
Effective prompts: - “Explain Rust lifetimes like I’m coming from Python” - “Why does this code need a lifetime annotation? [paste code]” - “What does ‘a mean in this function signature? [paste signature]“ - ”How do I fix ’lifetime may not live long enough’?” - “When can I omit lifetime annotations in Rust?”
Pro tip: Use Rust Playground to experiment. Change code, see errors, ask AI to explain the errors.
Lifetimes in structs
struct Excerpt<'a>{ text:&'astr,// This reference must be valid as long as the Excerpt exists}fn main() {let novel =String::from("Call me Ishmael. Some years ago...");let first_sentence = novel.split('.').next().unwrap();let excerpt = Excerpt { text: first_sentence };println!("{}", excerpt.text);}// excerpt and novel both go out of scope here (OK: excerpt doesn't outlive novel)
When a struct contains a reference, we must annotate the lifetime
'a says: “this struct can’t live longer than the data it references”
What ownership gives you: - Memory safety without garbage collection - No data races in safe Rust (enforced at compile time) - Predictable performance (no GC pauses) - Compile-time checks that usually do not add runtime work
The trade-off: - Steeper learning curve (you’re learning now!) - Resolve borrow-checker feedback while learning the ownership model - More upfront thinking about data ownership
Transfer: Ownership practice gives you another way to reason about resource lifetimes, aliases, and mutation in Python, JavaScript, and other languages.
Case studies to investigate
Examples of organizations describing Rust migrations:
Discord: Discusses latency behavior in one service migration from Go to Rust
Dropbox: Describes design choices in a cross-platform sync engine
npm: Reports results from selected CPU-bound services
Microsoft: Discusses memory-safety vulnerabilities and safer systems-language research
Discussion question: Which results come from the language, the new design, the workload, or the implementation team?
Week 11 preview: structs, enums, and pattern matching - Creating custom data types with struct - Rust’s powerful enum type - Pattern matching with match - AI-assisted data modeling strategies
For now: Practice ownership and borrowing. These concepts support the Rust topics that follow.
Questions?
Remember: - The borrow checker is your friend (even when it feels like your enemy) - Read the compiler diagnostic before deciding on a fix - Use AI assistants to deepen understanding, not to skip learning - Explain each ownership fix in terms of the value, owner, and borrow involved
Office hours: Available on Microsoft Teams - reach out anytime!