This is the full developer documentation for Rust for TS/JS Developers # Rust for TypeScript & JavaScript Developers > Learn Rust through side-by-side TypeScript and JavaScript comparisons covering ownership, errors, async, web APIs, databases, WebAssembly, and production. free · open source · written for TS/JS developers # Learn Rust from the TS/JS you know This guide assumes you already write TypeScript or JavaScript. It uses familiar code to explain ownership, error handling, traits, async, and the Rust ecosystem, including the points where Rust needs a different approach. $ you’ll learn ownership and borrowing [start the guide](/00-introduction/)[why Rust?](/01-getting-started/00-why-rust/)[view the source](https://github.com/zeybek/rs4ts.dev) // one\_problem\_two\_languages familiar control flow, explicit error types user.ts→user.rs typescript// familiar control flow ```ts 1 type User = { id: string; name: string }; 2 3 async function getUser(id: string): Promise { 4 const response = await fetch("/api/users/" + id); 5 if (!response.ok) throw new Error("request failed"); 6 return response.json(); 7 } ``` rust// failure stays in the return type ```rust 1 struct User { id: String, name: String } 2 3 async fn get_user(id: &str) -> Result { 4 let url = format!("/api/users/{id}"); 5 let response = reqwest::get(url).await?.error_for_status()?; 6 Ok(response.json().await?) 7 } ``` 31Guide sections 6Capstone projects 2024Rust edition MITOpen source // how\_it\_works ## how the guide is organized [Section titled “how the guide is organized”](#how-the-guide-is-organized) ### Start from familiar code Chapters put familiar TS/JS code beside its Rust equivalent, then explain the rules behind the differences. ### Compare semantics, not just syntax The guide marks places where familiar syntax behaves differently, especially around ownership, errors, async execution, and type modeling. ### Read errors with the working code Compiler messages and common mistakes are explained beside the corrected version, so the rejected code has context. ### Practice beyond isolated snippets Exercises reinforce individual topics. Six dependency-locked capstone crates combine them in complete programs checked by CI. // core\_differences ## three differences to understand first [Section titled “three differences to understand first”](#three-differences-to-understand-first) 01 ### ownership\_and\_borrowing/ JavaScript relies on garbage collection. Rust uses ownership and borrowing to make data lifetime and mutation rules explicit at compile time. \[ownership]\[borrow]\[lifetimes]\[drop] ownership.rs ```rust 1 let s = String::from("hello"); 2 let s2 = s; // ownership moves to s2 3 // println!("{s}"); // compile error: s no longer valid 4 println!("{s2}"); // ok: s2 owns the string ``` 02 ### result\_and\_option/ For recoverable failures, Rust returns `Result`; missing values are `Option`. The `?` operator propagates errors without hiding them from the function signature. \[result]\[option]\[question\_mark]\[error\_types] errors.rs ```rust 1 fn parse_double(input: &str) -> Result { 2 let n: i32 = input.trim().parse()?; // ? propagates the error 3 Ok(n * 2) 4 } ``` 03 ### async\_and\_concurrency/ The `async`/`await` syntax is familiar, but Rust futures are lazy and need an executor such as Tokio. Async coordinates waiting; CPU-bound parallelism is a separate choice. \[future]\[tokio]\[tasks]\[channels] async.rs ```rust 1 #[tokio::main] 2 async fn main() { 3 // Tokio's runtime drives both futures concurrently 4 let (a, b) = tokio::join!(fetch_user(1), fetch_user(2)); 5 println!("{a:?} {b:?}"); 6 } ``` // curriculum ## one guide, three stages [Section titled “one guide, three stages”](#one-guide-three-stages) language/00–15 Setup and syntax lead into **ownership**, collections, errors, traits, async, testing, macros, and serialization. ****** applications/ **build here**16–24 Build with **Axum**, SQLx, CLI tooling, WebAssembly, FFI, performance tools, common patterns, and the Rust ecosystem. ****** shipping/25–30 Continue with advanced topics, systems programming, **security**, production operations, migration, and the capstone projects. ****** The chapter order is the default route, not a requirement. Use the sidebar to open a topic directly when a project gives you a concrete question. // capstone\_projects ## use Rust in complete programs [Section titled “use Rust in complete programs”](#use-rust-in-complete-programs) [01REST API/30-projects/00-rest-api/an Express-style CRUD service with Axum, validation, and typed errors→](/30-projects/00-rest-api/)[02CLI task manager/30-projects/01-cli-tool/subcommands, JSON persistence, file I/O, and recoverable errors→](/30-projects/01-cli-tool/)[03WebSocket chat/30-projects/03-websocket-chat/rooms, Tokio broadcast channels, shared state, and JSON messages→](/30-projects/03-websocket-chat/)[04Full-stack notes app/30-projects/05-full-stack/an Axum API and WebAssembly frontend in one Cargo workspace→](/30-projects/05-full-stack/) The project section also includes a WASM Game of Life and a production-shaped URL shortener. [Browse all six projects →](/30-projects/) ## start with the introduction [Section titled “start with the introduction”](#start-with-the-introduction) It explains the intended audience, prerequisites, suggested routes, and how to use the TS/JS comparisons without treating them as exact equivalents. [start the guide](/00-introduction/)[browse the projects](/30-projects/) github.com/zeybek/rs4ts.dev · MIT licensed · no analytics \>\_rs4ts A free guide to Rust for developers who already know TypeScript or JavaScript. [](https://github.com/zeybek/rs4ts.dev)[](https://github.com/zeybek/rs4ts.dev/stargazers)[](/llms.txt) [start reading](/00-introduction/)[projects](/30-projects/)[migration](/29-migration-guide/)[contributing](https://github.com/zeybek/rs4ts.dev/blob/main/CONTRIBUTING.md)[license](https://github.com/zeybek/rs4ts.dev/blob/main/LICENSE) © 2026 rs4ts.dev · MIT licensed · built by [zeybek](https://github.com/zeybek)six capstone crates are checked in CI # Introduction > Welcome to Rust for TS/JS Developers! This section introduces the guide and helps you get oriented. Welcome to **Rust for TS/JS Developers**! This section introduces the guide and helps you get oriented. *** ## What’s in This Section [Section titled “What’s in This Section”](#whats-in-this-section) * **[Target Audience](/00-introduction/00-target-audience/)** - Who this guide is for * **[How to Read This Guide](/00-introduction/01-how-to-read/)** - Learning strategies and navigation * **[Prerequisites](/00-introduction/02-prerequisites/)** - What you need to know before starting *** ## About This Guide [Section titled “About This Guide”](#about-this-guide) This is a thorough, book-style guide designed specifically for TypeScript and JavaScript developers who want to learn Rust. Unlike general Rust tutorials, this guide: ### Leverages Your Existing Knowledge [Section titled “Leverages Your Existing Knowledge”](#leverages-your-existing-knowledge) We assume you already know TypeScript/JavaScript well. Instead of explaining programming from scratch, we: * **Show familiar code first** - Start with TypeScript/JavaScript you already understand * **Compare side-by-side** - See the Rust equivalent next to familiar code * **Explain the differences** - Focus on what’s new, not what you already know * **Flag gotchas** - Point out common mistakes TS/JS developers make ### Example: Variables [Section titled “Example: Variables”](#example-variables) Instead of just teaching Rust variables, we compare: **TypeScript:** ```typescript 1 let x = 5; // mutable 2 const y = 10; // immutable 3 x = 6; // allowed 4 // y = 11; // error ``` **Rust:** ```rust 1 let x = 5; // immutable by default! 2 let mut y = 10; // explicitly mutable 3 // x = 6; // error - x is immutable 4 y = 11; // allowed - y is mut ``` **Key Difference:** Rust is immutable by default (opposite of JavaScript!) *** ## What You’ll Learn [Section titled “What You’ll Learn”](#what-youll-learn) ### Core Rust Concepts [Section titled “Core Rust Concepts”](#core-rust-concepts) 1. **The Ownership System** - Rust’s secret sauce for memory safety without garbage collection 2. **Borrowing & Lifetimes** - How Rust prevents data races at compile time 3. **Type System** - Stronger than TypeScript’s, but for good reasons 4. **Error Handling** - No exceptions, use Result and Option instead 5. **Pattern Matching** - Like switch statements on steroids 6. **Traits** - Similar to interfaces but more capable 7. **Async/Await** - Different syntax, same concepts 8. **Macros** - Compile-time code transforms (closer to Babel plugins than to decorators) ### Practical Skills [Section titled “Practical Skills”](#practical-skills) * Building REST APIs (Axum vs Express) * Working with databases (SQLx, Diesel) * Creating CLI tools (better performance than Node.js CLIs) * WebAssembly (run Rust in the browser) * Systems programming * Performance optimization * Production deployment *** ## Why Learn Rust? [Section titled “Why Learn Rust?”](#why-learn-rust) ### For TypeScript/JavaScript Developers [Section titled “For TypeScript/JavaScript Developers”](#for-typescriptjavascript-developers) **You should learn Rust if you want to:** * Build **high-performance backends** (no GC pauses; far lower p99 latency and memory use than Node.js) * Create **CLI tools** that start instantly (no Node.js startup time) * Eliminate **entire classes of bugs** at compile time * Work on **systems programming** (OS, embedded, game engines) * Add **Rust to JavaScript** via WebAssembly or native addons * Learn a language that **makes you a better programmer** **Rust might NOT be for you if:** * You only build simple web frontends (stick with TypeScript) * You need extremely fast development iteration (Node.js is faster to prototype) * Your team isn’t ready for the learning curve * You’re building CRUD apps where performance isn’t critical ### Real-World Use Cases [Section titled “Real-World Use Cases”](#real-world-use-cases) **Companies using Rust:** * **Discord** - Rewrote its Read States service from Go to Rust, eliminating GC latency spikes * **AWS** - Building Firecracker, Lambda runtime * **Microsoft** - Windows components, Azure services * **Meta** - Source control system (Sapling) * **Cloudflare** - Edge computing platform * **Dropbox** - File sync engine * **npm** - Registry authorization service **Common Rust Projects:** * Web servers (faster than Node.js) * CLI tools (replace Node.js CLIs) * Game engines * Blockchain/crypto * Embedded systems * Operating systems * Database engines * Network tools *** ## Guide Philosophy [Section titled “Guide Philosophy”](#guide-philosophy) ### 1. Side-by-Side Comparisons [Section titled “1. Side-by-Side Comparisons”](#1-side-by-side-comparisons) We **always** show TypeScript/JavaScript first, then Rust. This helps you: * Connect new concepts to existing knowledge * Understand why Rust does things differently * Spot patterns and differences quickly ### 2. Real-World Examples [Section titled “2. Real-World Examples”](#2-real-world-examples) No toy examples or contrived code. Every example: * Uses realistic scenarios * Includes proper error handling * Follows best practices * Could be used in production ### 3. Honest About Trade-offs [Section titled “3. Honest About Trade-offs”](#3-honest-about-trade-offs) We don’t pretend Rust is perfect. We’ll tell you: * When Rust is harder than TypeScript * When Node.js might be a better choice * Where the learning curve gets steep * How to overcome common struggles ### 4. Progressive Learning [Section titled “4. Progressive Learning”](#4-progressive-learning) Content is carefully sequenced: * Foundation → Core Language → Practical Skills → Advanced Topics → Production * Each section builds on previous ones * Dependencies are clearly marked * You can skip ahead, but we don’t recommend it *** ## How This Guide Is Different [Section titled “How This Guide Is Different”](#how-this-guide-is-different) ### vs. The Rust Book [Section titled “vs. The Rust Book”](#vs-the-rust-book) * **The Rust Book:** Thorough, assumes no prior programming knowledge * **This Guide:** Assumes strong TS/JS knowledge, focuses on differences ### vs. Rust by Example [Section titled “vs. Rust by Example”](#vs-rust-by-example) * **Rust by Example:** Brief examples with minimal explanation * **This Guide:** Detailed explanations with side-by-side comparisons ### vs. YouTube Tutorials [Section titled “vs. YouTube Tutorials”](#vs-youtube-tutorials) * **YouTube:** Sequential watching, hard to reference * **This Guide:** Written format, easy to search, return to, and reference ### vs. Rustlings [Section titled “vs. Rustlings”](#vs-rustlings) * **Rustlings:** Small exercises to learn by doing * **This Guide:** Full explanations + exercises **Best approach:** Use this guide as your primary resource, supplement with The Rust Book and Rustlings for practice. *** ## The Journey Ahead [Section titled “The Journey Ahead”](#the-journey-ahead) The guide moves through four phases: **Foundation** (sections 00-05, where ownership lives — take your time here), **Core Language** (06-10: data structures, collections, error handling, traits — you’ll start feeling productive), **Practical Skills** (11-19: web APIs, CLI tools, WASM — where Rust becomes fun), and **Advanced Topics** (20-30: production readiness and complete projects). Time estimates and suggested reading paths live in [How to Read This Guide](/00-introduction/01-how-to-read/). *** ## Overcoming the Learning Curve [Section titled “Overcoming the Learning Curve”](#overcoming-the-learning-curve) ### Common Struggles [Section titled “Common Struggles”](#common-struggles) **Week 1:** “Why won’t the compiler let me do this simple thing?” * Normal! The borrow checker is strict * Your code is probably not wrong, just needs restructuring * This teaches you to write better code **Week 2-3:** “I’m fighting with lifetimes!” * Also normal! Lifetimes are the hardest part * Most code doesn’t need explicit lifetime annotations * It gets easier with practice **Week 4+:** “Oh, I get it now!” * The “aha!” moment * Everything starts to make sense * You begin appreciating Rust’s design ### Tips for Success [Section titled “Tips for Success”](#tips-for-success) 1. **Don’t fight the compiler** - It’s trying to help you 2. **Read error messages carefully** - They’re very helpful 3. **Practice daily** - Even 30 minutes helps 4. **Join the community** - Discord, Reddit, forums 5. **Build projects** - Theory only goes so far 6. **Be patient** - Everyone struggles at first 7. **Celebrate small wins** - You’re learning a hard language! *** ## Next Steps [Section titled “Next Steps”](#next-steps) Ready to begin? Here’s your roadmap: 1. **You’re here!** - Introduction 2. **[Target Audience](/00-introduction/00-target-audience/)** - Make sure this guide is right for you 3. **[How to Read](/00-introduction/01-how-to-read/)** - Learn navigation strategies 4. **[Prerequisites](/00-introduction/02-prerequisites/)** - Verify you’re ready 5. **[Section 01: Getting Started](/01-getting-started/)** - Install Rust and write your first program! *** ## Questions? [Section titled “Questions?”](#questions) * **“Should I learn Rust or Go?”** - Different use cases. Rust for systems/performance, Go for simplicity/concurrency * **“Will this replace JavaScript?”** - No, different domains. But you might build backends in Rust * **“How long until I’m productive?”** - 3-4 weeks for basic projects, 2-3 months for production code * **“Is Rust dying?”** - No! It has topped Stack Overflow’s “most loved/admired language” ranking every year since 2016 * **“Should I learn Rust in 2026?”** - Yes! Adoption is growing, especially in backend/systems *** ## Let’s Begin! [Section titled “Let’s Begin!”](#lets-begin) You’re about to learn one of the most capable programming languages in existence. It will be challenging, but rewarding. ### [Continue to Target Audience](/00-introduction/00-target-audience/) [Section titled “Continue to Target Audience”](#continue-to-target-audience) # Target Audience > This guide is designed specifically for TypeScript and JavaScript developers who want to learn Rust. Let's make sure this is the right resource for you! This guide is designed specifically for TypeScript and JavaScript developers who want to learn Rust. Let’s make sure this is the right resource for you! *** ## This Guide Is Perfect For You If… [Section titled “This Guide Is Perfect For You If…”](#this-guide-is-perfect-for-you-if) ### You’re a TypeScript/JavaScript Developer [Section titled “You’re a TypeScript/JavaScript Developer”](#youre-a-typescriptjavascript-developer) * You have **1+ years** of TypeScript or JavaScript experience * You understand **async/await, promises, and callbacks** * You’ve worked with **npm/yarn** and modern JavaScript tooling * You’re comfortable with **types, interfaces, and generics** (TypeScript) * You’ve built **real applications** (not just tutorials) ### You Want to Learn Rust [Section titled “You Want to Learn Rust”](#you-want-to-learn-rust) * You’re curious about **systems programming** * You want to build **high-performance backends** * You’re interested in **WebAssembly** * You need to create **fast CLI tools** * You want to understand **memory management** without garbage collection * You’re looking for a **“JavaScript alternative”** for certain use cases ### Your Learning Style [Section titled “Your Learning Style”](#your-learning-style) * You learn best by **comparing to what you know** * You appreciate **detailed explanations** * You want **production-ready examples**, not toy code * You’re willing to invest **time to learn properly** * You prefer **written documentation** over video tutorials *** ## This Guide Might Not Be Ideal If… [Section titled “This Guide Might Not Be Ideal If…”](#this-guide-might-not-be-ideal-if) ### Your Background [Section titled “Your Background”](#your-background) * You have **less than 1 year** of programming experience * **Better choice:** Start with [The Rust Book](https://doc.rust-lang.org/book/) which assumes no prior knowledge * You’re **not familiar with TypeScript** or modern JavaScript (ES6+) * **Better choice:** Learn TypeScript first, then come back * You primarily use **vanilla JavaScript** and avoid types * **Note:** You can still use this guide, but TypeScript knowledge helps ### Your Goals [Section titled “Your Goals”](#your-goals) * You only want to build **simple web frontends** * **Stick with:** TypeScript/JavaScript - they’re perfect for that * You need to **ship something quickly** (this week/month) * **Better choice:** Use Node.js for now, learn Rust later * You’re looking for an **“easy” language** * **Reality:** Rust is challenging, but worth it ### Time Commitment [Section titled “Time Commitment”](#time-commitment) * You can only spare **1-2 hours total** * **Reality:** Rust requires 20+ hours minimum to be useful * You want to be **productive immediately** * **Reality:** Expect 3-4 weeks before comfortable productivity *** ## Which Sections Matter Most for You? [Section titled “Which Sections Matter Most for You?”](#which-sections-matter-most-for-you) Different backgrounds, different priorities. Find yourself in this table: | You are… | Why Rust fits | Start with | | ------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------ | | **Backend developer** (Node/Express/NestJS) hitting performance limits or cloud costs | No GC pauses, lower memory, compile-time safety | 16 (Web APIs), 17 (Database), 28 (Production) | | **Full-stack developer** (React/Next.js) expanding your toolkit | WebAssembly, fast CLI tools, lower-level understanding | 19 (WASM), 18 (CLI Tools) | | **Platform engineer** whose Bash/Python tooling is getting complex | Single static binary, no runtime deps, robust error handling | 18 (CLI), 24 (Tooling), 26 (Systems) | | **Career switcher** eyeing systems/blockchain/gamedev roles | Growing demand, future-proof skill | The complete path, in order | | **Performance optimizer** fighting Lambda cold starts and p99 latency | Predictable performance, small binaries, low latency | 16 (Web APIs), 21 (Performance), 28 (Production) | *** ## Skill Level Guide [Section titled “Skill Level Guide”](#skill-level-guide) ### Beginner Level (This Guide Starts Here) [Section titled “Beginner Level (This Guide Starts Here)”](#beginner-level-this-guide-starts-here) **Your TypeScript/JavaScript Skills:** * Can write functions, classes, async code * Understand types, interfaces, generics * Built at least one real application * Comfortable with npm and modern tools **Your Rust Skills:** * None required! * Never written Rust before? Perfect! * Tried Rust but struggled? This will help! **After This Guide:** * Comfortable writing Rust code * Can build web APIs, CLI tools * Understand ownership and lifetimes * Ready for real Rust projects *** ### Advanced Scenarios [Section titled “Advanced Scenarios”](#advanced-scenarios) **You’re a Rust beginner but want to learn from TS/JS perspective:** * Perfect! This guide is designed for you **You know some Rust but from other languages (C++, Java):** * This guide works, but comparisons are TS/JS focused * Still useful for different perspectives **You’re a Rust expert looking to teach TS/JS developers:** * Great! This guide shows you how to explain Rust concepts *** ## Not Sure If This Is For You? [Section titled “Not Sure If This Is For You?”](#not-sure-if-this-is-for-you) ### Try This Quick Assessment [Section titled “Try This Quick Assessment”](#try-this-quick-assessment) **Answer these questions:** 1. Can you explain what `async/await` does in TypeScript? 2. Have you used generics (`Array`, `Promise`) in TypeScript? 3. Can you explain how `strictNullChecks` changes the way you handle `null`/`undefined`? 4. Have you built something with Express, Next.js, or similar? 5. Are you willing to spend 20+ hours learning Rust basics? **Scoring:** * **5 Yes:** Perfect! You’re exactly who this guide is for * **4 Yes:** Great! You’ll do fine, might need to review TS concepts * **3 Yes:** Okay, but consider strengthening TypeScript first * **<3 Yes:** Build a TypeScript project first, then come back *** ## Alternative Resources [Section titled “Alternative Resources”](#alternative-resources) If this guide isn’t quite right, try these: ### Complete Beginners to Programming [Section titled “Complete Beginners to Programming”](#complete-beginners-to-programming) * **[The Rust Book](https://doc.rust-lang.org/book/)** - No prior experience needed * **[Rustlings](https://github.com/rust-lang/rustlings)** - Learn by small exercises ### Coming from Other Languages [Section titled “Coming from Other Languages”](#coming-from-other-languages) * **[Rust for C++ Programmers](https://github.com/nrc/r4cppp)** - If you know C++ * **[Rust for Java Developers](https://github.com/Dhghomon/programming_rust_for_java_devs)** - If you know Java ### Prefer Video Content [Section titled “Prefer Video Content”](#prefer-video-content) * **[Let’s Get Rusty](https://www.youtube.com/c/LetsGetRusty)** - Video tutorials * **[Rust Programming Course](https://www.udemy.com/course/rust-lang/)** - Udemy course ### Just Want to Experiment [Section titled “Just Want to Experiment”](#just-want-to-experiment) * **[Rust Playground](https://play.rust-lang.org/)** - Try Rust in browser * **[Tour of Rust](https://tourofrust.com/)** - Interactive tutorial *** ## Still Here? Let’s Go! [Section titled “Still Here? Let’s Go!”](#still-here-lets-go) If you’ve made it this far, you’re probably in the right place! Here’s what to do next: 1. **You’ve confirmed this guide is for you** 2. **Next: [How to Read This Guide](/00-introduction/01-how-to-read/)** - Learn navigation strategies 3. **Then: [Prerequisites](/00-introduction/02-prerequisites/)** - Make sure you’re ready 4. **Finally: [Section 01](/01-getting-started/)** - Start learning! *** ## Still Have Questions? [Section titled “Still Have Questions?”](#still-have-questions) Common questions from TypeScript/JavaScript developers: **Q: “I only know vanilla JavaScript, not TypeScript. Can I still use this?”**\ A: Yes, but it’ll be harder. Many examples use TypeScript syntax. Consider learning TypeScript basics first. **Q: “I’m a React developer. Is Rust relevant to me?”**\ A: For React only? Not really. But for WebAssembly, CLI tools, or backend work? Absolutely! **Q: “Will this make me abandon JavaScript?”**\ A: No! You’ll use both. Rust for performance-critical backend/systems work, TypeScript for everything else. **Q: “Is Rust harder than TypeScript?”**\ A: Yes, significantly. But this guide makes it easier by relating to what you know. **Q: “How much TypeScript do I need to know?”**\ A: Basic types, interfaces, generics, async/await. If you’ve shipped a TypeScript app, you’re good. # How to Read This Guide > This guide contains 31 sections and hundreds of pages. Here's how to navigate it effectively and get the most out of your learning experience. This guide contains 31 sections and hundreds of pages. Here’s how to navigate it effectively and get the most out of your learning experience. *** ## Navigation Strategies [Section titled “Navigation Strategies”](#navigation-strategies) ### Strategy 1: Sequential (Recommended for Most) [Section titled “Strategy 1: Sequential (Recommended for Most)”](#strategy-1-sequential-recommended-for-most) **Best for:** Beginners, those with time to learn properly **Path:** Read sections 00-30 in order ```plaintext 1 00 → 01 → 02 → 03 → 04 → 05 → ... → 30 ``` **Pros:** * Build strong foundation * No missing context * Everything makes sense **Cons:** * Takes roughly 300-400 hours with the projects and exercises * Slower to “useful” code **Timeline:** About 30-40 weeks at 10 hours/week *** ### Strategy 2: Fast Track (Essentials Only) [Section titled “Strategy 2: Fast Track (Essentials Only)”](#strategy-2-fast-track-essentials-only) **Best for:** Experienced developers, those needing quick results **Path:** Core sections only ```plaintext 1 00 → 01 → 02 → 03 → 04 → 05 → 08 → 11 → 16 ``` Chapter 05 (Ownership) is mandatory on this route; the later chapters assume its model. **Focus on:** * 01: Getting Started * 02: Basics * 03: Functions * 04: Control Flow * **05: Ownership** (Don’t skip!) * 08: Error Handling * 11: Async * 16: Web APIs **Pros:** * Focused route through the essentials (roughly 70-85 hours with practice) * Learn by building **Cons:** * Missing depth * Will need to revisit topics **Timeline:** About 7-9 weeks at 10 hours/week The time ranges in this page are derived from the chapter-level reading, practice, and exercise estimates. Treat them as planning ranges rather than deadlines. *** ### Strategy 3: Project-First (Learn by Doing) [Section titled “Strategy 3: Project-First (Learn by Doing)”](#strategy-3-project-first-learn-by-doing) **Best for:** Hands-on learners, those who hate theory **Path:** Jump to projects, backtrack when stuck ```plaintext 1 00 → 01 → 30 (Pick a project) 2 ↓ 3 Get stuck → Read relevant section 4 ↓ 5 Continue project ``` **Flow:** 1. Read sections 00-01 (basics) 2. Jump to section 30 (projects) 3. Pick a project (REST API, CLI tool, etc.) 4. Try to build it 5. When you don’t understand something, read that section 6. Return to project **Pros:** * Immediately practical * High motivation * Learn what you need, when you need it **Cons:** * Can be frustrating * Might develop bad habits * More time overall (lots of backtracking) **Timeline:** 4-6 weeks at 10-15 hours/week *** ### Strategy 4: Domain-Specific [Section titled “Strategy 4: Domain-Specific”](#strategy-4-domain-specific) **Best for:** Those with specific goals (web dev, CLI, etc.) #### For Web API Developers [Section titled “For Web API Developers”](#for-web-api-developers) ```plaintext 1 00 → 01 → 02 → 05 → 06 → 08 → 11 → 12 → 15 → 16 → 17 → 27 → 28 2 ↑ ↑ └─→ Database 3 Basics Ownership Security & Production ``` #### For CLI Tool Developers [Section titled “For CLI Tool Developers”](#for-cli-tool-developers) ```plaintext 1 00 → 01 → 02 → 05 → 06 → 07 → 08 → 12 → 13 → 18 → 24 2 Basics Own. Data Collections CLI Testing Tooling ``` #### For Systems Programmers [Section titled “For Systems Programmers”](#for-systems-programmers) ```plaintext 1 00 → 01 → 02 → 05 → 10 → 20 → 21 → 26 2 Basics Own. Smart Unsafe Perf Systems 3 Ptr. ``` #### For WebAssembly Developers [Section titled “For WebAssembly Developers”](#for-webassembly-developers) ```plaintext 1 00 → 01 → 02 → 05 → 06 → 12 → 15 → 19 2 Basics Own. Data Mods Serde WASM ``` *** ## How Each Section Is Organized [Section titled “How Each Section Is Organized”](#how-each-section-is-organized) ### Section Structure [Section titled “Section Structure”](#section-structure) Every section follows this pattern: ```plaintext 1 XX-section-name/ 2 ├── index.md # Section overview, navigation 3 ├── 00-topic-1.md # Individual topics 4 ├── 01-topic-2.md 5 └── ... ``` ### Topic File Structure [Section titled “Topic File Structure”](#topic-file-structure) Each **concept topic** includes the parts below. (Orientation pages like this one, and the capstone projects in Section 30, use a tailored subset. For example, projects end with “Extending It” instead of graded exercises.) 1. **Quick Overview** - 2-3 sentence summary 2. **TypeScript/JavaScript Example** - Code you know 3. **Rust Equivalent** - The Rust way 4. **Detailed Explanation** - How and why 5. **Key Differences** - Important changes from TS/JS 6. **Common Pitfalls** - What TS/JS devs get wrong 7. **Best Practices** - Idiomatic Rust 8. **Real-World Example** - Production code 9. **Further Reading** - Additional resources 10. **Exercises** - Practice problems *** ## How to Approach Each Topic [Section titled “How to Approach Each Topic”](#how-to-approach-each-topic) ### Step 1: Read the Overview [Section titled “Step 1: Read the Overview”](#step-1-read-the-overview) Understand what you’re about to learn and why it matters. ### Step 2: Study the TypeScript Example [Section titled “Step 2: Study the TypeScript Example”](#step-2-study-the-typescript-example) This should be familiar. If it’s not, review TypeScript first. ### Step 3: Compare to Rust [Section titled “Step 3: Compare to Rust”](#step-3-compare-to-rust) Look for similarities and differences. Don’t just read the Rust code: compare it line by line to the TypeScript. ### Step 4: Read the Explanation [Section titled “Step 4: Read the Explanation”](#step-4-read-the-explanation) Understand not just “how” but “why” Rust does it this way. ### Step 5: Note the Pitfalls [Section titled “Step 5: Note the Pitfalls”](#step-5-note-the-pitfalls) These are real mistakes TS/JS developers make. Avoid them! ### Step 6: Try It Yourself [Section titled “Step 6: Try It Yourself”](#step-6-try-it-yourself) ```bash 1 # Create a playground 2 cargo new try_this 3 cd try_this 4 5 # Edit src/main.rs 6 # Try the examples yourself! 7 cargo run ``` ### Step 7: Do the Exercises [Section titled “Step 7: Do the Exercises”](#step-7-do-the-exercises) If provided, complete the exercises. They reinforce learning. *** ## Time Management [Section titled “Time Management”](#time-management) ### Daily Learning (30-60 minutes) [Section titled “Daily Learning (30-60 minutes)”](#daily-learning-30-60-minutes) **Best for:** Busy professionals ```plaintext 1 Day 1: Read one topic (30 min) 2 Day 2: Try examples, exercises (30 min) 3 Day 3: Read next topic (30 min) 4 Day 4: Review and practice (30 min) 5 Day 5: Build something small (1 hour) 6 Weekend: Longer project work ``` **Progress:** Complete guide in 2-3 months ### Weekend Warrior (5-10 hours/week) [Section titled “Weekend Warrior (5-10 hours/week)”](#weekend-warrior-5-10-hoursweek) **Best for:** Those with weekday commitments ```plaintext 1 Saturday: 3-4 hours learning 2 Sunday: 2-3 hours practicing 3 Weekdays: Quick reviews (15 min/day) ``` **Progress:** Complete guide in 1.5-2 months ### Intensive (20+ hours/week) [Section titled “Intensive (20+ hours/week)”](#intensive-20-hoursweek) **Best for:** Bootcamp style, career transition ```plaintext 1 Morning: 2-3 hours reading/learning 2 Afternoon: 2-3 hours practicing 3 Evening: 1-2 hours review/projects ``` **Progress:** Complete guide in 3-4 weeks *** ## Reading Tips [Section titled “Reading Tips”](#reading-tips) ### Active Reading [Section titled “Active Reading”](#active-reading) **Don’t just read - engage!** **Bad:** Skim through, think “that makes sense” **Good:** Type every example, modify it, break it, fix it **Bad:** Read code once, move on **Good:** Read it 3 times - once for syntax, once for meaning, once for patterns **Bad:** Skip exercises **Good:** Do all exercises, even if they seem easy ### Taking Notes [Section titled “Taking Notes”](#taking-notes) **Create a Rust learning journal:** ```markdown 1 # Day 1: Variables and Mutability 2 3 ## Key Insight 4 5 Rust is immutable by default! Opposite of JS. 6 7 ## Syntax I Need to Remember 8 9 let x = 5; // immutable 10 let mut y = 5; // mutable 11 12 ## Gotcha 13 14 Forgot `mut` and spent 10 minutes debugging why 15 assignment failed. Compiler error was helpful though! 16 17 ## Try Tomorrow 18 19 Practice with loops and mutable counters ``` ### When You’re Stuck [Section titled “When You’re Stuck”](#when-youre-stuck) **Follow this process:** 1. **Read the error message** - Rust errors are helpful! 2. **Check the relevant section** - Find the topic 3. **Try the Rust Playground** - Isolate the problem 4. **Ask for help** - Discord, Reddit, Stack Overflow 5. **Take a break** - Sometimes you just need to sleep on it *** ## Bookmark These [Section titled “Bookmark These”](#bookmark-these) Keep these sections handy for quick reference: ### Essential References [Section titled “Essential References”](#essential-references) * **[05 - Ownership](/05-ownership/)** - You’ll refer back constantly * **[07 - Collections](/07-collections/)** - String and Vec APIs * **[08 - Error Handling](/08-error-handling/)** - Result and Option * **[09 - Traits](/09-generics-traits/)** - Common trait patterns ### Quick Lookups [Section titled “Quick Lookups”](#quick-lookups) * **[02 - Basics](/02-basics/)** - Type conversion, operators * **[12 - Modules](/12-modules-packages/)** - Import syntax * **[24 - Tooling](/24-tooling/)** - Cargo commands *** ## Study Aids [Section titled “Study Aids”](#study-aids) ### Cheat Sheets (Create Your Own!) [Section titled “Cheat Sheets (Create Your Own!)”](#cheat-sheets-create-your-own) **TypeScript → Rust Quick Reference:** ```typescript 1 // TypeScript 2 const x = 5; 3 let y = [1, 2, 3]; 4 async function f() { ... } 5 interface User { name: string } ``` ```rust 1 // Rust 2 let x = 5; 3 let mut y = vec![1, 2, 3]; 4 async fn f() { ... } 5 struct User { name: String } ``` ### Flashcards [Section titled “Flashcards”](#flashcards) Use Anki, Quizlet, or paper cards for: * Syntax conversions * Ownership rules * Common patterns * Error types ### Practice Projects [Section titled “Practice Projects”](#practice-projects) After each major section, build something: * **After 05:** Simple CLI calculator * **After 08:** Error-handling file reader * **After 11:** Async HTTP client * **After 16:** Basic REST API * **After 30:** Your own project! *** ## Learning with Others [Section titled “Learning with Others”](#learning-with-others) ### Study Groups [Section titled “Study Groups”](#study-groups) Find or create a study group: * Discord channels * Local meetups * Online cohorts * Pair programming **Benefits:** * Stay motivated * Learn from others’ questions * Teach to reinforce learning * Make friends in Rust community ### Code Review [Section titled “Code Review”](#code-review) Share your code: * Reddit r/rust (helpful community!) * Discord #beginners channel * GitHub discussions * Stack Overflow *** ## Track Your Progress [Section titled “Track Your Progress”](#track-your-progress) ### Section Checklist [Section titled “Section Checklist”](#section-checklist) Keep track of what you’ve completed: ```plaintext 1 [ ] 00 - Introduction 2 [ ] 01 - Getting Started 3 [ ] 02 - Basics 4 ... 5 [ ] 30 - Projects ``` ### Skill Assessment [Section titled “Skill Assessment”](#skill-assessment) Periodically check yourself: **Week 1:** * Can I create a new Cargo project? * Do I understand mutable vs immutable? * Have I written a basic function? **Week 2:** * Do I understand ownership? * Can I use borrowing correctly? * Have I successfully used Vec and String? **Week 4:** * Can I handle errors with Result? * Do I understand traits? * Have I written async code? **Week 8:** * Can I build a REST API? * Do I understand lifetimes? * Have I shipped a Rust project? *** ## Goal Setting [Section titled “Goal Setting”](#goal-setting) ### Set Clear Milestones [Section titled “Set Clear Milestones”](#set-clear-milestones) **Bad goal:** “Learn Rust”\ **Good goal:** “Build a REST API that handles user authentication by end of month” **Bad goal:** “Understand ownership”\ **Good goal:** “Complete section 05 and build a CLI tool that manipulates strings without compiler errors” ### Celebrate Wins [Section titled “Celebrate Wins”](#celebrate-wins) Learning Rust is hard! Celebrate progress: * First successful compile * First program without compiler errors * Understanding ownership * First async program * First production code * Helping another beginner *** ## What NOT to Do [Section titled “What NOT to Do”](#what-not-to-do) ### Common Mistakes [Section titled “Common Mistakes”](#common-mistakes) **Skipping Section 05 (Ownership)** → You’ll be confused forever. Don’t skip it! **Rushing through examples** → Type them out, don’t just read **Comparing to JavaScript too much** → Rust is different. Embrace it! **Fighting the compiler** → It’s helping you. Listen to it! **Learning in isolation** → Join the community, ask questions **Giving up after Day 3** → Week 1 is the hardest. Push through! **Trying to memorize everything** → Focus on understanding, reference docs for details *** ## After Completing This Guide [Section titled “After Completing This Guide”](#after-completing-this-guide) ### What’s Next? [Section titled “What’s Next?”](#whats-next) 1. **Build a real project** - Not a tutorial, something you’ll use 2. **Contribute to open source** - Find a Rust project 3. **Read advanced resources** - [Rust for Rustaceans](https://rust-for-rustaceans.com/) 4. **Keep practicing** - Skills decay without use 5. **Teach others** - Best way to solidify knowledge ### Staying Current [Section titled “Staying Current”](#staying-current) Rust evolves. Stay updated: * [This Week in Rust](https://this-week-in-rust.org/) * [Rust Blog](https://blog.rust-lang.org/) * [Reddit r/rust](https://reddit.com/r/rust) * [Rust Discord](https://discord.gg/rust-lang) *** ## Getting Help [Section titled “Getting Help”](#getting-help) ### When You’re Stuck [Section titled “When You’re Stuck”](#when-youre-stuck-1) **Good question template:** ```plaintext 1 I'm trying to [specific goal]. 2 3 I expected [what you thought would happen]. 4 5 Instead, [what actually happened]. 6 7 Here's my code: [minimal example] 8 9 Error message: [full error] 10 11 I've tried: [what you've attempted] ``` ### Where to Ask [Section titled “Where to Ask”](#where-to-ask) * **Beginners:** [Discord #beginners](https://discord.gg/rust-lang) * **Code review:** [r/rust](https://reddit.com/r/rust) * **Specific errors:** Stack Overflow * **General discussion:** [Rust Users Forum](https://users.rust-lang.org/) *** ## You’re Ready! [Section titled “You’re Ready!”](#youre-ready) You now know how to navigate this guide effectively. Choose your strategy and let’s begin! ### Your Next Steps [Section titled “Your Next Steps”](#your-next-steps) 1. **You know how to use this guide** 2. **Next: [Prerequisites](/00-introduction/02-prerequisites/)** - Verify you’re ready 3. **Then: [Section 01](/01-getting-started/)** - Start coding! # Prerequisites > Before you start Rust, let's make sure you have the necessary background knowledge and tools. This will set you up for a smooth learning experience. Before you start Rust, let’s make sure you have the necessary background knowledge and tools. This will set you up for a smooth learning experience. *** ## Required Knowledge [Section titled “Required Knowledge”](#required-knowledge) ### 1. TypeScript/JavaScript Experience (Required) [Section titled “1. TypeScript/JavaScript Experience (Required)”](#1-typescriptjavascript-experience-required) You should be comfortable with: #### Basic Programming [Section titled “Basic Programming”](#basic-programming) ```typescript 1 // Variables and types 2 let name: string = "Alice"; 3 const age: number = 30; 4 let isActive: boolean = true; 5 6 // Functions 7 function greet(name: string): string { 8 return `Hello, ${name}!`; 9 } 10 11 // Arrow functions 12 const add = (a: number, b: number): number => a + b; ``` **Why it matters:** These map directly to Rust concepts. If this looks unfamiliar, review JavaScript/TypeScript basics first. #### Arrays and Objects [Section titled “Arrays and Objects”](#arrays-and-objects) ```typescript 1 // Arrays 2 const numbers: number[] = [1, 2, 3, 4, 5]; 3 const doubled = numbers.map((n) => n * 2); 4 const filtered = numbers.filter((n) => n > 2); 5 6 // Objects and interfaces 7 interface User { 8 id: number; 9 name: string; 10 email: string; 11 } 12 13 const user: User = { 14 id: 1, 15 name: "Alice", 16 email: "alice@example.com", 17 }; ``` **Why it matters:** Collections work similarly in Rust (Vec, HashMap, etc.) #### Async/Await [Section titled “Async/Await”](#asyncawait) ```typescript 1 // Promises and async/await 2 async function fetchUser(id: number): Promise { 3 const response = await fetch(`/api/users/${id}`); 4 const data = await response.json(); 5 return data; 6 } 7 8 // Error handling 9 try { 10 const user = await fetchUser(1); 11 console.log(user); 12 } catch (error) { 13 console.error("Failed:", error); 14 } ``` **Why it matters:** Rust has async/await too, with similar (but not identical) syntax. #### Generics [Section titled “Generics”](#generics) ```typescript 1 // Generic functions 2 function first(arr: T[]): T | undefined { 3 return arr[0]; 4 } 5 6 // Generic interfaces 7 interface Response { 8 data: T; 9 status: number; 10 } 11 12 const userResponse: Response = { 13 data: user, 14 status: 200, 15 }; ``` **Why it matters:** Rust’s generics are similar but can do more. #### Modules and Imports [Section titled “Modules and Imports”](#modules-and-imports) ```typescript 1 // Importing 2 import { User, fetchUser } from "./api"; 3 import type { Config } from "./config"; 4 5 // Exporting 6 export interface Product { 7 id: number; 8 name: string; 9 } 10 11 export function getProducts(): Product[] { 12 // ... 13 } ``` **Why it matters:** Rust’s module system is similar conceptually. *** ### 2. Command Line Basics (Required) [Section titled “2. Command Line Basics (Required)”](#2-command-line-basics-required) You should know how to: ```bash 1 # Navigate directories 2 cd ~/projects 3 ls 4 pwd 5 6 # Create files and directories 7 mkdir my-project 8 touch file.txt 9 10 # Run commands 11 node app.js 12 npm install 13 npm start 14 15 # View file contents 16 cat README.md 17 18 # Basic git 19 git clone 20 git add . 21 git commit -m "message" ``` **Why it matters:** You’ll use terminal commands for Cargo and rustc. *** ### 3. Development Tools (Required) [Section titled “3. Development Tools (Required)”](#3-development-tools-required) #### Text Editor/IDE [Section titled “Text Editor/IDE”](#text-editoride) You need a text editor. Recommended options: **VS Code** (Most popular) ```bash 1 # Install VS Code 2 # Then install rust-analyzer extension 3 code --install-extension rust-lang.rust-analyzer ``` **Other options:** * **IntelliJ IDEA** with Rust plugin * **Vim/Neovim** with rust.vim * **Emacs** with rust-mode * **Sublime Text** with Rust Enhanced **Why it matters:** You’ll write lots of Rust code! #### Terminal/Shell [Section titled “Terminal/Shell”](#terminalshell) * macOS/Linux: Built-in terminal * Windows: PowerShell, WSL2, or Git Bash *** ## Helpful but Not Required [Section titled “Helpful but Not Required”](#helpful-but-not-required) ### Nice to Have [Section titled “Nice to Have”](#nice-to-have) #### System Programming Concepts [Section titled “System Programming Concepts”](#system-programming-concepts) If you’ve heard of these, great! If not, don’t worry, we’ll explain: * **Stack vs Heap** - We’ll teach you * **Pointers** - Rust handles them safely * **Memory management** - Rust’s ownership system makes it easier * **Compilation** - You’ll learn as you go #### Other Compiled Languages [Section titled “Other Compiled Languages”](#other-compiled-languages) Experience with these helps but isn’t necessary: * C, C++, Go, Java, C#, Swift, Kotlin **If you have:** Great! Some concepts will feel familiar\ **If you don’t:** Perfect! You’ll learn Rust’s way without bad habits *** ## What You DON’T Need [Section titled “What You DON’T Need”](#what-you-dont-need) ### You Don’t Need to Know [Section titled “You Don’t Need to Know”](#you-dont-need-to-know) **Computer Science degree** - Nope! **Low-level programming** - We’ll teach you **C or C++** - Actually better if you don’t (fewer preconceptions) **Assembly** - Not at all **Operating systems** - Helpful but not required **Math** - Just basic programming logic ### You Don’t Need to Be [Section titled “You Don’t Need to Be”](#you-dont-need-to-be) **A “10x developer”** - Everyone starts somewhere **A CS major** - Self-taught? Perfect! **Young** - Age doesn’t matter **A native English speaker** - Code is universal *** ## System Requirements [Section titled “System Requirements”](#system-requirements) ### Operating System [Section titled “Operating System”](#operating-system) Rust works on: * **Linux** (any distro) * **macOS** (10.7+) * **Windows** (7+, but 10+ recommended) * **BSD, etc.** **Note for Windows users:** Consider using [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install) for a better experience. Or use PowerShell/CMD - both work fine. ### Hardware [Section titled “Hardware”](#hardware) **Minimum:** * 2 GB RAM (4 GB recommended) * 2 GB disk space * Any processor from the last 10 years **Why it matters:** Rust compilation can be slow on old hardware. ### Internet Connection [Section titled “Internet Connection”](#internet-connection) Required for: * Installing Rust * Downloading dependencies (crates) * Accessing documentation **Tip:** Once you’ve downloaded crates, you can work offline. *** ## Required Software [Section titled “Required Software”](#required-software) ### 1. Node.js (for TypeScript examples) [Section titled “1. Node.js (for TypeScript examples)”](#1-nodejs-for-typescript-examples) ```bash 1 # Check if installed 2 node --version # Should be 22+ (any maintained LTS works) 3 npm --version # Comes bundled with Node 4 5 # If not installed: https://nodejs.org/ ``` **Why:** We’ll compare TypeScript and Rust code side-by-side. ### 2. TypeScript (optional but recommended) [Section titled “2. TypeScript (optional but recommended)”](#2-typescript-optional-but-recommended) ```bash 1 # Install globally 2 npm install -g typescript 3 4 # Verify 5 tsc --version ``` **Why:** To run the TypeScript examples in this guide. ### 3. Git (recommended) [Section titled “3. Git (recommended)”](#3-git-recommended) ```bash 1 # Check if installed 2 git --version 3 4 # If not installed: https://git-scm.com/ ``` **Why:** For cloning examples and managing your code. *** ## Pre-Learning Checklist [Section titled “Pre-Learning Checklist”](#pre-learning-checklist) Before starting Section 01, verify: ### Knowledge Checklist [Section titled “Knowledge Checklist”](#knowledge-checklist) * [ ] I can write TypeScript functions with type annotations * [ ] I understand async/await and Promises * [ ] I’ve used generics (`Array`, `Promise`) * [ ] I know basic terminal commands (cd, ls, mkdir) * [ ] I’ve built at least one TypeScript/JavaScript application ### Tools Checklist [Section titled “Tools Checklist”](#tools-checklist) * [ ] I have a text editor/IDE installed * [ ] I can open and use a terminal * [ ] I have Node.js installed (node —version works) * [ ] I have Git installed (optional but recommended) * [ ] I have at least 2 GB free disk space ### Mindset Checklist [Section titled “Mindset Checklist”](#mindset-checklist) * [ ] I’m ready to invest 20+ hours learning * [ ] I’m okay with compiler errors (they’re helpful!) * [ ] I’m willing to think differently about programming *** ## Quick Self-Assessment [Section titled “Quick Self-Assessment”](#quick-self-assessment) ### Test Your TypeScript Knowledge [Section titled “Test Your TypeScript Knowledge”](#test-your-typescript-knowledge) Try to answer these without looking them up: **Question 1:** ```typescript 1 const numbers = [1, 2, 3, 4, 5]; 2 const result = numbers.map((n) => n * 2).filter((n) => n > 5); ``` What is `result`? Answer `[6, 8, 10]` - Numbers doubled, then filtered to only values > 5 **Question 2:** ```typescript 1 interface User { 2 name: string; 3 age?: number; 4 } ``` What does the `?` mean? Answer Optional property - `age` may or may not be present **Question 3:** ```typescript 1 async function fetchData(): Promise { 2 const response = await fetch("/api/data"); 3 return response.text(); 4 } ``` What does this function return? Answer A `Promise` - The function is async, so it always returns a Promise **Scoring:** * **3/3:** Perfect! You’re ready * **2/3:** Good, review weak areas * **1/3 or less:** Review TypeScript basics first *** ## Recommended Pre-Reading (Optional) [Section titled “Recommended Pre-Reading (Optional)”](#recommended-pre-reading-optional) If you want to prepare even more: ### For TypeScript Review [Section titled “For TypeScript Review”](#for-typescript-review) * [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) * [TypeScript Deep Dive](https://basarat.gitbook.io/typescript/) ### For Programming Concepts [Section titled “For Programming Concepts”](#for-programming-concepts) * [JavaScript.info](https://javascript.info/) - Modern JavaScript * [MDN Web Docs](https://developer.mozilla.org/) - Web APIs ### For Command Line [Section titled “For Command Line”](#for-command-line) * [Command Line Crash Course](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line) *** ## Ready to Install Rust? [Section titled “Ready to Install Rust?”](#ready-to-install-rust) If you’ve checked all the boxes above, you’re ready! ### What’s Next? [Section titled “What’s Next?”](#whats-next) 1. **You’ve verified prerequisites** 2. **Next: [Section 01 - Getting Started](/01-getting-started/)** 3. **Install Rust and write your first program!** *** ## Still Not Sure? [Section titled “Still Not Sure?”](#still-not-sure) ### Common Concerns [Section titled “Common Concerns”](#common-concerns) **“I’ve only used JavaScript, not TypeScript.”** → That’s okay! You’ll learn TypeScript concepts as we compare to Rust. Just note that examples use TypeScript syntax. **“I’ve never compiled a program before.”** → Perfect! We’ll walk you through it. Compilation is just turning code into an executable. **“The terminal scares me.”** → Don’t worry! We’ll explain every command. You’ll be comfortable with it soon. **“I don’t have much time.”** → Start with 30 minutes a day. Progress is progress! **“What if I get stuck?”** → Join the [Rust Discord](https://discord.gg/rust-lang) - the community is super helpful! **“Am I too old/young/etc. to learn Rust?”** → Absolutely not! If you can write TypeScript, you can learn Rust. *** ## You’re All Set! [Section titled “You’re All Set!”](#youre-all-set) You have the knowledge and tools needed. Time to write some Rust! # JavaScript & TypeScript to Rust Cheatsheet > The fast map from JavaScript and TypeScript to Rust: variables, array methods, null handling, promises, classes, and modules, each linked to a full chapter. A single-page lookup for the question you will ask most often while learning Rust: *“what is the Rust equivalent of this JavaScript or TypeScript thing?”* Each row pairs the code you already write on the left with its idiomatic Rust counterpart on the right. The mappings are deliberately terse; every section links to the full chapter where the *why* lives. If you only skim one page before starting, make it this one, then keep it open in a tab. *** ## Variables and types [Section titled “Variables and types”](#variables-and-types) | TypeScript / JavaScript | Rust | | ------------------------------------- | ---------------------------------------------------------------------------------------- | | `let x = 1` (reassignable) | `let mut x = 1;` | | `const x = 1` (no reassign) | `let x = 1;` (immutable by default) | | `const MAX = 100` (true constant) | `const MAX: i32 = 100;` | | reassign with a new type | shadowing: `let x = 5; let x = "five";` | | `number` | sized: `i32`, `i64`, `u32`, `usize`, `f64` | | `bigint` (arbitrary precision) | `i128` / `u128` (fixed 128-bit; for true arbitrary precision use the `num-bigint` crate) | | `string` | `String` (owned) and `&str` (borrowed) | | `boolean` | `bool` | | `null` / `undefined` | `Option` with `None` | | `any` | no equivalent; reach for generics, an `enum`, or `serde_json::Value` | | `[1, 2, 3]` | `vec![1, 2, 3]` (a growable `Vec`) | | fixed-length tuple `[string, number]` | `(String, i32)` | | `{ id: 1 }` object | a `struct`, or a `HashMap` for dynamic keys | See: [Variables and Mutability](/02-basics/00-variables/), [Basic Types](/02-basics/01-types/), [Stack vs Heap](/05-ownership/00-stack-heap/). *** ## Functions and closures [Section titled “Functions and closures”](#functions-and-closures) | TypeScript / JavaScript | Rust | | -------------------------------------------- | -------------------------------------------------- | | `function add(a: number, b: number): number` | `fn add(a: i32, b: i32) -> i32` | | `return a + b;` | `a + b` (last expression, no `return`, no `;`) | | `(x) => x + 1` | `\|x\| x + 1` | | capturing closure `() => count++` | `move \|\| count += 1` (see `Fn`/`FnMut`/`FnOnce`) | | default parameter `f(a = 1)` | no defaults; take `Option` or use a builder | | rest parameter `...args` | a slice `args: &[T]` | | `void` return | `()` (the unit type) | | pass a function `g(f)` | `fn g(f: impl Fn() -> T)` | See: [Basic Functions](/03-functions/00-basic-functions/), [Parameters](/03-functions/01-parameters/), [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/), [Higher-Order Functions](/03-functions/04-higher-order/). *** ## Control flow [Section titled “Control flow”](#control-flow) | TypeScript / JavaScript | Rust | | ----------------------------- | ----------------------------------------------- | | `cond ? a : b` | `if cond { a } else { b }` (an expression) | | `if (value)` (truthy) | `if value` (must be a real `bool`) | | `switch (x) { ... }` | `match x { ... }` (exhaustive, no fall-through) | | `for (let i = 0; i < n; i++)` | `for i in 0..n` | | `for (const x of arr)` | `for x in &arr` | | `arr.forEach(f)` | `arr.iter().for_each(f)` or a `for` loop | | `while (cond)` | `while cond` | | `while (true) { ... }` | `loop { ... }` (can `break value`) | | labelled `break outer` | `'outer: loop { break 'outer; }` | See: [Conditionals](/04-control-flow/00-conditionals/), [Match](/04-control-flow/02-match/), [Loops](/04-control-flow/01-loops/), [if let / while let](/04-control-flow/03-if-let-while-let/). *** ## Null, undefined, and errors [Section titled “Null, undefined, and errors”](#null-undefined-and-errors) There is no `null` and no `undefined` in Rust. Absence is the `None` variant of `Option`, and a failure is the `Err` variant of `Result`. The type system makes you handle both. | TypeScript / JavaScript | Rust | | --------------------------------- | ---------------------------------------------- | | `value ?? fallback` | `option.unwrap_or(fallback)` | | `obj?.prop` (optional chaining) | `option.map(\|o\| o.prop)` / `.and_then(...)` | | `if (x != null) { use(x) }` | `if let Some(x) = option { use(x) }` | | `throw new Error("boom")` | `return Err(MyError::Boom)` | | `try { ... } catch (e) { ... }` | `match result { Ok(v) => ..., Err(e) => ... }` | | `const v = await f()` (may throw) | `let v = f().await?;` | | rethrow / propagate | the `?` operator | | `class HttpError extends Error` | `enum AppError { ... }` with `thiserror` | See: [Result and Option](/08-error-handling/00-result-option/), [The `?` Operator](/08-error-handling/01-question-mark/), [Option Enum](/06-data-structures/03-option-enum/), [Custom Errors](/08-error-handling/04-custom-errors/). *** ## Collections and array methods [Section titled “Collections and array methods”](#collections-and-array-methods) Most array methods exist in Rust, but on **iterators**, and they are **lazy**: nothing runs until a consumer such as `.collect()`, `.sum()`, or a `for` loop pulls the values through. | TypeScript / JavaScript | Rust | | ------------------------------------- | --------------------------------------------------- | | `arr.map(f)` | `arr.iter().map(f).collect()` | | `arr.filter(f)` | `arr.iter().filter(f).collect()` | | `arr.reduce(f, init)` | `arr.iter().fold(init, f)` | | `arr.find(f)` | `arr.iter().find(f)` (returns `Option`) | | `arr.some(f)` / `arr.every(f)` | `arr.iter().any(f)` / `.all(f)` | | `arr.includes(x)` | `arr.contains(&x)` | | `arr.push(x)` / `arr.length` | `vec.push(x)` / `vec.len()` | | `arr.slice(a, b)` | `&vec[a..b]` | | `arr.sort()` | `vec.sort()` | | `[...a, ...b]` | `a.iter().chain(&b).collect()` | | `new Map()` / `map.get(k)` | `HashMap::new()` / `map.get(&k)` (returns `Option`) | | `new Set()` | `HashSet::new()` | | `Object.keys(o)` / `Object.values(o)` | `map.keys()` / `map.values()` | | `Array.from({ length: n }, ...)` | `(0..n).map(...).collect()` | See: [Vectors](/07-collections/00-vectors/), [Iterators](/07-collections/06-iterators/), [Iterator Consumers](/07-collections/07-iterator-consumers/), [HashMaps](/07-collections/03-hashmaps/). *** ## Strings [Section titled “Strings”](#strings) | TypeScript / JavaScript | Rust | | ---------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `"hello " + name` | `format!("hello {name}")` | | `` `total: ${n}` `` (template) | `format!("total: {n}")` | | `s.length` (UTF-16 code units — `"🎉".length === 2`) | `s.chars().count()` (Unicode scalars) or `s.len()` (UTF-8 bytes); neither matches JS exactly | | `s.toUpperCase()` | `s.to_uppercase()` | | `s.split(",")` | `s.split(',')` | | `s.includes("x")` | `s.contains("x")` | | `s.trim()` | `s.trim()` | | `s.replace(a, b)` | `s.replace(a, b)` | | `s.startsWith("/")` | `s.starts_with('/')` | | accept a string argument | take `&str`, return `String` | See: [Strings](/07-collections/01-strings/), [String Manipulation](/07-collections/02-string-manipulation/). *** ## Structs, enums, classes, and interfaces [Section titled “Structs, enums, classes, and interfaces”](#structs-enums-classes-and-interfaces) Rust has no classes and no inheritance. Data lives in a `struct` or `enum`; behaviour lives in `impl` blocks; shared behaviour is a `trait` (an interface you can implement for any type). | TypeScript / JavaScript | Rust | | ------------------------------- | -------------------------------------------- | | `interface User { id: number }` | `struct User { id: i32 }` | | `class C { method() {} }` | `struct C; impl C { fn method(&self) {} }` | | `type Shape = Circle \| Square` | `enum Shape { Circle, Square }` | | discriminated union with data | `enum` variants carry data | | `implements Serializable` | `impl Serializable for T` | | `extends Base` (inheritance) | composition plus traits (no inheritance) | | `this` | `&self`, `&mut self`, or `self` | | `new C(args)` | `C::new(args)` (a convention, not a keyword) | | `instanceof` | `match` on an `enum` or a trait object | | generic `class Box` | `struct Box` with trait bounds | See: [Structs](/06-data-structures/00-structs/), [Enums](/06-data-structures/02-enums/), [impl Blocks](/06-data-structures/05-impl-blocks/), [Traits](/09-generics-traits/03-traits/), [Trait Objects](/09-generics-traits/06-trait-objects/). *** ## Async [Section titled “Async”](#async) The keywords match, but Rust futures are **lazy** (they do nothing until `.await`ed) and there is no built-in event loop, so you pick a runtime such as Tokio. | TypeScript / JavaScript | Rust | | ------------------------------- | ----------------------------------------------------- | | `Promise` | `impl Future` | | `async function f()` | `async fn f()` | | `await p` | `p.await` | | `Promise.all([a, b])` | `tokio::join!(a, b)` / `futures::future::join_all` | | `Promise.race([a, b])` | `tokio::select!` | | built-in event loop | a runtime via `#[tokio::main]` | | `setTimeout(fn, ms)` | `tokio::time::sleep(Duration::from_millis(ms)).await` | | `for await (const x of stream)` | `while let Some(x) = stream.next().await` | See: [Promises vs Futures](/11-async/00-promises-vs-futures/), [async/await](/11-async/01-async-await/), [select and join](/11-async/07-select-join/), [Async vs Sync](/11-async/13-async-vs-sync/). *** ## Modules, packages, and tooling [Section titled “Modules, packages, and tooling”](#modules-packages-and-tooling) | TypeScript / JavaScript | Rust | | ------------------------- | ---------------------------------------- | | `import { x } from "./m"` | `use crate::m::x;` | | `export function f()` | `pub fn f()` | | `export default` | no default export; name the item | | a file is a module | declare modules with `mod` | | `package.json` | `Cargo.toml` | | `npm install serde` | `cargo add serde` | | `npm run build` | `cargo build --release` | | `node index.js` | `cargo run` | | `npm test` | `cargo test` | | `tsc` (type-check) | `cargo check` | | ESLint / Prettier | `cargo clippy` / `cargo fmt` | | `node_modules/` | `~/.cargo/` plus the `target/` build dir | See: [The Module Tree](/12-modules-packages/01-module-tree/), [The `use` Keyword](/12-modules-packages/02-use-keyword/), [Visibility](/12-modules-packages/03-pub-visibility/), [Cargo](/12-modules-packages/04-cargo/). *** ## Everyday idioms [Section titled “Everyday idioms”](#everyday-idioms) | TypeScript / JavaScript | Rust | | ------------------------------ | ------------------------------------------------------------- | | `console.log(x)` | `println!("{x}")` or `println!("{x:?}")` for any `Debug` type | | `console.error(x)` | `eprintln!("{x}")` | | quick debug print | `dbg!(x)` | | `JSON.stringify(v)` | `serde_json::to_string(&v)?` | | `JSON.parse(s)` | `serde_json::from_str(&s)?` | | `Number("42")` | `"42".parse::()?` | | `x as Y` (numeric) | `x as Y` | | `typeof x` (runtime) | not needed; types are checked at compile time | | object spread `{ ...a, b: 1 }` | struct update `User { b: 1, ..a }` | | immutability by convention | immutable by default; opt in with `mut` | See: [Output](/02-basics/04-output/), [JSON with Serde](/15-serialization/03-json/), [Serde Basics](/15-serialization/01-serde-basics/). *** ## Where to go next [Section titled “Where to go next”](#where-to-go-next) This page is the map; the territory is the rest of the guide. The one idea with no JavaScript analogue, and the one worth learning first, is **ownership**: who is responsible for each value and when it is freed. Start there. * [Why Rust?](/01-getting-started/00-why-rust/) for the motivation. * [Ownership](/05-ownership/) for the concept that makes everything else click. * [Error Handling](/08-error-handling/) to replace `try/catch` with values. * [The Migration Guide](/29-migration-guide/) when you are ready to port real code. # Rust for JavaScript & TypeScript Developers: FAQ > Straight answers to what JS and TS developers actually ask about Rust: ownership, async, null, classes, performance, npm, WebAssembly, and whether to learn it. The questions a JavaScript or TypeScript developer actually types into a search box or asks an AI, answered in a sentence or two each, with a link to the chapter that goes deeper. If you are deciding whether Rust is worth your time, start at the top; if you are mid-learning and stuck on one idea, jump to its section. *** ## Is Rust hard to learn coming from JavaScript or TypeScript? [Section titled “Is Rust hard to learn coming from JavaScript or TypeScript?”](#is-rust-hard-to-learn-coming-from-javascript-or-typescript) The syntax is familiar within a day; the mental model takes longer. The one genuinely new idea is **ownership**, the rule for who is responsible for each value and when it is freed. Most of the early friction is the borrow checker teaching you that rule. Everything else (functions, generics, async, modules) maps closely to what you know. See [Why Rust?](/01-getting-started/00-why-rust/) and [Ownership](/05-ownership/). ## How long does it take to learn Rust? [Section titled “How long does it take to learn Rust?”](#how-long-does-it-take-to-learn-rust) Plan on a few weeks to feel productive and a few months to feel fluent. You can write useful programs in the first week; ownership, lifetimes, and async are where the real time goes. Coming from TypeScript helps, because you already think in types, generics, and `async`/`await`. ## Do I still need JavaScript or TypeScript if I learn Rust? [Section titled “Do I still need JavaScript or TypeScript if I learn Rust?”](#do-i-still-need-javascript-or-typescript-if-i-learn-rust) Yes. Rust is a complement, not a replacement. You will still ship UI in TypeScript and reach for Rust where it pays off: CPU-heavy work, WebAssembly modules, CLIs, services, and native Node addons. The two languages increasingly live in the same codebase. See the [Migration Guide](/29-migration-guide/) and [WebAssembly](/19-wasm/). ## Should a JavaScript developer learn Rust in 2026? [Section titled “Should a JavaScript developer learn Rust in 2026?”](#should-a-javascript-developer-learn-rust-in-2026) If you want to understand memory, performance, and systems, or to write the fast path your JavaScript app calls into, yes. Rust powers parts of Figma, Discord, Cloudflare, and the tooling under many JS frameworks. It is a senior-leaning skill with a real payoff, not a quick win. *** ## How does ownership work coming from JavaScript? [Section titled “How does ownership work coming from JavaScript?”](#how-does-ownership-work-coming-from-javascript) In JavaScript the garbage collector frees memory whenever it likes, and you share references freely. In Rust, every value has exactly **one owner**; assigning it or passing it to a function **moves** ownership, and the value is freed when its owner goes out of scope. No garbage collector, decided at compile time. See [The Three Ownership Rules](/05-ownership/01-ownership-rules/). ## What is the borrow checker, and why does it keep rejecting my code? [Section titled “What is the borrow checker, and why does it keep rejecting my code?”](#what-is-the-borrow-checker-and-why-does-it-keep-rejecting-my-code) It is the compile-time analysis that enforces ownership. The core rule: you can have **many shared `&` references or one mutable `&mut` reference, never both at once**. It rejects exactly the aliasing and use-after-free bugs JavaScript ships to production. The fight early on is normal and fades fast. See [Borrowing](/05-ownership/02-borrowing/) and [Mutable References](/05-ownership/03-mutable-references/). ## Why does Rust not have a garbage collector? [Section titled “Why does Rust not have a garbage collector?”](#why-does-rust-not-have-a-garbage-collector) Because ownership lets the compiler insert the cleanup for you at exactly the right moment, with no runtime that pauses your program to scan for garbage. You get predictable memory use and no GC pauses, at the cost of learning the ownership rules up front. See [Stack vs Heap](/05-ownership/00-stack-heap/) and [The Drop Trait](/05-ownership/08-drop-trait/). ## Why is my Rust code full of `.clone()` and `&`? [Section titled “Why is my Rust code full of .clone() and &?”](#why-is-my-rust-code-full-of-clone-and-) Usually a sign you are fighting ownership instead of borrowing. Reach for `&` to lend a value without giving it away, and treat `.clone()` as a deliberate “make a real copy here,” not a reflex to silence the compiler. The habit clicks after the first few programs. See [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/). *** ## What replaces `null` and `undefined` in Rust? [Section titled “What replaces null and undefined in Rust?”](#what-replaces-null-and-undefined-in-rust) `Option`. A value that might be absent has type `Option` and is either `Some(value)` or `None`, and the compiler forces you to handle the `None` case. The billion-dollar mistake becomes a compile error instead of a runtime `TypeError`. See [Option Enum](/06-data-structures/03-option-enum/) and [Result and Option](/08-error-handling/00-result-option/). ## What is the Rust equivalent of `try`/`catch`? [Section titled “What is the Rust equivalent of try/catch?”](#what-is-the-rust-equivalent-of-trycatch) There are no exceptions. A function that can fail returns `Result`, either `Ok(value)` or `Err(error)`. The `?` operator propagates an error up the call stack in one character, replacing `try`/`catch` with values the type system makes you handle. See [The `?` Operator](/08-error-handling/01-question-mark/) and [Custom Errors](/08-error-handling/04-custom-errors/). ## What is the Rust equivalent of a Promise and `async`/`await`? [Section titled “What is the Rust equivalent of a Promise and async/await?”](#what-is-the-rust-equivalent-of-a-promise-and-asyncawait) The syntax matches: `async fn` and `.await`. The difference is that a Rust **future is lazy** (it does nothing until you `.await` it) and there is no built-in event loop, so you add a runtime like Tokio. `Promise.all` becomes `tokio::join!`. See [Promises vs Futures](/11-async/00-promises-vs-futures/) and [async/await](/11-async/01-async-await/). *** ## What is the Rust equivalent of array `map`, `filter`, and `reduce`? [Section titled “What is the Rust equivalent of array map, filter, and reduce?”](#what-is-the-rust-equivalent-of-array-map-filter-and-reduce) The same names, but on **iterators**, and lazy: `arr.iter().map(...).filter(...).collect()`. `reduce` is `fold`. Nothing runs until a consumer like `.collect()`, `.sum()`, or a `for` loop drives the chain, so chaining adaptors allocates nothing in between. See [Iterators](/07-collections/06-iterators/) and [Iterator Consumers](/07-collections/07-iterator-consumers/). ## What replaces JavaScript objects, `Map`, and `Set`? [Section titled “What replaces JavaScript objects, Map, and Set?”](#what-replaces-javascript-objects-map-and-set) A fixed shape is a `struct`; dynamic string keys are a `HashMap`; a `Set` is a `HashSet`. Unlike a JS object, a `HashMap` has one key type and one value type, and `map.get(&k)` returns an `Option` you must unwrap. See [Structs](/06-data-structures/00-structs/) and [HashMaps](/07-collections/03-hashmaps/). ## What is the difference between `String` and `&str`? [Section titled “What is the difference between String and \&str?”](#what-is-the-difference-between-string-and-str) `String` is an owned, growable, heap-allocated string you own and can mutate. `&str` is a borrowed view into string data you do not own. The rule of thumb: take `&str` as a function argument (it accepts both), and return or store a `String` when you need ownership. See [Strings](/07-collections/01-strings/). *** ## What replaces classes and interfaces in Rust? [Section titled “What replaces classes and interfaces in Rust?”](#what-replaces-classes-and-interfaces-in-rust) Rust splits the three jobs a class does. Data goes in a `struct` or `enum`, methods go in an `impl` block, and a shared contract (an interface) is a `trait` you can implement for any type. There is no inheritance; you compose with traits instead. See [Structs](/06-data-structures/00-structs/), [impl Blocks](/06-data-structures/05-impl-blocks/), and [Traits](/09-generics-traits/03-traits/). ## What is a trait, and how does it differ from a TypeScript interface? [Section titled “What is a trait, and how does it differ from a TypeScript interface?”](#what-is-a-trait-and-how-does-it-differ-from-a-typescript-interface) A trait is a set of methods a type promises to provide, like an interface. The differences: you can implement **your own local trait** for a type you do not own, traits can ship default method bodies, and the compiler usually dispatches them statically with zero runtime cost. Rust’s orphan rule forbids implementing a foreign trait for a foreign type; either the trait or the type must be local to your crate. See [Traits](/09-generics-traits/03-traits/) and [Trait Objects](/09-generics-traits/06-trait-objects/). ## How is Rust’s type system different from TypeScript’s? [Section titled “How is Rust’s type system different from TypeScript’s?”](#how-is-rusts-type-system-different-from-typescripts) TypeScript’s types are erased at runtime and can be bypassed with `any` or a wrong cast. Rust’s types are enforced all the way down, there is no `any`, and the same generics also guarantee memory and thread safety. What TypeScript checks optionally, Rust checks always. See [Basic Types](/02-basics/01-types/) and [Generics and Traits](/09-generics-traits/). *** ## Is Rust faster than Node.js? [Section titled “Is Rust faster than Node.js?”](#is-rust-faster-than-nodejs) For CPU-bound work, usually by a wide margin: no garbage collector, no interpreter warm-up, contiguous memory, and zero-cost abstractions. For I/O-bound work the gap narrows, because both spend most of their time waiting. Measure your real workload before assuming. See the [Performance](/21-performance/) section and [Rust vs Node.js](/21-performance/09-comparison/). ## Can I use Rust in the browser with JavaScript? [Section titled “Can I use Rust in the browser with JavaScript?”](#can-i-use-rust-in-the-browser-with-javascript) Yes, through **WebAssembly**. You compile a Rust crate to a `.wasm` module and call it from JavaScript like any module. WebAssembly runs on the thread that invokes it, so call it from a Web Worker when the goal is to move parsing, image work, or another hot path off the browser’s main thread. `wasm-bindgen` generates the glue and the TypeScript types. See [WebAssembly](/19-wasm/) and [Your First WASM Module](/19-wasm/02-first-wasm/). ## Can Rust call JavaScript or Node libraries? [Section titled “Can Rust call JavaScript or Node libraries?”](#can-rust-call-javascript-or-node-libraries) Both directions work. In the browser, `wasm-bindgen` lets Rust call JS APIs. On the server, you build a native Node addon in Rust with `napi-rs` and `import` it from JavaScript like any package, with generated TypeScript types and no C++. See [Node Addons with napi-rs](/20-unsafe-ffi/06-napi/) and [Calling JavaScript from Rust](/19-wasm/03-js-interop/). *** ## What is the Rust equivalent of npm and `package.json`? [Section titled “What is the Rust equivalent of npm and package.json?”](#what-is-the-rust-equivalent-of-npm-and-packagejson) **Cargo**, plus `Cargo.toml`. `npm install x` is `cargo add x`, `npm run build` is `cargo build --release`, and `npm test` is `cargo test`. Cargo also bundles the formatter, linter, test runner, and docs generator that you wire up separately in the JS world. See [Cargo](/12-modules-packages/04-cargo/) and [Dependencies](/12-modules-packages/06-dependencies/). ## How do I work with JSON in Rust? [Section titled “How do I work with JSON in Rust?”](#how-do-i-work-with-json-in-rust) With **Serde**. Derive `Serialize` and `Deserialize` on a struct and `serde_json` converts to and from JSON, type-checked. `JSON.parse` becomes `serde_json::from_str`, and `JSON.stringify` becomes `serde_json::to_string`, against a concrete type instead of `any`. See [JSON with Serde](/15-serialization/03-json/) and [Serde Basics](/15-serialization/01-serde-basics/). ## What is the Rust equivalent of `console.log`? [Section titled “What is the Rust equivalent of console.log?”](#what-is-the-rust-equivalent-of-consolelog) `println!("{x}")` for normal output and `eprintln!` for stderr. To print any value while debugging, derive `Debug` and use `println!("{x:?}")` or the `dbg!(x)` macro, which also prints the file and line. See [Output and Formatting](/02-basics/04-output/). *** ## What is the hardest part of Rust for JavaScript developers? [Section titled “What is the hardest part of Rust for JavaScript developers?”](#what-is-the-hardest-part-of-rust-for-javascript-developers) Three things, in order: **ownership and borrowing** (the borrow checker), **lifetimes** (proving references stay valid), and **async** (lazy futures and a runtime you choose). None are conceptually huge, but they are unfamiliar. Ownership is the one that unlocks the rest. See [Ownership](/05-ownership/), [Lifetimes](/05-ownership/04-lifetimes/), and [Async vs Sync](/11-async/13-async-vs-sync/). ## Do I need to learn C before Rust? [Section titled “Do I need to learn C before Rust?”](#do-i-need-to-learn-c-before-rust) No. Rust is designed so you almost never touch raw pointers or manual memory management in everyday code; the safe subset covers the vast majority of programs. C knowledge helps only when you reach for `unsafe` or foreign-function interfaces, which most application code never does. See [When to Use Unsafe](/20-unsafe-ffi/09-when-to-use/). ## What can I build with Rust as a web developer? [Section titled “What can I build with Rust as a web developer?”](#what-can-i-build-with-rust-as-a-web-developer) WebAssembly modules for the browser, HTTP services and APIs, command-line tools, native Node addons, and the performance-critical core of an otherwise-JavaScript app. It is a strong fit anywhere you currently hit the ceiling of a single-threaded, garbage-collected runtime. See the [Projects](/30-projects/) section for full worked examples. # Version and Verification Policy > How rs4ts.dev pins Rust, records crate versions, and verifies capstone projects and runnable documentation snippets without calling an old pin the latest stable release. Rust and its crate ecosystem release frequently. This guide therefore separates three ideas that are easy to conflate: * **The Rust edition** is the language compatibility boundary used by a crate. New projects in this guide use the **2024 edition**. * **The repository toolchain** is the exact compiler used for reproducible local and CI checks. Its source of truth is [`rust-toolchain.toml`](https://github.com/zeybek/rs4ts.dev/blob/main/rust-toolchain.toml). * **The latest stable Rust release** changes on Rust’s release cadence. The guide does not hard-code that moving value across chapter prose. At the time of this repository snapshot, the verification toolchain is pinned to **Rust 1.96.0**. That sentence describes a tested baseline, not a claim that 1.96.0 is still the newest stable release. Run `rustup update stable` in a separate project when you specifically want to try the newest compiler; use the repository pin when reproducing this guide’s CI results. ## What CI verifies [Section titled “What CI verifies”](#what-ci-verifies) The repository applies different checks to different kinds of examples: 1. **Six capstone crates** are checked with the pinned toolchain. CI runs rustfmt, Clippy with warnings denied, native tests, and the relevant `wasm32-unknown-unknown` checks. 2. **A deterministic subset of self-contained standard-library `rust playground` programs** is extracted from normal lesson sections and compiled as Rust 2024 code. Fragments, Common Pitfalls/Exercises sections, deliberately failing teaching examples, and snippets requiring external crates or multiple files are outside that automatic subset. CI enforces a minimum selected count so the gate cannot silently shrink to zero. 3. **Internal links and anchors** are checked by the production site build. 4. **External-crate snippets** state their required dependencies beside the example. They still require a focused Cargo project when edited; a green standard-library snippet job does not prove those integrations. This scope is intentionally explicit. “Checked on the pinned toolchain” is a reproducible claim only for the named gate; “every example compiles on current stable” would be broader than the automated evidence. ## Crate versions [Section titled “Crate versions”](#crate-versions) Chapter prose uses a major or compatible line when that is the meaningful API contract (`axum 0.8`, `serde 1`, `tokio 1`). Exact versions belong in the capstone `Cargo.lock` files or in a page only when an API changed across releases and the distinction matters to the lesson. When updating the repository toolchain or a documented dependency: 1. change the single source of truth (`rust-toolchain.toml`, a capstone manifest/lockfile, or the page’s dependency block); 2. run the focused examples affected by the API change; 3. run the documentation snippet checker and full site build; 4. describe historical measurements as “measured with” or “verified with,” never as permanently “current.” That wording keeps old benchmark output reproducible without turning yesterday’s tested version into today’s release claim. # Getting Started with Rust > Welcome to Rust! This section will get you up and running with your first Rust program. Welcome to Rust! This section will get you up and running with your first Rust program. *** ## What’s in This Section [Section titled “What’s in This Section”](#whats-in-this-section) * **[Why Rust](/01-getting-started/00-why-rust/)** - Detailed reasons to learn Rust as a TS/JS developer * **[Installing Rust](/01-getting-started/01-installation/)** - Setup guide for all platforms * **[Hello World](/01-getting-started/02-hello-world/)** - Your first Rust program * **[Understanding Cargo](/01-getting-started/03-cargo-basics/)** - Rust’s build tool and package manager * **[Rust Playground](/01-getting-started/04-playground/)** - Try Rust without installing anything *** ## Learning Objectives [Section titled “Learning Objectives”](#learning-objectives) By the end of this section, you will: * Understand why Rust is valuable for TS/JS developers * Have Rust installed and configured on your system * Write and run your first Rust program * Understand Cargo and its role (think: npm + webpack + more) * Know how to use the online Rust Playground *** ## Time Estimate [Section titled “Time Estimate”](#time-estimate) * **Reading:** 45-60 minutes * **Installation & Setup:** 15-30 minutes * **Hands-on Practice:** 30-45 minutes * **Total:** 1.5-2 hours *** ## Quick Start Path [Section titled “Quick Start Path”](#quick-start-path) If you want to jump right in: 1. **[Install Rust](/01-getting-started/01-installation/)** (15 minutes) 2. **[Hello World](/01-getting-started/02-hello-world/)** (15 minutes) 3. **[Cargo Basics](/01-getting-started/03-cargo-basics/)** (15 minutes) Then come back and read the other topics at your leisure. *** ## Detailed Learning Path [Section titled “Detailed Learning Path”](#detailed-learning-path) ### Recommended Order [Section titled “Recommended Order”](#recommended-order) ```plaintext 1 1. Why Rust (understand the motivation) 2 ↓ 3 2. Installation (get Rust on your machine) 4 ↓ 5 3. Hello World (write your first program) 6 ↓ 7 4. Cargo Basics (learn the tooling) 8 ↓ 9 5. Playground (for quick experiments) ``` ### Alternative: Hands-On First [Section titled “Alternative: Hands-On First”](#alternative-hands-on-first) ```plaintext 1 1. Playground (try Rust immediately, no install) 2 ↓ 3 2. Installation (once you're convinced) 4 ↓ 5 3. Hello World 6 ↓ 7 4. Why Rust (understand what you just did) 8 ↓ 9 5. Cargo Basics ``` *** ## What You’ll Learn [Section titled “What You’ll Learn”](#what-youll-learn) ### Why Rust? (vs TypeScript/Node.js) [Section titled “Why Rust? (vs TypeScript/Node.js)”](#why-rust-vs-typescriptnodejs) Compare Rust to what you already know: **TypeScript/Node.js:** * Interpreted/JIT compiled * Garbage collected * Single-threaded event loop * Runtime errors * 50-100ms startup time * 50-200 MB memory baseline **Rust:** * Compiled to native code * No garbage collection * Safe, compiler-checked, opt-in multithreading (fearless concurrency) * Compile-time error checking * <1ms startup time * 1-5 MB memory baseline ### Installation [Section titled “Installation”](#installation) You’ll learn: * How to install Rust using rustup * How to verify your installation * How to update Rust * Editor setup for the best experience ### Hello World [Section titled “Hello World”](#hello-world) Your first Rust program: ```rust 1 fn main() { 2 println!("Hello, world!"); 3 } ``` Compared to TypeScript, where top-level code runs directly with no required entry-point function: ```typescript 1 console.log("Hello, world!"); ``` ### Cargo: The Complete Toolchain [Section titled “Cargo: The Complete Toolchain”](#cargo-the-complete-toolchain) Cargo is like npm, webpack, and more combined: | Feature | TypeScript/JS | Rust | | --------------- | ------------- | ----------- | | Package Manager | npm/yarn/pnpm | Cargo | | Build Tool | webpack/vite | Cargo | | Test Runner | jest/vitest | Cargo | | Formatter | prettier | rustfmt | | Linter | eslint | clippy | | Documentation | typedoc | rustdoc | | Benchmarking | (various) | Cargo bench | | Task Runner | npm scripts | Cargo | **All in one tool!** ### The Playground [Section titled “The Playground”](#the-playground) [play.rust-lang.org](https://play.rust-lang.org/) lets you: * Try Rust without installing anything * Share code snippets * Test ideas quickly * Learn with instant feedback *** ## What Makes Rust Different [Section titled “What Makes Rust Different”](#what-makes-rust-different) ### 1. Compiled, Not Interpreted [Section titled “1. Compiled, Not Interpreted”](#1-compiled-not-interpreted) **TypeScript/JavaScript:** ```bash 1 node app.js # V8 parses, then JIT-compiles hot code at runtime 2 ts-node app.ts # Strips types, then runs through the same V8 pipeline ``` **Rust:** ```bash 1 rustc main.rs # Compiled to native binary 2 ./main # Execute native code ``` **Impact:** * Faster execution, especially CPU-bound work (no GC pauses) * Smaller deployments (single binary) * No runtime dependencies * Slower build times (seconds vs milliseconds) ### 2. No Garbage Collector [Section titled “2. No Garbage Collector”](#2-no-garbage-collector) **TypeScript/JavaScript:** * Automatic memory management * GC pauses (unpredictable timing) * Memory overhead * Easy to use **Rust:** * Ownership system (compile-time memory management) * No GC pauses (predictable performance) * Minimal memory overhead * Learning curve ### 3. Stronger Type System [Section titled “3. Stronger Type System”](#3-stronger-type-system) **TypeScript:** ```typescript 1 let x: any = "hello"; // Escape hatch — type checking is disabled 2 x = 42; // No complaint from the compiler 3 let y = x.length; // No error: silently `undefined` at runtime ``` **Rust:** ```rust 1 let x = "hello"; // Type inference 2 let y = x.len(); // Always safe, or won't compile ``` ### 4. Explicit Error Handling [Section titled “4. Explicit Error Handling”](#4-explicit-error-handling) **TypeScript:** ```typescript 1 function divide(a: number, b: number): number { 2 if (b === 0) { 3 throw new Error("Division by zero"); // Exception 4 } 5 return a / b; 6 } ``` **Rust:** ```rust 1 fn divide(a: i32, b: i32) -> Result { 2 if b == 0 { 3 Err("Division by zero".to_string()) // Explicit error value 4 } else { 5 Ok(a / b) 6 } 7 } ``` **No exceptions in Rust!** All errors are explicit in the type system. *** ## Your Goals for This Section [Section titled “Your Goals for This Section”](#your-goals-for-this-section) ### Minimum Goals [Section titled “Minimum Goals”](#minimum-goals) * [ ] Install Rust on your machine * [ ] Run `cargo --version` successfully * [ ] Create and run a “Hello World” program * [ ] Understand what Cargo does ### Stretch Goals [Section titled “Stretch Goals”](#stretch-goals) * [ ] Configure your editor with rust-analyzer * [ ] Experiment with the Rust Playground * [ ] Create a custom Cargo project * [ ] Read Cargo.toml and understand its structure *** ## Key Concepts Preview [Section titled “Key Concepts Preview”](#key-concepts-preview) ### Concepts You’ll Encounter [Section titled “Concepts You’ll Encounter”](#concepts-youll-encounter) **rustup** - The Rust toolchain installer (like nvm for Node.js) **cargo** - Build tool and package manager (like npm + webpack) **rustc** - The Rust compiler. Where `tsc` erases types and emits JavaScript that still needs a runtime, `rustc` produces a self-contained native binary. **crates** - Rust packages (like npm packages) **Cargo.toml** - Project manifest (like package.json) **main.rs** - Entry point file (like index.ts) Don’t worry if these don’t make sense yet - they will soon! *** ## Common First-Time Issues [Section titled “Common First-Time Issues”](#common-first-time-issues) ### ”rustup: command not found” [Section titled “”rustup: command not found””](#rustup-command-not-found) **Problem:** Installation didn’t add rustup to PATH **Solution:** ```bash 1 # Restart your terminal first. 2 # On macOS/Linux you can also source the env file in the current shell: 3 source "$HOME/.cargo/env" ``` > **Note:** `source ~/.cargo/env` is a Unix shell command and does **not** work in Windows PowerShell or `cmd.exe`. On Windows, the rustup installer adds Cargo to your `PATH` automatically; just open a new terminal window. ### ”cargo: command not found” [Section titled “”cargo: command not found””](#cargo-command-not-found) **Problem:** Same as above **Solution:** Restart your terminal (Windows), or source the cargo env file in the current shell (macOS/Linux) ### “error: linking with `cc` failed” [Section titled ““error: linking with cc failed””](#error-linking-with-cc-failed) **Problem:** Missing C compiler (Rust needs it for linking) **Solution:** * **macOS:** `xcode-select --install` * **Linux:** `sudo apt install build-essential` * **Windows:** Install Visual Studio Build Tools ### Slow Compilation [Section titled “Slow Compilation”](#slow-compilation) **Problem:** First compile is always slow **Why:** Rust compiles dependencies from source **Tips:** * Use `cargo build` without `--release` during development * Dependencies are cached after first build * Consider using `sccache` for faster recompilation *** ## Additional Resources [Section titled “Additional Resources”](#additional-resources) ### Official Resources [Section titled “Official Resources”](#official-resources) * [Rust Installation Guide](https://www.rust-lang.org/tools/install) * [Cargo Book](https://doc.rust-lang.org/cargo/) * [Rust Playground](https://play.rust-lang.org/) ### Community Resources [Section titled “Community Resources”](#community-resources) * [Rust Discord](https://discord.gg/rust-lang) * [r/rust](https://reddit.com/r/rust) * [This Week in Rust](https://this-week-in-rust.org/) *** ## Ready to Begin? [Section titled “Ready to Begin?”](#ready-to-begin) Let’s get Rust installed and write your first program! ### [Start with Why Rust](/01-getting-started/00-why-rust/) [Section titled “Start with Why Rust”](#start-with-why-rust) Or jump straight to: * [Installation Guide](/01-getting-started/01-installation/) * [Hello World](/01-getting-started/02-hello-world/) # Why Rust for TS/JS Developers? > You already know TypeScript and JavaScript. Why should you invest time learning Rust? Let's compare the two and explore where Rust excels. You already know TypeScript and JavaScript. Why should you invest time learning Rust? Let’s compare the two and explore where Rust excels. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Rust is **not** a replacement for TypeScript/JavaScript. They solve different problems: * **TypeScript/JavaScript:** Web development, rapid prototyping, full-stack apps * **Rust:** Performance-critical backends, systems programming, CLI tools, WebAssembly **Think of it as:** Adding another tool to your toolbox, not replacing your existing tools. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) Let’s start with a familiar example - a web server that handles user requests: ```typescript 1 // Express.js server 2 import express from "express"; 3 4 interface User { 5 id: number; 6 name: string; 7 email: string; 8 } 9 10 const app = express(); 11 const users: User[] = []; 12 13 app.get("/users/:id", (req, res) => { 14 const id = parseInt(req.params.id); 15 const user = users.find((u) => u.id === id); 16 17 if (user) { 18 res.json(user); 19 } else { 20 res.status(404).json({ error: "User not found" }); 21 } 22 }); 23 24 app.listen(3000, () => { 25 console.log("Server running on port 3000"); 26 }); ``` **Characteristics:** * Quick to write * Easy to understand * Fast development iteration * The main event loop runs JavaScript on one thread; worker threads and multi-process clustering are explicit options * Startup time and memory depend on the Node version, framework, dependencies, and deployment packaging * Garbage collection can introduce latency pauses; measure whether they matter for your workload * TypeScript checks source types, while runtime input still needs validation *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) The same server in Rust (using the **Axum** framework, version 0.8): ```rust 1 // Axum server 2 use axum::{ 3 extract::Path, 4 http::StatusCode, 5 routing::get, 6 Json, Router, 7 }; 8 use serde::{Deserialize, Serialize}; 9 use std::sync::Arc; 10 use tokio::sync::RwLock; 11 12 #[derive(Clone, Serialize, Deserialize)] 13 struct User { 14 id: u32, 15 name: String, 16 email: String, 17 } 18 19 type UserDb = Arc>>; 20 21 async fn get_user( 22 Path(id): Path, 23 db: axum::extract::State, 24 ) -> Result, StatusCode> { 25 let users = db.read().await; 26 users 27 .iter() 28 .find(|u| u.id == id) 29 .cloned() 30 .map(Json) 31 .ok_or(StatusCode::NOT_FOUND) 32 } 33 34 #[tokio::main] 35 async fn main() { 36 let db: UserDb = Arc::new(RwLock::new(Vec::new())); 37 38 let app = Router::new() 39 .route("/users/{id}", get(get_user)) 40 .with_state(db); 41 42 println!("Server running on port 3000"); 43 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000") 44 .await 45 .unwrap(); 46 axum::serve(listener, app).await.unwrap(); 47 } ``` **Characteristics:** * Tokio’s multi-thread scheduler can move ready tasks across worker threads * Native startup and memory are often low, but still depend on dependencies, allocator, build profile, and workload * No garbage collection pauses * Strong compile-time guarantees (whole classes of bugs are caught before runtime, though panics like `unwrap()` still exist) * CPU-bound code is often faster, but only a same-work, same-core benchmark can quantify the difference * More verbose * Slower compilation * Steeper learning curve *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Memory Management [Section titled “Memory Management”](#memory-management) **TypeScript/JavaScript:** ```typescript 1 let data = { value: 42 }; 2 let data2 = data; // Both reference same object 3 data = null; // data2 still points to object 4 // Garbage collector cleans up eventually ``` * Memory is automatically managed * Garbage collector runs periodically * Unpredictable pauses (can be 1-100ms) * Memory overhead for GC bookkeeping **Rust:** ```rust 1 let data = String::from("hello"); 2 let data2 = data; // Ownership moved to data2 3 // data is no longer valid - compile error if used! 4 // Memory freed immediately when data2 goes out of scope ``` * Memory is tracked at compile time * No garbage collector needed * Predictable performance (no pauses) * Minimal memory overhead **Why it matters:** For web servers handling thousands of requests, GC pauses can add up. Rust’s approach eliminates these pauses entirely. ### Concurrency [Section titled “Concurrency”](#concurrency) **TypeScript/JavaScript:** ```typescript 1 // Single-threaded, event loop 2 async function processRequests(requests: Request[]) { 3 // Processes one at a time (unless you spawn workers) 4 for (const req of requests) { 5 await handleRequest(req); 6 } 7 } ``` * Single-threaded by default * Worker threads are complex to use * Async/await for I/O concurrency * No true parallelism (without workers) **Rust:** ```rust 1 // I/O concurrency: Tokio can schedule ready tasks on its worker threads 2 async fn process_requests(requests: Vec) { 3 // Requests make progress concurrently while they wait for I/O. 4 let handles: Vec<_> = requests 5 .into_iter() 6 .map(|req| tokio::spawn(handle_request(req))) 7 .collect(); 8 9 for handle in handles { 10 handle.await.unwrap(); 11 } 12 } ``` * Safe, opt-in multithreading (fearless concurrency) * Thread-safety requirements checked at compile time * No data races in safe Rust * Concurrent I/O; use threads or Rayon when the work itself is CPU-bound **Why it matters:** Tokio can keep many I/O-bound requests moving without dedicating one thread to each connection. CPU-intensive work (image processing, data transformation) needs explicit CPU parallelism such as Rayon, `std::thread`, or carefully bounded `spawn_blocking`; wrapping it in `tokio::spawn` does not make the computation faster. ### Error Handling [Section titled “Error Handling”](#error-handling) **TypeScript/JavaScript:** ```typescript 1 function getUser(id: number): User { 2 const user = database.find(id); 3 if (!user) { 4 throw new Error("User not found"); // Exception can crash the app 5 } 6 return user; 7 } 8 9 // Easy to forget error handling 10 const user = getUser(123); // What if this throws? ``` * Exceptions can be thrown anywhere * Easy to forget to catch them * Runtime crashes possible * Try/catch adds noise **Rust:** ```rust 1 fn get_user(id: u32) -> Result { 2 match database.find(id) { 3 Some(user) => Ok(user), 4 None => Err("User not found".to_string()), 5 } 6 } 7 8 // `?` explicitly propagates an error to this function's caller. 9 let user = get_user(123)?; ``` * No exceptions * Errors are explicit in the type system * Using the success value requires matching, propagating, choosing a fallback, or deliberately panicking * `?` operator for convenient error propagation **Why it matters:** In production, unhandled exceptions cause outages. Rust puts recoverable failure in the function signature, and an entirely discarded `Result` produces an `unused_must_use` warning by default. Teams commonly deny warnings in CI, but Rust still lets you make an explicit choice to discard, propagate, or panic. *** ## Key Differences [Section titled “Key Differences”](#key-differences) ### 1. **Performance** [Section titled “1. Performance”](#1-performance) There is no honest universal multiplier between Node.js and Rust. Framework, payload, allocator, warm-up, database latency, concurrency, hardware, and build profile can change the result by orders of magnitude. Compare representative workloads under the same conditions: | Workload | What usually dominates | Fair comparison | | --------------- | ---------------------------------------------- | --------------------------------------------------------------- | | HTTP API | Database/network latency, framework overhead | Same routes, pool sizes, payloads, cores, and load model | | JSON processing | Schema, allocation pattern, payload shape | The real production payload, validated the same way | | Cold start | Runtime initialization, binary/dependency size | Same host and deployment packaging, multiple cold runs | | Memory | V8 heap/GC policy vs. Rust allocations | RSS under the same sustained load, including caches | | CPU kernel | JIT warm-up, AOT optimization, vectorization | Warm Node first; compile Rust with `--release`; use equal cores | The measured [Rust vs Node.js comparison](/21-performance/09-comparison/) publishes its machine, commands, code, and caveats. Use that as a reproducible example, then benchmark your own service before making a migration decision. **When it matters:** * High-traffic APIs (save on cloud costs) * Real-time systems (gaming, trading) * CLI tools (instant startup) * Data processing pipelines **When it doesn’t:** * Low-traffic CRUD apps * Simple websites * Prototypes ### 2. **Safety** [Section titled “2. Safety”](#2-safety) **TypeScript:** ```typescript 1 function getFirst(arr: readonly T[]): T | undefined { 2 return arr[0]; // With noUncheckedIndexedAccess, TypeScript infers this possibility too. 3 } ``` **Rust:** ```rust 1 fn get_first(arr: &[T]) -> Option<&T> { 2 arr.first() // Extracting &T requires matching, propagating, or choosing a fallback. 3 } ``` Strict TypeScript with `noUncheckedIndexedAccess` models this example correctly, so the difference is not “TypeScript cannot express absence.” Rust makes the check language-wide and non-configurable, while TypeScript’s guarantee depends on project settings and is erased at runtime. Both languages still need runtime validation for untrusted JSON. **What Rust prevents at compile time:** * Null/undefined dereferences * Buffer overflows * Data races * Use-after-free * Double-free * Iterator invalidation TypeScript can model many domain-level mistakes when strict options are enabled, but it does not enforce Rust’s memory and thread-safety guarantees and its types do not validate runtime input by themselves. ### 3. **Deployment** [Section titled “3. Deployment”](#3-deployment) **TypeScript/JavaScript:** ```bash 1 # Need Node.js runtime on server 2 node dist/index.js 3 4 # Docker image: ~200-500 MB 5 FROM node:18 6 COPY package*.json ./ 7 RUN npm install 8 COPY . . 9 CMD ["node", "dist/index.js"] ``` **Rust:** ```bash 1 # Single binary, no runtime needed 2 ./my-app 3 4 # Docker image: ~10-20 MB 5 FROM scratch 6 COPY ./my-app / 7 CMD ["/my-app"] ``` **Why it matters:** * Faster deployments * Smaller container images * No runtime vulnerabilities * Easier distribution (single file!) ### 4. **Type System** [Section titled “4. Type System”](#4-type-system) **TypeScript:** ```typescript 1 // Type system can be escaped 2 let x: any = "hello"; 3 x = 123; // Allowed with 'any' 4 5 // Under strict mode, the optional type is enforced 6 let y: string | undefined; 7 console.log(y.length); // Compile error under strict mode: 'y' is possibly 'undefined' ``` **Rust:** ```rust 1 // No escape hatches (except unsafe blocks) 2 let x: String = String::from("hello"); 3 // x = 123; // Compile error! 4 5 // Optional is enforced 6 let y: Option = None; 7 // println!("{}", y.len()); // Compile error! 8 println!("{}", y.unwrap_or_default().len()); // Must handle None case ``` **Why it matters:** Rust catches more bugs at compile time, TypeScript catches some but allows escape hatches. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: “Rust will make me more productive immediately” [Section titled “Pitfall 1: “Rust will make me more productive immediately””](#pitfall-1-rust-will-make-me-more-productive-immediately) **Reality:** Rust has a steep learning curve. Expect 3-4 weeks before you’re comfortable, 2-3 months before you’re productive. **Mitigation:** Start with small projects, don’t rewrite critical systems immediately. ### Pitfall 2: “I should rewrite everything in Rust” [Section titled “Pitfall 2: “I should rewrite everything in Rust””](#pitfall-2-i-should-rewrite-everything-in-rust) **Reality:** Most web apps don’t need Rust’s performance. Node.js is fine for CRUD apps. **Use Rust when:** * You have performance problems * You’re building a new performance-critical service * You need predictable latency * You’re building CLI tools **Stick with Node.js when:** * Rapid prototyping * Simple CRUD apps * Small teams without Rust experience * Time-to-market is critical ### Pitfall 3: “Rust is just like TypeScript with different syntax” [Section titled “Pitfall 3: “Rust is just like TypeScript with different syntax””](#pitfall-3-rust-is-just-like-typescript-with-different-syntax) **Reality:** The ownership system is completely new. You’ll need to think differently about data lifecycle. **What’s similar:** * Async/await syntax * Type system concepts * Generics **What’s different:** * Ownership and borrowing * No garbage collection * No null (use Option instead) * No exceptions (use Result instead) ### Pitfall 4: “Fighting the compiler” [Section titled “Pitfall 4: “Fighting the compiler””](#pitfall-4-fighting-the-compiler) **Reality:** The Rust compiler is strict. Don’t fight it - learn from it! ```rust 1 // This won't compile 2 let s = String::from("hello"); 3 let s2 = s; 4 println!("{}", s); // Error: value borrowed after move ``` **Don’t think:** “The compiler is annoying!”\ **Think:** “The compiler is preventing a bug I would have in production!” *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. **Use Rust for the Right Problems** [Section titled “1. Use Rust for the Right Problems”](#1-use-rust-for-the-right-problems) **Great for:** * High-performance APIs * CLI tools * Systems programming * WebAssembly * Microservices with high load * Replacing Python/Ruby/Node.js bottlenecks **Not ideal for:** * Simple CRUD apps * Rapid prototyping * When team doesn’t know Rust * When time-to-market is critical ### 2. **Hybrid Approach** [Section titled “2. Hybrid Approach”](#2-hybrid-approach) Many companies use **both** Node.js and Rust: ```plaintext 1 TypeScript/Node.js: 2 - Web frontend (Next.js, React) 3 - Simple APIs 4 - Admin dashboards 5 - Internal tools 6 7 Rust: 8 - Performance-critical services 9 - Data processing pipelines 10 - Real-time systems 11 - CLI tools ``` Example: Discord adopted Rust for latency-sensitive backend services (such as their Read States service) while keeping other languages elsewhere in their stack. ### 3. **Gradual Migration** [Section titled “3. Gradual Migration”](#3-gradual-migration) Don’t rewrite everything at once: **Phase 1:** Identify bottlenecks ```typescript 1 // Profile your Node.js app 2 // Find the slow parts ``` **Phase 2:** Extract and rewrite ```rust 1 // Rewrite just the slow service in Rust 2 // Keep the rest in Node.js ``` **Phase 3:** Compare ```plaintext 1 Measure performance improvement 2 If significant: keep Rust version 3 If not: was Node.js the problem? ``` ### 4. **Learn the Ownership System First** [Section titled “4. Learn the Ownership System First”](#4-learn-the-ownership-system-first) Don’t skip the ownership section! It’s the foundation of Rust: 1. Ownership rules 2. Borrowing and references 3. Lifetimes 4. Moving vs copying Everything else in Rust builds on these concepts. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) ### Discord’s Experience [Section titled “Discord’s Experience”](#discords-experience) Discord rewrote their **Read States** service from Go to Rust to eliminate garbage-collection latency: **Before (Go):** * Latency spikes every few minutes (caused by Go’s garbage collector) * Tail-latency tied to GC pauses * Memory pressure under load **After (Rust):** * Consistent low latency * No GC pauses * Lower memory usage **Source:** [Discord: Why Discord is switching from Go to Rust](https://discord.com/blog/why-discord-is-switching-from-go-to-rust) ### Figma’s Experience [Section titled “Figma’s Experience”](#figmas-experience) Figma rewrote performance-critical parts of its multiplayer server (originally TypeScript/Node.js) in Rust: **Why Rust:** * Needed predictable performance * No GC pauses (critical for real-time collaboration) * Multi-threaded performance **Result:** * Handles millions of concurrent users * Consistent low latency * Lower infrastructure costs *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Resources [Section titled “Official Resources”](#official-resources) * [Rust vs Go](https://bitfieldconsulting.com/golang/rust-vs-go) * [Rust for Node Developers](https://github.com/Mercateo/rust-for-node-developers) * [Rust Performance Book](https://nnethercote.github.io/perf-book/) ### Case Studies [Section titled “Case Studies”](#case-studies) * [Discord: From Go to Rust](https://discord.com/blog/why-discord-is-switching-from-go-to-rust) * [AWS: Why We Built Firecracker in Rust](https://aws.amazon.com/blogs/opensource/why-aws-loves-rust-and-how-wed-like-to-help/) * [ZDNet: Microsoft to explore using Rust](https://www.zdnet.com/article/microsoft-to-explore-using-rust/) ### Community Discussions [Section titled “Community Discussions”](#community-discussions) * [r/rust: Success Stories](https://www.reddit.com/r/rust/search?q=success+story) * [Rust Users Forum](https://users.rust-lang.org/) *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Benchmark Comparison [Section titled “Exercise 1: Benchmark Comparison”](#exercise-1-benchmark-comparison) Run a simple benchmark comparing Node.js and Rust: **Node.js:** fib.ts ```typescript 1 function fibonacci(n: number): number { 2 if (n <= 1) return n; 3 return fibonacci(n - 1) + fibonacci(n - 2); 4 } 5 6 console.time("fib"); 7 console.log(fibonacci(40)); 8 console.timeEnd("fib"); ``` **Rust:** fib.rs ```rust 1 fn fibonacci(n: u32) -> u32 { 2 if n <= 1 { 3 return n; 4 } 5 fibonacci(n - 1) + fibonacci(n - 2) 6 } 7 8 fn main() { 9 let start = std::time::Instant::now(); 10 println!("{}", fibonacci(40)); 11 println!("Time: {:?}", start.elapsed()); 12 } ``` **Task:** Run both (compile the Rust version with `cargo run --release`) and compare times. The exact ratio depends heavily on your machine, the input, and how well V8’s JIT optimizes the JavaScript — for this naive recursive benchmark expect Rust to be roughly a few times faster, not orders of magnitude. The dramatic wins show up in workloads that are tight loops over typed data, parallelism, or memory layout. ### Exercise 2: Identify Use Cases [Section titled “Exercise 2: Identify Use Cases”](#exercise-2-identify-use-cases) For each scenario, decide: Node.js or Rust? 1. Simple blog with 100 daily users 2. Real-time stock trading system 3. CLI tool to process large CSV files 4. Admin dashboard for internal use 5. API serving 10,000 requests/second 6. Startup MVP with 2-week deadline Solutions 1. **Node.js** - Simple, low traffic 2. **Rust** - Needs predictable low latency 3. **Rust** - CPU-intensive, CLI tools benefit from instant startup 4. **Node.js** - Internal tool, development speed matters 5. **Could be either** - Node.js can handle it, Rust would be more efficient 6. **Node.js** - Speed of development matters most *** ## Summary [Section titled “Summary”](#summary) **Why learn Rust as a TS/JS developer:** 1. **Performance** - often substantially faster for CPU-bound and memory-bound tasks (the exact margin depends on the workload) 2. **Safety** - Catch bugs at compile time 3. **Concurrency** - Fearless multi-threading 4. **Deployment** - Single binary, no runtime 5. **Career Growth** - High demand, interesting problems 6. **Better Developer** - Understanding low-level concepts improves all coding **When to use Rust:** * Performance-critical backends * CLI tools * Systems programming * WebAssembly * Microservices with high load **When to use Node.js:** * Rapid prototyping * Simple CRUD apps * Tight deadlines * Small teams **Remember:** Rust is not a replacement for TypeScript/JavaScript. It’s an additional tool for specific problems. # Installing Rust > Let's get Rust installed on your system. This guide covers all major platforms: macOS, Linux, and Windows. Let’s get Rust installed on your system. This guide covers all major platforms: macOS, Linux, and Windows. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Rust installation is simple using **rustup**, the official Rust toolchain installer. Think of it like nvm for Node.js - it manages Rust versions and tooling. **Time required:** 10-20 minutes *** ## TypeScript/JavaScript Comparison [Section titled “TypeScript/JavaScript Comparison”](#typescriptjavascript-comparison) **Installing Node.js:** ```bash 1 # Download installer from nodejs.org 2 # Or use nvm 3 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash 4 nvm install --lts # Installs current LTS (e.g. Node 22) 5 nvm use --lts ``` **Installing Rust:** ```bash 1 # Use rustup (official installer) 2 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 3 source $HOME/.cargo/env ``` **Key similarities:** * Both use version managers (nvm/rustup) * Both install command-line tools * Both update your PATH **Key differences:** * The rustup download is larger (\~200 MB) because it bundles the toolchain, but it’s a single step * Rust includes everything (compiler, package manager, formatter, linter) * No separate npm install - Cargo is built-in! *** ## Installation by Platform [Section titled “Installation by Platform”](#installation-by-platform) ### macOS [Section titled “macOS”](#macos) #### Prerequisites [Section titled “Prerequisites”](#prerequisites) First, install Xcode Command Line Tools (if not already installed): ```bash 1 xcode-select --install ``` This provides the C compiler needed for Rust’s linker. #### Install Rust [Section titled “Install Rust”](#install-rust) ```bash 1 # Download and run rustup 2 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` **What this does:** 1. Downloads rustup 2. Installs the latest stable Rust 3. Installs cargo (package manager) 4. Adds Rust to your PATH 5. Installs rustfmt (formatter) and cargo-clippy (linter) **Interactive options:** ```plaintext 1 1) Proceed with installation (default) 2 2) Customize installation 3 3) Cancel installation ``` Just press Enter for option 1 (default). #### Configure Your Shell [Section titled “Configure Your Shell”](#configure-your-shell) ```bash 1 # Add cargo to your PATH 2 source $HOME/.cargo/env 3 4 # Or restart your terminal ``` **For zsh (default on macOS):** rustup automatically adds this line to `~/.zshrc`: ```bash 1 . "$HOME/.cargo/env" ``` **For bash:** rustup adds this to `~/.bash_profile`: ```bash 1 . "$HOME/.cargo/env" ``` #### Verify Installation [Section titled “Verify Installation”](#verify-installation) ```bash 1 # Check Rust compiler 2 rustc --version 3 # Output: rustc 1.96.0 (or newer) 4 5 # Check cargo 6 cargo --version 7 # Output: cargo 1.96.0 (or newer) 8 9 # Check rustup 10 rustup --version 11 # Output: rustup 1.29.0 (or newer) ``` *** ### Linux [Section titled “Linux”](#linux) #### Prerequisites [Section titled “Prerequisites”](#prerequisites-1) Install build essentials (C compiler and linker): **Debian/Ubuntu:** ```bash 1 sudo apt update 2 sudo apt install build-essential ``` **Fedora:** ```bash 1 sudo dnf groupinstall "Development Tools" ``` **Arch:** ```bash 1 sudo pacman -S base-devel ``` #### Install Rust [Section titled “Install Rust”](#install-rust-1) ```bash 1 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` Follow the same steps as macOS. #### Configure Your Shell [Section titled “Configure Your Shell”](#configure-your-shell-1) ```bash 1 source $HOME/.cargo/env ``` Add to your shell rc file (`~/.bashrc`, `~/.zshrc`, etc.): ```bash 1 . "$HOME/.cargo/env" ``` #### Verify Installation [Section titled “Verify Installation”](#verify-installation-1) ```bash 1 rustc --version 2 cargo --version 3 rustup --version ``` *** ### Windows [Section titled “Windows”](#windows) #### Option 1: Native Windows (Recommended) [Section titled “Option 1: Native Windows (Recommended)”](#option-1-native-windows-recommended) **Prerequisites:** Install Visual Studio C++ Build Tools: 1. Download from: 2. Run the installer 3. Select “C++ build tools” workload 4. Install (takes \~5-10 minutes) **Install Rust:** 1. Download rustup-init.exe from: 2. Run the installer 3. Follow prompts (default options work fine) 4. Restart your terminal **Verify:** ```powershell 1 rustc --version 2 cargo --version ``` #### Option 2: WSL2 (Windows Subsystem for Linux) [Section titled “Option 2: WSL2 (Windows Subsystem for Linux)”](#option-2-wsl2-windows-subsystem-for-linux) If you’re using WSL2, follow the Linux instructions above. **Why WSL2?** * Better compatibility with Unix-based tools * Faster compilation in some cases * More similar to Linux production environments **How to install WSL2:** ```powershell 1 # In PowerShell (Administrator) 2 wsl --install ``` Then follow Linux instructions inside WSL2. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### What Gets Installed? [Section titled “What Gets Installed?”](#what-gets-installed) When you run rustup, you get: **1. rustc** - The Rust compiler ```bash 1 rustc main.rs # Compiles to executable ``` **2. cargo** - Package manager and build tool ```bash 1 cargo new my_project # Create new project 2 cargo build # Build project 3 cargo run # Build and run 4 cargo test # Run tests ``` **3. rustup** - Toolchain manager ```bash 1 rustup update # Update Rust 2 rustup default stable # Set default version 3 rustup doc # Open docs ``` **4. rustfmt** - Code formatter ```bash 1 rustfmt main.rs # Format code 2 cargo fmt # Format whole project ``` **5. clippy** - Linter ```bash 1 cargo clippy # Run linter ``` **6. rust-docs** - Offline documentation ```bash 1 rustup doc # Open in browser 2 rustup doc --std # Standard library docs ``` **All included!** No separate installations needed (unlike npm, prettier, eslint, etc. in Node.js). ### Directory Structure [Section titled “Directory Structure”](#directory-structure) Rust installs to: **macOS/Linux:** ```plaintext 1 ~/.cargo/ # Cargo home 2 ├── bin/ # Executables (cargo, rustc, rustfmt, etc.) 3 ├── registry/ # Downloaded crate metadata 4 └── git/ # Git dependencies 5 6 ~/.rustup/ # Rustup home 7 ├── toolchains/ # Installed Rust versions 8 └── settings.toml # Configuration ``` **Windows:** ```plaintext 1 C:\Users\\.cargo\ # Cargo home 2 C:\Users\\.rustup\ # Rustup home ``` ### Environment Variables [Section titled “Environment Variables”](#environment-variables) Rustup adds these to your PATH: ```bash 1 # macOS/Linux 2 export PATH="$HOME/.cargo/bin:$PATH" 3 4 # Windows (added by installer) 5 %USERPROFILE%\.cargo\bin ``` **Compare to Node.js:** ```bash 1 # Node.js (with nvm) 2 export NVM_DIR="$HOME/.nvm" 3 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" ``` Similar concept, different implementation. *** ## Key Differences from Node.js [Section titled “Key Differences from Node.js”](#key-differences-from-nodejs) ### 1. Single Installer [Section titled “1. Single Installer”](#1-single-installer) **Node.js ecosystem:** ```bash 1 # Multiple separate installs 2 npm install -g typescript 3 npm install -g ts-node 4 npm install -g prettier 5 npm install -g eslint ``` **Rust:** ```bash 1 # All in one! 2 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 3 # You now have: compiler, package manager, formatter, linter, docs ``` ### 2. One-Step Installation [Section titled “2. One-Step Installation”](#2-one-step-installation) **Node.js:** * Download size: \~50-100 MB * Installation time: 2-5 minutes * node\_modules can be gigabytes per project **Rust:** * Download size: \~200 MB (includes the full toolchain, so the download is larger) * Installation time: 2-5 minutes * A single command sets up the compiler, package manager, formatter, and linter; project dependencies are compiled and cached under `~/.cargo` ### 3. Version Management Built-In [Section titled “3. Version Management Built-In”](#3-version-management-built-in) **Node.js:** ```bash 1 # Need separate tool (nvm, n, fnm) 2 nvm install --lts 3 nvm use --lts 4 nvm list ``` **Rust:** ```bash 1 # Built into rustup 2 rustup install stable 3 rustup default stable 4 rustup toolchain list ``` ### 4. Offline Documentation [Section titled “4. Offline Documentation”](#4-offline-documentation) **Node.js:** ```bash 1 # Online only (mostly) 2 # Browse to nodejs.org or devdocs.io ``` **Rust:** ```bash 1 # Full docs offline! 2 rustup doc # Opens local browser 3 rustup doc --std # Standard library 4 rustup doc --book # The Rust Book ``` *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: “command not found” After Installation [Section titled “Pitfall 1: “command not found” After Installation”](#pitfall-1-command-not-found-after-installation) **Problem:** ```bash 1 rustc --version 2 # zsh: command not found: rustc ``` **Solution:** ```bash 1 # Option 1: Restart your terminal 2 3 # Option 2: Source the cargo env 4 source $HOME/.cargo/env 5 6 # Option 3: Check PATH 7 echo $PATH | grep cargo 8 # Should see: /Users/you/.cargo/bin ``` **Why:** Your shell needs to reload its configuration to see the new PATH. ### Pitfall 2: “linking with `cc` failed” [Section titled “Pitfall 2: “linking with cc failed””](#pitfall-2-linking-with-cc-failed) **Problem:** ```bash 1 cargo build 2 # error: linking with `cc` failed ``` **Solution:** **macOS:** ```bash 1 xcode-select --install ``` **Linux:** ```bash 1 sudo apt install build-essential # Debian/Ubuntu ``` **Windows:** Install Visual Studio C++ Build Tools **Why:** Rust needs a C compiler for linking. The Rust compiler produces object files, but needs a linker to create the final executable. ### Pitfall 3: Old Version of Rust [Section titled “Pitfall 3: Old Version of Rust”](#pitfall-3-old-version-of-rust) **Problem:** Some examples don’t compile because you’re using an old Rust version. **Solution:** ```bash 1 # Update to latest stable 2 rustup update 3 4 # Check version 5 rustc --version ``` **Best practice:** Run `rustup update` periodically; a new stable release ships every 6 weeks. ### Pitfall 4: Permission Errors on Linux [Section titled “Pitfall 4: Permission Errors on Linux”](#pitfall-4-permission-errors-on-linux) **Problem:** ```bash 1 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 2 # Permission denied ``` **Solution:** Don’t use sudo! Install in your home directory: ```bash 1 # Correct (no sudo) 2 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 3 4 # Then install system packages with sudo if needed 5 sudo apt install build-essential ``` *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Keep Rust Updated [Section titled “1. Keep Rust Updated”](#1-keep-rust-updated) ```bash 1 # Update Rust (a new stable lands every 6 weeks) 2 rustup update 3 4 # Check for updates 5 rustup check ``` New versions come every 6 weeks with improvements and bug fixes. ### 2. Install rust-analyzer (VS Code) [Section titled “2. Install rust-analyzer (VS Code)”](#2-install-rust-analyzer-vs-code) ```bash 1 # In VS Code, install extension: 2 # rust-lang.rust-analyzer 3 4 # Or from terminal: 5 code --install-extension rust-lang.rust-analyzer ``` **What it provides:** * Code completion * Inline errors * Go to definition * Refactoring tools * Inline type hints **Compare to TypeScript:** ```typescript 1 // TypeScript has built-in language server 2 // Rust needs rust-analyzer extension ``` ### 3. Configure Your Editor [Section titled “3. Configure Your Editor”](#3-configure-your-editor) **VS Code settings.json:** ```json 1 { 2 "[rust]": { 3 "editor.defaultFormatter": "rust-lang.rust-analyzer", 4 "editor.formatOnSave": true 5 }, 6 "rust-analyzer.checkOnSave": true, 7 "rust-analyzer.check.command": "clippy" 8 } ``` This enables: * Format on save (like Prettier) * Clippy linting on save (like ESLint) ### 4. Set Up Shell Completions [Section titled “4. Set Up Shell Completions”](#4-set-up-shell-completions) ```bash 1 # For bash 2 rustup completions bash >> ~/.bash_completion 3 4 # For zsh 5 rustup completions zsh > ~/.zfunc/_rustup 6 7 # For fish 8 rustup completions fish > ~/.config/fish/completions/rustup.fish ``` Now you can tab-complete Rust commands! *** ## Real-World Example: Complete Setup [Section titled “Real-World Example: Complete Setup”](#real-world-example-complete-setup) Here’s a complete setup script for a new machine: **macOS/Linux:** ```bash 1 #!/bin/bash 2 3 # 1. Install prerequisites 4 # macOS 5 xcode-select --install 6 7 # Linux (Ubuntu/Debian) 8 # sudo apt update && sudo apt install build-essential 9 10 # 2. Install Rust 11 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 12 13 # 3. Source cargo env 14 source $HOME/.cargo/env 15 16 # 4. Verify installation 17 rustc --version 18 cargo --version 19 20 # 5. Install rust-analyzer (if using VS Code) 21 code --install-extension rust-lang.rust-analyzer 22 23 # 6. Configure VS Code 24 mkdir -p ~/.config/Code/User 25 cat > ~/.config/Code/User/settings.json << EOF 26 { 27 "[rust]": { 28 "editor.defaultFormatter": "rust-lang.rust-analyzer", 29 "editor.formatOnSave": true 30 } 31 } 32 EOF 33 34 echo "Rust installed successfully!" ``` **Compare to Node.js setup:** ```bash 1 #!/bin/bash 2 3 # 1. Install nvm 4 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash 5 6 # 2. Install Node.js (current LTS) 7 nvm install --lts 8 nvm use --lts 9 10 # 3. Install global tools 11 npm install -g typescript ts-node prettier eslint 12 13 # 4. Verify 14 node --version 15 npm --version 16 17 echo "Node.js installed successfully!" ``` *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [Rustup Book](https://rust-lang.github.io/rustup/) - Complete rustup guide * [Installation Page](https://www.rust-lang.org/tools/install) - Official installation guide * [Rust Forge](https://forge.rust-lang.org/) - Infrastructure documentation ### Editor Setup [Section titled “Editor Setup”](#editor-setup) * [rust-analyzer Manual](https://rust-analyzer.github.io/manual.html) - VS Code extension docs * [IntelliJ Rust](https://plugins.jetbrains.com/plugin/8182-rust) - For IntelliJ/CLion * [Vim Rust](https://github.com/rust-lang/rust.vim) - For Vim/Neovim ### Platform-Specific [Section titled “Platform-Specific”](#platform-specific) * [Windows Installation Guide](https://doc.rust-lang.org/book/ch01-01-installation.html#installing-rustup-on-windows) * [WSL2 Setup](https://docs.microsoft.com/en-us/windows/wsl/install) *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Verify Your Installation [Section titled “Exercise 1: Verify Your Installation”](#exercise-1-verify-your-installation) Run these commands and verify the output: ```bash 1 # Should print version (1.96.0 or newer) 2 rustc --version 3 4 # Should print version 5 cargo --version 6 7 # Should print version 8 rustup --version 9 10 # Should list one toolchain (stable) 11 rustup toolchain list 12 13 # Should open documentation in browser 14 rustup doc 15 16 # Should print help text 17 cargo --help ``` ### Exercise 2: Update Rust [Section titled “Exercise 2: Update Rust”](#exercise-2-update-rust) ```bash 1 # Check for updates 2 rustup check 3 4 # Update all components 5 rustup update 6 7 # Verify new version 8 rustc --version ``` ### Exercise 3: Install Additional Components [Section titled “Exercise 3: Install Additional Components”](#exercise-3-install-additional-components) ```bash 1 # Install nightly (for experimental features) 2 rustup toolchain install nightly 3 4 # Install source code (for rust-analyzer) 5 rustup component add rust-src 6 7 # List installed components 8 rustup component list --installed ``` ### Exercise 4: Configure Your Editor [Section titled “Exercise 4: Configure Your Editor”](#exercise-4-configure-your-editor) 1. Install rust-analyzer for your editor 2. Create a test file: `test.rs` 3. Type: `fn main() {` 4. Verify that auto-completion works 5. Add a syntax error and verify that inline errors appear *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * How to install Rust using rustup * What tools come with Rust (rustc, cargo, rustfmt, clippy) * How to verify your installation * Common pitfalls and solutions * Best practices for editor setup **What you have now:** * Rust compiler (rustc) * Package manager (cargo) * Formatter (rustfmt) * Linter (clippy) * Documentation (rustup doc) * Version manager (rustup) **Compare to Node.js:** * Node.js requires separate tools (npm, prettier, eslint) * Rust includes everything in one install * Both use version managers (nvm/rustup) * Rust has offline documentation built-in **You’re ready to write Rust!** # Hello World in Rust > Your first Rust program! We'll create a classic "Hello World" and compare it to TypeScript/JavaScript. Your first Rust program! We’ll create a classic “Hello World” and compare it to TypeScript/JavaScript. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Rust programs start with a `main` function, just like many other languages. Unlike JavaScript, which is interpreted, Rust is compiled to a native executable. **Time required:** 15-20 minutes *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) Let’s start with something familiar: hello.ts ```typescript 1 function main() { 2 console.log("Hello, world!"); 3 } 4 5 main(); ``` **Running it:** ```bash 1 # Option 1: With ts-node 2 ts-node hello.ts 3 4 # Option 2: Compile then run 5 tsc hello.ts # Creates hello.js 6 node hello.js # Runs the JS file ``` **What happens:** 1. TypeScript is transpiled to JavaScript 2. Node.js interprets the JavaScript 3. V8 JIT-compiles hot code paths 4. Output appears in terminal *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) hello.rs ```rust 1 fn main() { 2 println!("Hello, world!"); 3 } ``` **Running it:** ```bash 1 # Compile 2 rustc hello.rs # Creates executable 'hello' 3 4 # Run 5 ./hello # macOS/Linux 6 hello.exe # Windows ``` **What happens:** 1. Rust is compiled to native machine code 2. Linker creates standalone executable 3. No runtime or interpreter needed 4. Output appears in terminal *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Line-by-Line Comparison [Section titled “Line-by-Line Comparison”](#line-by-line-comparison) **TypeScript:** ```typescript 1 function main() { 2 // Function declaration 3 console.log("Hello, world!"); // Print to console 4 } 5 6 main(); // Call the function ``` **Rust:** ```rust 1 fn main() { // Function declaration 2 println!("Hello, world!"); // Print to console (with newline) 3 } 4 // main() is auto-called (entry point) ``` ### Key Syntax Differences [Section titled “Key Syntax Differences”](#key-syntax-differences) | Aspect | TypeScript | Rust | | ---------------- | --------------------------------------- | ----------------------------- | | Function keyword | `function` or `=>` | `fn` | | Print function | `console.log()` | `println!()` | | Statement end | `;` optional (ASI; most styles keep it) | `;` required | | Block style | `{` same line or next | `{` same line (idiomatic) | | Indentation | 2 spaces (common) | 4 spaces (standard) | | Entry point | Call `main()` explicitly | `main()` called automatically | | Macro syntax | None (no macros) | `!` indicates macro | ### The `println!` Macro [Section titled “The println! Macro”](#the-println-macro) Notice the `!` in `println!()`: ```rust 1 println!("Hello, world!"); // Macro (notice the !) ``` **What’s a macro?** * Code that writes code at compile time * Like a template that expands before compilation * `println!` is a macro, not a function **Why `!`?** * The `!` simply marks a **macro invocation** — it has nothing to do with format strings * Macros can do things functions can’t (e.g. accept a variable number of arguments and check the format string at compile time) * You’ll see it on `vec!`, `format!`, `assert!`, and many others **Compare to TypeScript:** ```typescript 1 // TypeScript has no macro system 2 // Closest equivalent: template literals 3 console.log(`Hello, world!`); ``` **Rust also has `print!` (without newline):** ```rust 1 print!("Hello, "); // No newline 2 print!("world!\n"); // Explicit newline ``` *** ## Multiple Ways to Create and Run [Section titled “Multiple Ways to Create and Run”](#multiple-ways-to-create-and-run) ### Method 1: Single File with rustc [Section titled “Method 1: Single File with rustc”](#method-1-single-file-with-rustc) **Create the file:** ```bash 1 # Create hello.rs 2 echo 'fn main() { 3 println!("Hello, world!"); 4 }' > hello.rs ``` **Compile and run:** ```bash 1 # Compile 2 rustc hello.rs 3 4 # Run 5 ./hello ``` **Pros:** * Simple and direct * No additional files needed * Good for learning **Cons:** * No dependency management * No standard project structure * Manual compilation **When to use:** Quick tests, learning exercises *** ### Method 2: Cargo Project (Recommended) [Section titled “Method 2: Cargo Project (Recommended)”](#method-2-cargo-project-recommended) **Create project:** ```bash 1 # Create new project 2 cargo new hello_world 3 cd hello_world ``` **This creates:** ```plaintext 1 hello_world/ 2 ├── Cargo.toml # Project manifest (like package.json) 3 └── src/ 4 └── main.rs # Your code (already has Hello World!) ``` **Run it:** ```bash 1 cargo run ``` **Output:** ```text 1 Compiling hello_world v0.1.0 (/path/to/hello_world) 2 Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s 3 Running `target/debug/hello_world` 4 Hello, world! ``` **Pros:** * Standard project structure * Dependency management built-in * Automatic compilation * Testing framework included **Cons:** * Slightly more complex for tiny scripts **When to use:** Any real project (always use this!) **Compare to Node.js/TypeScript:** ```bash 1 # Node.js equivalent 2 npm init -y # Create package.json 3 npm install -D typescript @types/node 4 npx tsc --init # Create tsconfig.json 5 # Create src/index.ts 6 npm run dev # (after configuring package.json) ``` Cargo is simpler - one command! *** ### Method 3: Cargo Script (nightly-only, unstable) [Section titled “Method 3: Cargo Script (nightly-only, unstable)”](#method-3-cargo-script-nightly-only-unstable) > **Warning:** Single-file “cargo scripts” are an **unstable** feature. As of Rust 1.96.0 they only work on the **nightly** toolchain behind the `-Zscript` flag. On stable, `cargo` rejects an embedded manifest with `error: embedded manifest ... requires -Zscript`. When enabled, a script carries its dependencies in a `---` TOML **frontmatter** block at the top of the file (this is the real syntax — there is no `// cargo.toml` comment manifest): ```rust 1 #!/usr/bin/env -S cargo +nightly -Zscript 2 --- 3 [package] 4 edition = "2024" 5 --- 6 7 fn main() { 8 println!("Hello, world!"); 9 } ``` **Run directly:** ```bash 1 chmod +x hello.rs 2 ./hello.rs ``` **When to use:** Quick automation scripts — but only once you are on nightly and accept that the format may still change before it stabilizes. For anything real, prefer Method 2 (a Cargo project). *** ## Key Differences from TypeScript/JavaScript [Section titled “Key Differences from TypeScript/JavaScript”](#key-differences-from-typescriptjavascript) ### 1. Compilation [Section titled “1. Compilation”](#1-compilation) **TypeScript:** ```bash 1 tsc hello.ts # Transpiles to hello.js 2 node hello.js # Node.js interprets 3 4 # Size: hello.js is ~same size as hello.ts ``` **Rust:** ```bash 1 rustc hello.rs # Compiles to machine code 2 3 # Size: executable is larger (~400KB for "Hello World") 4 # But runs much faster and needs no runtime ``` **Why Rust binary is larger:** * Includes all dependencies statically * No runtime needed * Can be stripped for production **Strip symbols for smaller binary:** ```bash 1 strip hello # Removes debug symbols (roughly a 20-30% cut for a tiny binary) ``` ### 2. Startup Time [Section titled “2. Startup Time”](#2-startup-time) **TypeScript/Node.js:** ```bash 1 time node hello.js 2 # real 0m0.050s (50ms) ``` **Rust:** ```bash 1 time ./hello 2 # real 0m0.001s (1ms) ``` **Why:** Rust is native code, no runtime or interpreter startup. ### 3. Entry Point [Section titled “3. Entry Point”](#3-entry-point) **TypeScript:** ```typescript 1 // Must explicitly call main 2 function main() { 3 console.log("Hello!"); 4 } 5 6 main(); // ← Required! ``` **Rust:** ```rust 1 // main() is automatically called at program start 2 fn main() { 3 println!("Hello!"); 4 } 5 // No explicit call needed ``` ### 4. Print Statement [Section titled “4. Print Statement”](#4-print-statement) **TypeScript/JavaScript:** ```typescript 1 console.log("Hello"); // No newline is added 2 console.log("World"); // Separate line 3 4 // Output: 5 // Hello 6 // World ``` **Rust:** ```rust 1 println!("Hello"); // Newline added automatically 2 println!("World"); // Separate line 3 4 print!("Hello"); // No newline 5 print!(" World\n"); // Manual newline 6 7 // Both output: 8 // Hello 9 // World ``` *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Forgetting the `!` in println [Section titled “Pitfall 1: Forgetting the ! in println”](#pitfall-1-forgetting-the--in-println) **Wrong:** ```rust 1 fn main() { 2 println("Hello, world!"); // Error: expected function, found macro `println` 3 } ``` **Right:** ```rust 1 fn main() { 2 println!("Hello, world!"); // Correct: println! is a macro 3 } ``` **Error message:** ```text 1 error[E0423]: expected function, found macro `println` 2 --> hello.rs:2:5 3 | 4 2 | println("Hello, world!"); 5 | ^^^^^^^ not a function 6 | 7 help: use `!` to invoke the macro 8 | 9 2 | println!("Hello, world!"); 10 | + ``` **Why:** `println!` is a macro, not a function. The `!` is required. ### Pitfall 2: Missing Semicolon [Section titled “Pitfall 2: Missing Semicolon”](#pitfall-2-missing-semicolon) A missing `;` is only an error when another statement follows it (or when a non-`()` value would be silently discarded). A single trailing expression with no semicolon is fine — it just becomes the block’s return value. **Compiles and runs** (the trailing expression evaluates to `()`): ```rust 1 fn main() { 2 println!("Hello, world!") // OK as the last expression — no `;` needed 3 } ``` **Error** — the moment a second statement follows the un-terminated one: ```rust 1 fn main() { 2 println!("Hello, world!") // now this needs a `;` 3 println!("Again!"); 4 } ``` **Error message:** ```text 1 error: expected `;`, found `println` 2 --> hello.rs:2:30 3 | 4 2 | println!("Hello, world!") 5 | ^ help: add `;` here 6 3 | println!("Again!"); 7 | ------- unexpected token 8 9 error: aborting due to 1 previous error ``` **Why:** Unlike JavaScript (which inserts semicolons automatically), Rust uses `;` to separate statements. The safe habit is to end every statement with `;`; omit it only on a deliberate trailing expression. ### Pitfall 3: Wrong File Extension [Section titled “Pitfall 3: Wrong File Extension”](#pitfall-3-wrong-file-extension) **Wrong:** ```bash 1 # Creating hello.js or hello.txt 2 echo 'fn main() { ... }' > hello.js 3 rustc hello.js // Won't work well ``` **Right:** ```bash 1 # Use .rs extension 2 echo 'fn main() { ... }' > hello.rs 3 rustc hello.rs // ``` **Why:** While rustc might compile it, tooling (rust-analyzer, cargo) expects `.rs` files. ### Pitfall 4: Trying to Run the .rs File [Section titled “Pitfall 4: Trying to Run the .rs File”](#pitfall-4-trying-to-run-the-rs-file) **Wrong:** ```bash 1 ./hello.rs // Cannot execute text file ``` **Right:** ```bash 1 # Compile first 2 rustc hello.rs 3 4 # Then run the executable 5 ./hello // ``` **Why:** Rust is compiled, not interpreted. You run the compiled binary, not the source file. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Always Use Cargo for Projects [Section titled “1. Always Use Cargo for Projects”](#1-always-use-cargo-for-projects) ** Don’t:** ```bash 1 rustc my_app.rs 2 rustc module1.rs 3 rustc module2.rs 4 # Manual compilation is painful ``` ** Do:** ```bash 1 cargo new my_app 2 cd my_app 3 cargo run 4 # Cargo handles everything ``` ### 2. Use `cargo run` for Development [Section titled “2. Use cargo run for Development”](#2-use-cargo-run-for-development) **During development:** ```bash 1 cargo run # Debug build, fast compile ``` **For production:** ```bash 1 cargo build --release # Optimized build, slow compile, fast execution 2 ./target/release/hello_world ``` **Compare compile times:** ```bash 1 time cargo build # ~0.5s (debug) 2 time cargo build --release # ~3s (release, but executable is 10x faster) ``` ### 3. Format Your Code [Section titled “3. Format Your Code”](#3-format-your-code) ```bash 1 # Format a single file 2 rustfmt hello.rs 3 4 # Format entire project 5 cargo fmt 6 7 # Check without formatting 8 cargo fmt -- --check ``` **Rust standard style:** * 4-space indentation (not 2!) * Opening brace on same line * No trailing commas in single-line ### 4. Use `println!` for Debugging [Section titled “4. Use println! for Debugging”](#4-use-println-for-debugging) ```rust 1 fn main() { 2 let x = 42; 3 println!("The value is: {}", x); // Format with variables 4 println!("x = {x}"); // Shorter syntax (Rust 2021+) 5 println!("Multiple: {} and {}", x, 10); // Multiple values 6 } ``` **Compare to TypeScript:** ```typescript 1 const x = 42; 2 console.log("The value is:", x); // Comma-separated 3 console.log(`The value is: ${x}`); // Template literal ``` *** ## Real-World Example: Slightly More Complex Hello World [Section titled “Real-World Example: Slightly More Complex Hello World”](#real-world-example-slightly-more-complex-hello-world) Let’s make it more interesting: **TypeScript:** ```typescript 1 function greet(name: string): void { 2 console.log(`Hello, ${name}!`); 3 } 4 5 function main(): void { 6 const names = ["Alice", "Bob", "Charlie"]; 7 names.forEach((name) => greet(name)); 8 } 9 10 main(); ``` **Rust:** ```rust 1 fn greet(name: &str) { 2 println!("Hello, {}!", name); 3 } 4 5 fn main() { 6 let names = vec!["Alice", "Bob", "Charlie"]; 7 for name in names { 8 greet(name); 9 } 10 } ``` **Run it:** ```bash 1 # Save as greet.rs 2 rustc greet.rs 3 ./greet ``` **Output:** ```plaintext 1 Hello, Alice! 2 Hello, Bob! 3 Hello, Charlie! ``` **Key differences:** * `&str` is a string slice (we’ll learn this in section 02) * `vec![]` is a vector (like an array) * `for` loop instead of `.forEach()` * No `void` return type needed *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book - Hello World](https://doc.rust-lang.org/book/ch01-02-hello-world.html) * [println! macro docs](https://doc.rust-lang.org/std/macro.println.html) * [Macros in Rust](https://doc.rust-lang.org/book/ch19-06-macros.html) ### Related Topics [Section titled “Related Topics”](#related-topics) * [Cargo Book - Creating a New Project](https://doc.rust-lang.org/cargo/guide/creating-a-new-project.html) * [rustc Book](https://doc.rust-lang.org/rustc/) *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Basic Hello World [Section titled “Exercise 1: Basic Hello World”](#exercise-1-basic-hello-world) Create and run a Hello World program using both methods: ```bash 1 # Method 1: rustc 2 echo 'fn main() { println!("Hello from rustc!"); }' > hello1.rs 3 rustc hello1.rs 4 ./hello1 5 6 # Method 2: cargo 7 cargo new hello2 8 cd hello2 9 cargo run ``` ### Exercise 2: Modify the Message [Section titled “Exercise 2: Modify the Message”](#exercise-2-modify-the-message) Change the program to print your name: ```rust 1 fn main() { 2 println!("Hello, !"); 3 } ``` ### Exercise 3: Multiple Prints [Section titled “Exercise 3: Multiple Prints”](#exercise-3-multiple-prints) Create a program that prints multiple lines: ```rust 1 fn main() { 2 println!("Line 1"); 3 println!("Line 2"); 4 println!("Line 3"); 5 } ``` ### Exercise 4: Print Without Newline [Section titled “Exercise 4: Print Without Newline”](#exercise-4-print-without-newline) Use `print!` to print on the same line: ```rust 1 fn main() { 2 print!("Hello, "); 3 print!("world!"); 4 println!(); // Add newline at end 5 } ``` ### Exercise 5: Format with Variables [Section titled “Exercise 5: Format with Variables”](#exercise-5-format-with-variables) ```rust 1 fn main() { 2 let name = "Rustacean"; 3 let age = 5; 4 println!("My name is {} and I am {} years old", name, age); 5 } ``` All Solutions All solutions are provided in the exercises above! Try modifying them and running with `cargo run`. *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Basic Rust syntax (`fn main`, `println!`) * How to compile with `rustc` * How to create projects with `cargo` * Differences between `print!` and `println!` * Key differences from TypeScript/JavaScript **Key syntax:** ```rust 1 fn main() { // Entry point 2 println!("Text"); // Print with newline 3 println!("Value: {}", x); // Print with formatting 4 } ``` **Commands to remember:** ```bash 1 rustc file.rs # Compile single file 2 cargo new project # Create new project 3 cargo run # Build and run 4 cargo fmt # Format code ``` **You’re ready to learn Cargo in depth!** # Understanding Cargo > Cargo is Rust's build system and package manager. Think of it as npm, webpack, jest, prettier, and eslint all combined into one tool. Cargo is Rust’s build system and package manager. Think of it as npm, webpack, jest, prettier, and eslint all combined into one tool. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Cargo is **the** Rust tool. It handles: * Project creation * Dependency management * Building and compiling * Running tests * Generating documentation * Publishing packages * Running benchmarks * And much more! **Time required:** 30-40 minutes *** ## TypeScript/JavaScript Comparison [Section titled “TypeScript/JavaScript Comparison”](#typescriptjavascript-comparison) **Node.js ecosystem (multiple tools):** ```bash 1 # Package manager 2 npm init 3 npm install express 4 npm install -D typescript 5 6 # Build tool 7 npx webpack 8 9 # Test runner 10 npx jest 11 12 # Formatter 13 npx prettier --write . 14 15 # Linter 16 npx eslint . 17 18 # Documentation 19 npx typedoc ``` **Rust (one tool - Cargo):** ```bash 1 # All-in-one tool 2 cargo new my_project # Create project 3 cargo add axum # Add dependency 4 cargo build # Build 5 cargo test # Run tests 6 cargo fmt # Format code 7 cargo clippy # Lint 8 cargo doc # Generate docs ``` **Everything through one consistent interface!** *** ## Rust Equivalent: Creating a Project [Section titled “Rust Equivalent: Creating a Project”](#rust-equivalent-creating-a-project) **TypeScript/Node.js:** ```bash 1 # Create directory 2 mkdir my-project 3 cd my-project 4 5 # Initialize 6 npm init -y 7 8 # Install TypeScript 9 npm install -D typescript @types/node 10 11 # Create tsconfig.json 12 npx tsc --init 13 14 # Create src directory 15 mkdir src 16 echo 'console.log("Hello");' > src/index.ts 17 18 # Add scripts to package.json (manual) 19 # ... 20 21 # Run 22 npx ts-node src/index.ts ``` **Result:** `package.json`, `tsconfig.json`, `node_modules/`, `src/` **Rust:** ```bash 1 # Create project (one command!) 2 cargo new my-project 3 cd my-project 4 5 # Run 6 cargo run ``` **Result:** `Cargo.toml`, `src/main.rs`, ready to go! **That’s it!** No configuration needed, batteries included. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Project Structure [Section titled “Project Structure”](#project-structure) When you run `cargo new my_project`, you get: ```plaintext 1 my_project/ 2 ├── Cargo.toml # Project manifest (like package.json) 3 ├── .git/ # Git repository (auto-initialized) 4 ├── .gitignore # Ignores /target (just one line) 5 └── src/ 6 └── main.rs # Entry point (with Hello World) ``` **Compare to Node.js/TypeScript:** ```plaintext 1 my-project/ 2 ├── package.json # Project manifest 3 ├── tsconfig.json # TypeScript config 4 ├── .gitignore # Manual creation 5 ├── node_modules/ # Dependencies (can be huge!) 6 └── src/ 7 └── index.ts # Entry point (empty) ``` ### Cargo.toml vs package.json [Section titled “Cargo.toml vs package.json”](#cargotoml-vs-packagejson) **Cargo.toml (Rust):** ```toml 1 [package] 2 name = "my_project" 3 version = "0.1.0" 4 edition = "2024" 5 6 [dependencies] 7 # Dependencies go here ``` > **Note:** You don’t pick the `edition` yourself. `cargo new` writes the newest edition supported by your toolchain into `Cargo.toml` automatically — on a recent stable toolchain that is `"2024"` (the latest stable edition). Editions are opt-in language revisions; existing code keeps compiling because crates of different editions interoperate. **package.json (Node.js):** ```json 1 { 2 "name": "my-project", 3 "version": "0.1.0", 4 "main": "dist/index.js", 5 "scripts": { 6 "build": "tsc", 7 "start": "node dist/index.js", 8 "dev": "ts-node src/index.ts", 9 "test": "jest" 10 }, 11 "dependencies": {}, 12 "devDependencies": { 13 "typescript": "^5.0.0", 14 "@types/node": "^18.0.0" 15 } 16 } ``` **Key differences:** * Cargo.toml is simpler (TOML vs JSON) * No “scripts” section needed (cargo commands are standard) * No “main” field (uses `src/main.rs` or `src/lib.rs` by convention) * No devDependencies distinction (just `[dependencies]` and `[dev-dependencies]`) *** ## Essential Cargo Commands [Section titled “Essential Cargo Commands”](#essential-cargo-commands) ### 1. Project Creation [Section titled “1. Project Creation”](#1-project-creation) ```bash 1 # Create binary (executable) project 2 cargo new my_app 3 4 # Create library project 5 cargo new my_lib --lib ``` **Compare to npm:** ```bash 1 npm init -y # Creates package.json 2 # But doesn't create any code structure ``` ### 2. Building [Section titled “2. Building”](#2-building) ```bash 1 # Debug build (fast compile, slow runtime) 2 cargo build 3 4 # Release build (slow compile, fast runtime) 5 cargo build --release 6 7 # Check without building (faster) 8 cargo check ``` **Output locations:** * Debug: `target/debug/my_app` * Release: `target/release/my_app` **Compare to TypeScript:** ```bash 1 tsc # Compile to JavaScript 2 npx webpack # Bundle 3 npm run build # (custom script) ``` **Build times comparison:** ```plaintext 1 cargo build # 0.5s (debug) 2 cargo build --release # 3-10s (release, but 10x faster execution) 3 tsc # 0.1-1s (depends on project size) ``` ### 3. Running [Section titled “3. Running”](#3-running) ```bash 1 # Build and run (debug) 2 cargo run 3 4 # Build and run (release) 5 cargo run --release 6 7 # Run with arguments 8 cargo run -- arg1 arg2 ``` **The `--` separator** passes arguments to your program, not to cargo. ```bash 1 # These arguments go to cargo 2 cargo run --release 3 4 # These arguments go to your program 5 cargo run -- --help ``` **Compare to Node.js:** ```bash 1 node dist/index.js # Run compiled JS 2 npx ts-node src/index.ts # Run TS directly 3 npm start # (custom script) ``` ### 4. Testing [Section titled “4. Testing”](#4-testing) ```bash 1 # Run all tests 2 cargo test 3 4 # Run specific test 5 cargo test test_name 6 7 # Run tests with output 8 cargo test -- --nocapture ``` **Compare to JavaScript:** ```bash 1 npm test # (jest, vitest, etc.) 2 npm run test:watch ``` Cargo’s test runner is built-in, no configuration needed! ### 5. Dependencies [Section titled “5. Dependencies”](#5-dependencies) ```bash 1 # Add dependency 2 cargo add serde 3 4 # Add dev dependency 5 cargo add --dev criterion 6 7 # Add with specific version 8 cargo add serde --version 1.0 9 10 # Remove dependency 11 cargo remove serde 12 13 # Update dependencies 14 cargo update ``` **Compare to npm:** ```bash 1 npm install express 2 npm install -D jest 3 npm install express@4.18.0 4 npm uninstall express 5 npm update ``` > **Note:** `cargo add` is built into Cargo (since Cargo 1.62, June 2022) — no extra install needed. You may still see older tutorials say “run `cargo install cargo-edit` first”; that hasn’t been required for years. You can always edit `Cargo.toml` by hand instead if you prefer: ```toml 1 [dependencies] 2 serde = "1.0" ``` ### 6. Code Quality [Section titled “6. Code Quality”](#6-code-quality) ```bash 1 # Format code (like Prettier) 2 cargo fmt 3 4 # Check formatting without changing 5 cargo fmt -- --check 6 7 # Lint (like ESLint) 8 cargo clippy 9 10 # Lint with auto-fix suggestions 11 cargo clippy --fix ``` **Compare to JavaScript:** ```bash 1 npx prettier --write . 2 npx prettier --check . 3 npx eslint . 4 npx eslint --fix . ``` ### 7. Documentation [Section titled “7. Documentation”](#7-documentation) ```bash 1 # Build documentation 2 cargo doc 3 4 # Build and open in browser 5 cargo doc --open 6 7 # Include private items 8 cargo doc --document-private-items ``` **Compare to TypeScript:** ```bash 1 npx typedoc --out docs src/ ``` Cargo docs are generated from code comments: ````rust 1 /// Returns the sum of two numbers 2 /// 3 /// # Examples 4 /// 5 /// ``` 6 /// let result = add(2, 3); 7 /// assert_eq!(result, 5); 8 /// ``` 9 pub fn add(a: i32, b: i32) -> i32 { 10 a + b 11 } ```` **`cargo doc`** automatically generates beautiful HTML documentation! *** ## Key Differences from npm [Section titled “Key Differences from npm”](#key-differences-from-npm) ### 1. No node\_modules! [Section titled “1. No node\_modules!”](#1-no-node_modules) **Node.js:** ```bash 1 npm install 2 # Creates node_modules/ (can be gigabytes!) 3 # Need to .gitignore it 4 # Re-download for every project ``` **Rust:** ```bash 1 cargo build 2 # Downloads to ~/.cargo/registry/ (shared across projects) 3 # Only compiled artifacts in target/ 4 # Reused across projects ``` **Why it matters:** * Faster builds (shared cache) * Less disk space * Faster git operations (no node\_modules to scan) ### 2. Lock Files [Section titled “2. Lock Files”](#2-lock-files) **package-lock.json (npm):** ```json 1 { 2 "name": "my-project", 3 "lockfileVersion": 3, 4 "requires": true, 5 "packages": { 6 "": { 7 // ... hundreds of lines ... 8 } 9 } 10 } ``` * Always commit to git * Ensures reproducible builds * Can be huge (thousands of lines) **Cargo.lock (Rust):** ```toml 1 # This file is automatically @generated by Cargo. 2 # It is not intended for manual editing. 3 [[package]] 4 name = "my_project" 5 version = "0.1.0" 6 7 [[package]] 8 name = "serde" 9 version = "1.0.152" ``` * Commit for binaries, don’t commit for libraries * Ensures reproducible builds * Usually smaller ### 3. Scripts [Section titled “3. Scripts”](#3-scripts) **package.json:** ```json 1 { 2 "scripts": { 3 "build": "tsc", 4 "start": "node dist/index.js", 5 "dev": "ts-node src/index.ts", 6 "test": "jest", 7 "lint": "eslint .", 8 "format": "prettier --write ." 9 } 10 } ``` **Cargo:** No scripts needed! Everything is a standard command: ```bash 1 cargo build # Standard 2 cargo run # Standard 3 cargo test # Standard 4 cargo fmt # Standard 5 cargo clippy # Standard ``` **For custom tasks** (the equivalent of npm `scripts`), Cargo has a couple of native options and several community tools: * **Cargo aliases** — define command shortcuts in `.cargo/config.toml`: .cargo/config.toml ```toml 1 [alias] 2 br = "build --release" ``` Then `cargo br` runs `cargo build --release`. Aliases only chain Cargo subcommands, not arbitrary shell. * **`cargo xtask`** — a convention where build automation lives in an ordinary Rust binary you run with `cargo xtask `. * **`cargo-make`** — a third-party task runner (`cargo install cargo-make`) for richer, cross-platform task definitions. > **Note:** Unlike npm, Cargo has no built-in `scripts` table that runs shell commands. But most projects don’t need one — the standard subcommands cover the common workflow. ### 4. Semantic Versioning [Section titled “4. Semantic Versioning”](#4-semantic-versioning) **Both** use semver, but Cargo enforces it more strictly: **Cargo.toml:** ```toml 1 [dependencies] 2 serde = "1.0" # Caret range: >=1.0.0, <2.0.0 3 tokio = "1.25.0" # ALSO a caret range: >=1.25.0, <2.0.0 (NOT exact!) 4 tokio_exact = "=1.25.0" # THIS is exact: only 1.25.0 5 regex = "1" # Caret range: >=1.0.0, <2.0.0 ``` > **Warning:** A bare version string like `"1.25.0"` is **not** an exact pin — Cargo treats it as the caret requirement `>=1.25.0, <2.0.0`. To require an exact version, prefix it with `=`, as in `"=1.25.0"`. **package.json:** ```json 1 { 2 "dependencies": { 3 "express": "^4.18.0", // 4.18.0 <= version < 5.0.0 4 "lodash": "4.17.21", // Exact 5 "react": "^18" // Latest 18.x 6 } 7 } ``` **Cargo is more conservative** with updates by default. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Forgetting to Rebuild [Section titled “Pitfall 1: Forgetting to Rebuild”](#pitfall-1-forgetting-to-rebuild) **Problem:** ```bash 1 # Edit main.rs 2 # Run old binary 3 ./target/debug/my_app # Runs OLD version! ``` **Solution:** ```bash 1 cargo run # Always rebuilds if needed ``` **Or:** ```bash 1 cargo build 2 ./target/debug/my_app # Now runs new version ``` ### Pitfall 2: Large target/ Directory [Section titled “Pitfall 2: Large target/ Directory”](#pitfall-2-large-target-directory) **Problem:** ```bash 1 du -sh target/ 2 # 5.0G target/ # Huge! ``` **Solution:** ```bash 1 # Clean build artifacts 2 cargo clean 3 4 # Or clean all Rust projects 5 cargo install cargo-cache 6 cargo cache --autoclean ``` **Why it gets big:** * Multiple debug/release builds * Dependencies compiled for your project * Test binaries **Compare to node\_modules:** ```bash 1 du -sh node_modules/ 2 # 500M node_modules/ # Also huge! ``` But `target/` can be cleaned and regenerated quickly. ### Pitfall 3: Slow First Build [Section titled “Pitfall 3: Slow First Build”](#pitfall-3-slow-first-build) **Problem:** ```bash 1 cargo build 2 # Compiling 150 crates... (takes 5 minutes) ``` **Why:** Cargo compiles all dependencies from source. **Solutions:** 1. **Use `cargo check` during development:** ```bash 1 cargo check # Faster, just type-checks ``` 2. **Use release mode only when needed:** ```bash 1 cargo build # Debug mode (fast compile) 2 # Only use --release for production ``` 3. **Use `sccache` for caching:** ```bash 1 cargo install sccache 2 export RUSTC_WRAPPER=sccache ``` ### Pitfall 4: Committing Cargo.lock (or not) [Section titled “Pitfall 4: Committing Cargo.lock (or not)”](#pitfall-4-committing-cargolock-or-not) **Rule:** * **Binary projects:** Commit `Cargo.lock` * **Library projects:** Don’t commit `Cargo.lock` **Why:** * Binaries need reproducible builds * Libraries want to test with latest compatible versions **Add to .gitignore (for libraries):** ```text 1 /target/ 2 /Cargo.lock ``` *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Use cargo fmt on Save [Section titled “1. Use cargo fmt on Save”](#1-use-cargo-fmt-on-save) **VS Code settings.json:** ```json 1 { 2 "[rust]": { 3 "editor.formatOnSave": true 4 } 5 } ``` **Or manually:** ```bash 1 cargo fmt ``` **Before commit:** ```bash 1 cargo fmt --check # Verify formatting ``` ### 2. Run clippy Before Commit [Section titled “2. Run clippy Before Commit”](#2-run-clippy-before-commit) ```bash 1 cargo clippy ``` **Or make it strict:** ```bash 1 cargo clippy -- -D warnings # Treat warnings as errors ``` **CI integration:** .github/workflows/ci.yml ```yaml 1 - name: Run clippy 2 run: cargo clippy -- -D warnings ``` ### 3. Use cargo check During Development [Section titled “3. Use cargo check During Development”](#3-use-cargo-check-during-development) **Instead of:** ```bash 1 cargo build # Slow: creates executable ``` **Use:** ```bash 1 cargo check # Fast: only type-checks ``` **3-5x faster!** Use during development, `cargo build` only when you need to run. ### 4. Separate Dev and Prod Dependencies [Section titled “4. Separate Dev and Prod Dependencies”](#4-separate-dev-and-prod-dependencies) ```toml 1 [dependencies] 2 # Production dependencies 3 serde = "1.0" 4 tokio = "1.25" 5 6 [dev-dependencies] 7 # Only used in tests/benchmarks 8 criterion = "0.5" 9 mock_instant = "0.3" ``` **Compare to package.json:** ```json 1 { 2 "dependencies": { 3 /* prod */ 4 }, 5 "devDependencies": { 6 /* dev only */ 7 } 8 } ``` ### 5. Use Workspaces for Monorepos [Section titled “5. Use Workspaces for Monorepos”](#5-use-workspaces-for-monorepos) ```plaintext 1 my-workspace/ 2 ├── Cargo.toml # Workspace manifest 3 ├── api/ 4 │ ├── Cargo.toml 5 │ └── src/ 6 ├── cli/ 7 │ ├── Cargo.toml 8 │ └── src/ 9 └── shared/ 10 ├── Cargo.toml 11 └── src/ ``` **Root Cargo.toml:** ```toml 1 [workspace] 2 members = ["api", "cli", "shared"] ``` **Compare to npm workspaces:** ```json 1 { 2 "workspaces": ["packages/*"] 3 } ``` Similar concept, different implementation. *** ## Real-World Example: Web API Project [Section titled “Real-World Example: Web API Project”](#real-world-example-web-api-project) **Create project:** ```bash 1 cargo new web_api 2 cd web_api ``` **Add dependencies:** ```bash 1 cargo add axum 2 cargo add tokio --features full 3 cargo add serde --features derive ``` **Or manually edit Cargo.toml:** ```toml 1 [package] 2 name = "web_api" 3 version = "0.1.0" 4 edition = "2024" 5 6 [dependencies] 7 axum = "0.8" 8 tokio = { version = "1", features = ["full"] } 9 serde = { version = "1.0", features = ["derive"] } 10 11 [profile.release] 12 opt-level = 3 # Maximum optimization 13 lto = true # Link-time optimization 14 codegen-units = 1 # Better optimization, slower compile ``` **Build and run:** ```bash 1 # Development 2 cargo run 3 4 # Production 5 cargo build --release 6 ./target/release/web_api ``` **Compare to Node.js/Express:** ```bash 1 npm init -y 2 npm install express typescript @types/node @types/express 3 npm install -D ts-node nodemon 4 # Edit tsconfig.json 5 # Edit package.json scripts 6 npm run dev ``` Cargo is simpler! *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Cargo Book](https://doc.rust-lang.org/cargo/) - Complete guide * [Cargo Commands Reference](https://doc.rust-lang.org/cargo/commands/index.html) * [Manifest Format (Cargo.toml)](https://doc.rust-lang.org/cargo/reference/manifest.html) ### Useful Tools [Section titled “Useful Tools”](#useful-tools) * [cargo-edit](https://github.com/killercup/cargo-edit) - `cargo upgrade`, `cargo set-version` (`cargo add`/`cargo remove` are now built into Cargo) * [cargo-watch](https://github.com/watchexec/cargo-watch) - Auto-rebuild on file change * [cargo-expand](https://github.com/dtolnay/cargo-expand) - Expand macros * [cargo-tree](https://doc.rust-lang.org/cargo/commands/cargo-tree.html) - View dependency tree (built-in) ### Comparisons [Section titled “Comparisons”](#comparisons) * [Cargo vs npm](https://www.reddit.com/r/rust/comments/3x4c2h/cargo_vs_npm/) * [Cargo for npm users](https://github.com/Mercateo/rust-for-node-developers/blob/master/Cargo-for-npm-users.md) *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Create and Run a Project [Section titled “Exercise 1: Create and Run a Project”](#exercise-1-create-and-run-a-project) ```bash 1 cargo new hello_cargo 2 cd hello_cargo 3 cargo run ``` ### Exercise 2: Add a Dependency [Section titled “Exercise 2: Add a Dependency”](#exercise-2-add-a-dependency) Add the `rand` crate and use it: **Cargo.toml:** ```toml 1 [dependencies] 2 rand = "0.9" ``` **src/main.rs:** ```rust 1 use rand::Rng; 2 3 fn main() { 4 let random_number = rand::rng().random_range(1..=100); 5 println!("Random number: {random_number}"); 6 } ``` > **Note:** rand 0.9 renamed the old `thread_rng()` to `rng()` and `gen_range()` to `random_range()` (because `gen` became a reserved keyword in edition 2024). If you follow a rand 0.8 tutorial, those are the two calls to update. **Run:** ```bash 1 cargo run ``` ### Exercise 3: Explore cargo Commands [Section titled “Exercise 3: Explore cargo Commands”](#exercise-3-explore-cargo-commands) ```bash 1 cargo build # Build 2 cargo clean # Clean 3 cargo doc --open # Generate and open docs 4 cargo tree # Show dependency tree 5 cargo --version # Show version 6 cargo --list # List all commands ``` ### Exercise 4: Create a Library [Section titled “Exercise 4: Create a Library”](#exercise-4-create-a-library) ```bash 1 cargo new my_lib --lib 2 cd my_lib ``` **src/lib.rs:** ```rust 1 pub fn add(a: i32, b: i32) -> i32 { 2 a + b 3 } 4 5 #[cfg(test)] 6 mod tests { 7 use super::*; 8 9 #[test] 10 fn test_add() { 11 assert_eq!(add(2, 3), 5); 12 } 13 } ``` **Run tests:** ```bash 1 cargo test ``` ### Exercise 5: Format and Lint [Section titled “Exercise 5: Format and Lint”](#exercise-5-format-and-lint) ```bash 1 # Format code 2 cargo fmt 3 4 # Check formatting 5 cargo fmt --check 6 7 # Lint 8 cargo clippy ``` *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Cargo is the all-in-one Rust tool * How to create projects (`cargo new`) * How to build and run (`cargo build`, `cargo run`) * How to manage dependencies (edit `Cargo.toml`) * How to test, format, and lint * Key differences from npm **Essential commands:** ```bash 1 cargo new # Create project 2 cargo run # Build and run 3 cargo test # Run tests 4 cargo fmt # Format code 5 cargo clippy # Lint 6 cargo doc --open # Generate docs 7 cargo clean # Clean build artifacts ``` **Cargo.toml structure:** ```toml 1 [package] 2 name = "my_project" 3 version = "0.1.0" 4 edition = "2024" # cargo new fills in the newest edition for you 5 6 [dependencies] 7 # Add dependencies here ``` **You now understand Rust’s tooling!** # Rust Playground > Try Rust in your browser without installing anything! The Rust Playground is an online tool for experimenting, learning, and sharing code. Try Rust in your browser without installing anything! The Rust Playground is an online tool for experimenting, learning, and sharing code. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) The Rust Playground ([play.rust-lang.org](https://play.rust-lang.org/)) lets you write Rust code in your browser and compile/run it on Rust’s servers (in a sandbox, not in the browser itself). It’s perfect for: * Quick experiments * Learning Rust * Sharing code snippets * Testing ideas **Time required:** 10-15 minutes to explore *** ## TypeScript/JavaScript Comparison [Section titled “TypeScript/JavaScript Comparison”](#typescriptjavascript-comparison) **TypeScript Playground:** * [TypeScript Playground](https://www.typescriptlang.org/play) * Transpiles TypeScript to JavaScript * Shows type errors * Browser-based **Rust Playground:** * [Rust Playground](https://play.rust-lang.org/) * Compiles and runs your code on Rust’s servers in a sandbox (not in your browser) * Shows compiler errors * Can switch between editions * Can run tests * Can format code * Can share via URL **Similar tools:** * [CodeSandbox](https://codesandbox.io/) - Full dev environment * [StackBlitz](https://stackblitz.com/) - Online VS Code * [Replit](https://replit.com/) - Multi-language online IDE *** ## Accessing the Playground [Section titled “Accessing the Playground”](#accessing-the-playground) **URL:** **When you open it, you see:** ```rust 1 fn main() { 2 println!("Hello, world!"); 3 } ``` **Ready to run immediately!** Click “Run” or press `Ctrl+Enter`. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Main Features [Section titled “Main Features”](#main-features) #### 1. Code Editor [Section titled “1. Code Editor”](#1-code-editor) * Syntax highlighting * Auto-completion * Error underlining (as you type!) * Multi-cursor editing (`Ctrl+Click`) **Try this:** ```rust 1 fn main() { 2 let x = 5; 3 println!("x = {}", x); 4 } ``` #### 2. Compiler Output [Section titled “2. Compiler Output”](#2-compiler-output) **Click “Run”** and see: ```plaintext 1 Compiling playground v0.0.1 (/playground) 2 Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.50s 3 Running `target/debug/playground` 4 x = 5 ``` * Shows compilation steps * Shows program output * Shows any errors or warnings #### 3. Share Button [Section titled “3. Share Button”](#3-share-button) **Create shareable link:** 1. Write your code 2. Click “Share” 3. Copy the URL 4. Anyone with the URL can see and run your code **Example URL:** ```plaintext 1 https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=abc123xyz ``` **Compare to TypeScript Playground:** * TypeScript encodes code in URL * Rust uses gist (cleaner URLs) #### 4. Configuration Options [Section titled “4. Configuration Options”](#4-configuration-options) **Edition:** * 2015 (original Rust) * 2018 (modern features) * 2021 (widely used) * 2024 (latest stable edition, recommended for new code) **Mode:** * Debug (fast compile, slow runtime) * Release (slow compile, fast runtime) **Channel:** * Stable (most recent release) * Beta (next release preview) * Nightly (latest features, unstable) ### Top Toolbar [Section titled “Top Toolbar”](#top-toolbar) ```plaintext 1 [Run ▶] [Format] [Clippy] [Miri] [Tools ▼] [Config ▼] [Share] [Help] ``` **Run** - Compile and execute **Format** - Auto-format code (like `cargo fmt`) **Clippy** - Lint code (like `cargo clippy`) **Miri** - Advanced interpreter (for unsafe code) **Tools** - ASM, LLVM IR, MIR, macro expansion, etc. **Config** - Edition, mode, channel **Share** - Get shareable link **Help** - Keyboard shortcuts > **Note:** The exact button labels and grouping shift between Playground versions. The capabilities below are stable even if the menu names change. *** ## Key Differences from TypeScript Playground [Section titled “Key Differences from TypeScript Playground”](#key-differences-from-typescript-playground) ### 1. Actual Compilation [Section titled “1. Actual Compilation”](#1-actual-compilation) **TypeScript Playground:** ```typescript 1 let x = 5; 2 console.log(x); 3 // Transpiled to JavaScript, runs in browser JS engine ``` **Rust Playground:** ```rust 1 let x = 5; 2 println!("{}", x); 3 // Sent to Rust's servers, compiled to a native binary, run in a sandbox ``` > **Note:** Unlike the TypeScript Playground (which transpiles and runs entirely in your browser), the Rust Playground sends your code to a backend, compiles it with a real `rustc`/`cargo` toolchain, and runs the resulting native binary in a sandboxed container. That’s why it needs a moment to respond and why it can report genuine compile and runtime errors. **Why it matters:** Rust Playground shows real compile errors, not just type errors. ### 2. More Tool Integration [Section titled “2. More Tool Integration”](#2-more-tool-integration) **TypeScript:** * Basic editor * Type checking * JS output **Rust:** * Editor with completions * Compiler errors/warnings * Format code * Lint code * View assembly * Run tests * View macro expansions ### 3. Shareable Builds [Section titled “3. Shareable Builds”](#3-shareable-builds) **Rust Playground can share:** * Source code * Compiler output * Standard output * Entire configuration **Perfect for:** * Asking questions on forums * Reporting bugs * Teaching examples *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Expecting File System Access [Section titled “Pitfall 1: Expecting File System Access”](#pitfall-1-expecting-file-system-access) **Problem:** ```rust 1 use std::fs; 2 3 fn main() { 4 let content = fs::read_to_string("file.txt").unwrap(); 5 println!("{}", content); 6 } ``` **Error:** ```plaintext 1 No such file or directory (os error 2) ``` **Why:** The playground is sandboxed, no file system access. **Solution:** Use string literals instead: ```rust 1 fn main() { 2 let content = "This is my file content"; 3 println!("{}", content); 4 } ``` ### Pitfall 2: Network Requests Don’t Work [Section titled “Pitfall 2: Network Requests Don’t Work”](#pitfall-2-network-requests-dont-work) **Problem:** ```rust 1 // This won't work! 2 let response = reqwest::get("https://api.example.com").await?; ``` **Why:** No network access in the playground. **Solution:** For network code, install Rust locally. ### Pitfall 3: Limited Dependencies [Section titled “Pitfall 3: Limited Dependencies”](#pitfall-3-limited-dependencies) **Problem:** ```rust 1 use some_obscure_crate::Thing; // Not available ``` **Why:** Playground has only the top 100 crates. **Available crates:** * Standard library (always) * Popular crates (serde, tokio, regex, etc.) **To check if a crate is available:** Look for the “ADD CRATE” button (shows available crates). ### Pitfall 4: Execution Time Limit [Section titled “Pitfall 4: Execution Time Limit”](#pitfall-4-execution-time-limit) **Problem:** ```rust 1 fn main() { 2 loop { 3 // Infinite loop 4 } 5 } ``` **Result:** Timeout after 30 seconds. **Solution:** Write code that completes quickly. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Use for Quick Tests [Section titled “1. Use for Quick Tests”](#1-use-for-quick-tests) **Good use:** ```rust 1 // Test how Option works 2 fn main() { 3 let x: Option = Some(5); 4 match x { 5 Some(val) => println!("Value: {}", val), 6 None => println!("No value"), 7 } 8 } ``` Quick test, immediate feedback! ### 2. Format Before Sharing [Section titled “2. Format Before Sharing”](#2-format-before-sharing) **Always click “Format”** before sharing code: * Makes code readable * Follows Rust conventions * Professional appearance ### 3. Use Clippy for Learning [Section titled “3. Use Clippy for Learning”](#3-use-clippy-for-learning) **Click “Clippy”** to get suggestions. Clippy goes beyond compiler errors and flags non-idiomatic code: ```rust 1 fn main() { 2 let s = String::from("hello"); 3 if s.len() == 0 { 4 println!("empty"); 5 } 6 } ``` **Clippy reports:** ```plaintext 1 warning: length comparison to zero 2 --> src/main.rs:3:8 3 | 4 3 | if s.len() == 0 { 5 | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` 6 | 7 = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero 8 = note: `#[warn(clippy::len_zero)]` on by default ``` **Learn from suggestions!** ### 4. Share Examples in Forums [Section titled “4. Share Examples in Forums”](#4-share-examples-in-forums) When asking for help: 1. Write minimal example in playground 2. Click “Share” 3. Post the link **Example:** “I’m having trouble with this: ” Much better than pasting code in a forum post! *** ## Real-World Example: Testing an Idea [Section titled “Real-World Example: Testing an Idea”](#real-world-example-testing-an-idea) **Scenario:** You want to test how Rust’s `Result` type works. **1. Go to playground** **2. Write code:** ```rust 1 fn divide(a: i32, b: i32) -> Result { 2 if b == 0 { 3 Err("Cannot divide by zero".to_string()) 4 } else { 5 Ok(a / b) 6 } 7 } 8 9 fn main() { 10 match divide(10, 2) { 11 Ok(result) => println!("Result: {}", result), 12 Err(e) => println!("Error: {}", e), 13 } 14 15 match divide(10, 0) { 16 Ok(result) => println!("Result: {}", result), 17 Err(e) => println!("Error: {}", e), 18 } 19 } ``` **3. Click “Run”** **Output:** ```plaintext 1 Result: 5 2 Error: Cannot divide by zero ``` **4. Click “Format”** to clean up **5. Click “Clippy”** for suggestions **6. Click “Share”** if you want to save it *** ## Advanced Features [Section titled “Advanced Features”](#advanced-features) ### 1. View Assembly [Section titled “1. View Assembly”](#1-view-assembly) Open the tools/options menu and choose to show the assembly output. See the actual machine code generated (x86-64, the Playground’s target): ```rust 1 pub fn add(a: i32, b: i32) -> i32 { 2 a + b 3 } ``` **Assembly output:** ```asm 1 playground::add: 2 lea eax, [rdi + rsi] 3 ret ``` **Why useful:** Understanding optimization. ### 2. View Macro Expansion [Section titled “2. View Macro Expansion”](#2-view-macro-expansion) Open the tools/options menu and choose to expand macros (this uses the nightly toolchain). See what macros expand to: ```rust 1 fn main() { 2 println!("Hello, {}", "world"); 3 } ``` **Expanded:** ```rust 1 fn main() { 2 { 3 ::std::io::_print(format_args!("Hello, {0}\n", "world")); 4 }; 5 } ``` **Why useful:** Understanding how macros work. ### 3. Run Tests [Section titled “3. Run Tests”](#3-run-tests) ```rust 1 fn add(a: i32, b: i32) -> i32 { 2 a + b 3 } 4 5 #[test] 6 fn test_add() { 7 assert_eq!(add(2, 3), 5); 8 } 9 10 #[test] 11 fn test_add_negative() { 12 assert_eq!(add(-1, 1), 0); 13 } ``` **Click “Test”** instead of “Run”: ```plaintext 1 running 2 tests 2 test test_add ... ok 3 test test_add_negative ... ok 4 5 test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` *** ## Keyboard Shortcuts [Section titled “Keyboard Shortcuts”](#keyboard-shortcuts) The most reliable shortcut is `Ctrl+Enter` (run the code). The editor also supports many of the usual code-editor bindings for commenting, moving lines, multi-cursor editing, and find/replace. | Shortcut | Action (typical) | | --------------- | ---------------------- | | `Ctrl+Enter` | Run code | | `Ctrl+K Ctrl+C` | Comment selection | | `Ctrl+K Ctrl+U` | Uncomment selection | | `Ctrl+/` | Toggle comment | | `Alt+↑/↓` | Move line up/down | | `Ctrl+D` | Select next occurrence | | `Ctrl+F` | Find | | `Ctrl+H` | Find and replace | > **Note:** Editor bindings can differ by browser, platform, and Playground version, so treat the table above as a guide rather than a guarantee. Formatting and Clippy are run from their toolbar buttons. For the current authoritative list, click “Help” in the toolbar. *** ## Comparison to Local Development [Section titled “Comparison to Local Development”](#comparison-to-local-development) | Feature | Playground | Local (cargo) | | -------------------- | --------------- | ------------------------- | | Installation | None needed | rustup install | | Startup time | Instant | cargo run \~0.5s | | File system access | No | Yes | | Network access | No | Yes | | Available crates | Top 100 only | All crates | | Execution time limit | 30 seconds | No limit | | Code sharing | Easy (URL) | Manual (gist, github) | | Editor features | Basic | Full (with rust-analyzer) | | Debugging | Limited | Full (lldb/gdb) | | Performance | Slightly slower | Full native speed | **When to use Playground:** * Learning basics * Quick tests * Sharing examples * No installation needed **When to use local:** * Real projects * Need file system * Need specific crates * Performance testing *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Resources [Section titled “Official Resources”](#official-resources) * [Rust Playground](https://play.rust-lang.org/) * [Playground Repository](https://github.com/rust-lang/rust-playground) * [Available Crates List](https://play.rust-lang.org/help) ### Similar Tools [Section titled “Similar Tools”](#similar-tools) * [Godbolt Compiler Explorer](https://godbolt.org/z/Rust) - View assembly * [Rust Explorer](https://www.rustexplorer.com/) - Another online IDE ### Community [Section titled “Community”](#community) * Share playground links on [r/rust](https://reddit.com/r/rust) * Use in [Rust Discord](https://discord.gg/rust-lang) * Reference in GitHub issues *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Hello Playground [Section titled “Exercise 1: Hello Playground”](#exercise-1-hello-playground) 1. Go to 2. Modify the default program 3. Click “Run” 4. Click “Format” 5. Click “Share” and copy the URL ### Exercise 2: Test Compiler Errors [Section titled “Exercise 2: Test Compiler Errors”](#exercise-2-test-compiler-errors) Try this code: ```rust 1 fn main() { 2 let x = 5; 3 x = 10; // Error! 4 println!("{}", x); 5 } ``` Read the error message. What does it say? Answer ```plaintext 1 error[E0384]: cannot assign twice to immutable variable `x` ``` Variables are immutable by default in Rust! **Fix:** ```rust 1 fn main() { 2 let mut x = 5; // Add 'mut' 3 x = 10; 4 println!("{}", x); 5 } ``` ### Exercise 3: Use Clippy [Section titled “Exercise 3: Use Clippy”](#exercise-3-use-clippy) Write this code: ```rust 1 fn main() { 2 let s = String::from("hello"); 3 let len = s.len(); 4 println!("{}", len); 5 } ``` Click “Clippy”. Does it suggest anything? ### Exercise 4: Format Messy Code [Section titled “Exercise 4: Format Messy Code”](#exercise-4-format-messy-code) Paste this: ```rust 1 fn main(){let x=5;let y=10;println!("{} {}",x,y);} ``` Click “Format”. See the difference! ### Exercise 5: Share Your Code [Section titled “Exercise 5: Share Your Code”](#exercise-5-share-your-code) 1. Write any Rust code 2. Click “Share” 3. Open the link in a private/incognito window 4. Verify your code is there! *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * How to use the Rust Playground * When to use it vs local development * How to share code via URL * Available tools (Format, Clippy, etc.) * Limitations (no file system, network) **Rust Playground features:** * Instant code execution * Compiler error messages * Format and lint tools * Easy sharing * No installation needed **Best for:** * Learning Rust * Quick experiments * Sharing examples * Teaching **Not suitable for:** * Real projects * File I/O * Network requests * Large dependencies **URL to remember:** **You’re now ready to start learning Rust syntax!** # Rust Basics > Learn the fundamental building blocks of Rust: variables, types, operators, and basic syntax. Learn the fundamental building blocks of Rust: variables, types, operators, and basic syntax. *** ## What’s in This Section [Section titled “What’s in This Section”](#whats-in-this-section) * **[Variables and Mutability](/02-basics/00-variables/)** - let, mut, shadowing, constants * **[Basic Types](/02-basics/01-types/)** - Integers, floats, booleans, characters, tuples * **[Operators](/02-basics/02-operators/)** - Arithmetic, comparison, logical, and more * **[Comments](/02-basics/03-comments/)** - Code comments and documentation * **[Output and Formatting](/02-basics/04-output/)** - println!, print!, formatting *** ## Learning Objectives [Section titled “Learning Objectives”](#learning-objectives) By the end of this section, you will: * Understand Rust’s immutability-by-default philosophy * Know all basic data types and when to use them * Use type annotations and type inference * Perform arithmetic and logical operations * Write clear comments and documentation * Format output with println! macro *** ## Time Estimate [Section titled “Time Estimate”](#time-estimate) * **Reading:** 60-90 minutes * **Hands-on Practice:** 45-60 minutes * **Exercises:** 30-45 minutes * **Total:** 2.5-3 hours *** ## Quick Start Path [Section titled “Quick Start Path”](#quick-start-path) If you want to jump right in: 1. **[Variables](/02-basics/00-variables/)** (20 min) - Most important! 2. **[Types](/02-basics/01-types/)** (20 min) 3. **[Output](/02-basics/04-output/)** (15 min) 4. Skip to exercises Then come back for operators and comments. *** ## Detailed Learning Path [Section titled “Detailed Learning Path”](#detailed-learning-path) ### Recommended Order [Section titled “Recommended Order”](#recommended-order) ```plaintext 1 1. Variables and Mutability (the big difference!) 2 ↓ 3 2. Basic Types (integers, floats, etc.) 4 ↓ 5 3. Operators (arithmetic, comparison) 6 ↓ 7 4. Comments (documentation) 8 ↓ 9 5. Output and Formatting (println!) ``` All topics are interconnected, sequential reading recommended. *** ## What You’ll Learn [Section titled “What You’ll Learn”](#what-youll-learn) ### Variables: Immutable by Default [Section titled “Variables: Immutable by Default”](#variables-immutable-by-default) The **biggest** difference from TypeScript/JavaScript: **TypeScript:** ```typescript 1 let x = 5; // mutable 2 x = 6; // OK ``` **Rust:** ```rust 1 let x = 5; // immutable by default! 2 x = 6; // Compile error! 3 4 let mut y = 5; // explicitly mutable 5 y = 6; // OK ``` **Key insight:** Rust makes you think about mutability upfront. ### Rich Type System [Section titled “Rich Type System”](#rich-type-system) **TypeScript:** ```typescript 1 let x: number = 42; // One number type 2 let y = 3.14; // Also number ``` **Rust:** ```rust 1 let x: i32 = 42; // 32-bit signed integer 2 let y: f64 = 3.14; // 64-bit float 3 let z: u8 = 255; // 8-bit unsigned integer ``` **Why:** More control over memory and performance. ### Type Inference [Section titled “Type Inference”](#type-inference) **Both languages have it:** ```typescript 1 let x = 5; // TypeScript infers: number ``` ```rust 1 let x = 5; // Rust infers: i32 ``` But Rust’s inference goes further in complex scenarios! ### Operators [Section titled “Operators”](#operators) **Mostly the same:** ```typescript 1 // TypeScript 2 let sum = 5 + 3; 3 let is_equal = x === y; ``` ```rust 1 // Rust 2 let sum = 5 + 3; 3 let is_equal = x == y; // Note: no ===, just == ``` ### Comments [Section titled “Comments”](#comments) **Similar syntax:** ```typescript 1 // TypeScript 2 // Single line comment 3 /* Multi-line comment */ 4 5 /** JSDoc documentation */ ``` ```rust 1 // Rust 2 // Single line comment 3 /* Multi-line comment */ 4 5 /// Documentation comment (generates docs!) ``` ### Formatted Output [Section titled “Formatted Output”](#formatted-output) **Different, but does more:** ```typescript 1 // TypeScript 2 console.log(`Hello, ${name}!`); 3 console.log("x =", x, "y =", y); ``` ```rust 1 // Rust 2 println!("Hello, {}!", name); 3 println!("x = {} y = {}", x, y); ``` *** ## Key Differences from TypeScript [Section titled “Key Differences from TypeScript”](#key-differences-from-typescript) ### 1. Mutability [Section titled “1. Mutability”](#1-mutability) | Aspect | TypeScript | Rust | | -------------- | --------------- | ----------------- | | Default | Mutable (`let`) | Immutable (`let`) | | Make immutable | Use `const` | Default! | | Make mutable | Default! | Use `let mut` | | Reassignment | Always allowed | Only with `mut` | ### 2. Type System [Section titled “2. Type System”](#2-type-system) | Feature | TypeScript | Rust | | ---------------- | --------------- | ------------------------------ | | Number types | `number` | `i8`, `i32`, `f64`… | | Integer types | `number` | 12 different types! | | Type annotations | `: Type` | `: Type` (same!) | | Type inference | Good | Excellent | | Null safety | `strict` mode | No `null` at all (`Option`) | | Escape hatches | `any` (opt-out) | `unsafe` / `dyn Any` (rare) | ### 3. Variables [Section titled “3. Variables”](#3-variables) **TypeScript:** ```typescript 1 let x = 5; 2 let x = 10; // Error: cannot redeclare ``` **Rust:** ```rust 1 let x = 5; 2 let x = 10; // OK - this is "shadowing"! ``` **Shadowing** is a Rust feature that lets you reuse variable names. ### 4. Constants [Section titled “4. Constants”](#4-constants) **TypeScript:** ```typescript 1 const MAX_SIZE = 100; // Runtime constant ``` **Rust:** ```rust 1 const MAX_SIZE: u32 = 100; // Compile-time constant 2 // ^^^ Must have type annotation! ``` *** ## Important Concepts [Section titled “Important Concepts”](#important-concepts) ### Immutability by Default [Section titled “Immutability by Default”](#immutability-by-default) **Why does Rust do this?** 1. **Safety:** Prevents accidental modification 2. **Concurrency:** Immutable data is thread-safe 3. **Optimization:** Compiler can optimize better 4. **Intent:** Makes mutability explicit **Coming from JavaScript, this feels backwards at first!** ```javascript 1 // JavaScript - everything is mutable 2 let x = 5; 3 x = 6; // No problem ``` ```rust 1 // Rust - explicit mutability 2 let x = 5; 3 // x = 6; // Won't compile 4 5 let mut y = 5; 6 y = 6; // OK ``` **You’ll get used to it!** After a week, you’ll appreciate it. ### Type Annotations vs Inference [Section titled “Type Annotations vs Inference”](#type-annotations-vs-inference) **Rust is smart about types:** ```rust 1 let x = 5; // Inferred as i32 2 let y: u8 = 5; // Explicitly u8 3 let z = 5u8; // Suffix notation ``` **When you need annotations:** ```rust 1 // Compiler can't infer 2 let numbers: Vec = Vec::new(); 3 4 // Multiple possible types 5 let guess: u32 = "42".parse().expect("Not a number!"); ``` ### Expressions vs Statements [Section titled “Expressions vs Statements”](#expressions-vs-statements) **Everything in Rust is an expression (almost):** ```rust 1 let x = { 2 let y = 3; 3 y + 1 // No semicolon - this is the return value! 4 }; 5 // x is now 4 ``` **Compare to TypeScript:** ```typescript 1 // TypeScript - blocks don't return values 2 let x = (() => { 3 let y = 3; 4 return y + 1; 5 })(); ``` *** ## Your Goals for This Section [Section titled “Your Goals for This Section”](#your-goals-for-this-section) ### Minimum Goals [Section titled “Minimum Goals”](#minimum-goals) * [ ] Understand `let` vs `let mut` * [ ] Know the basic integer types (`i32`, `u32`, etc.) * [ ] Use `println!` with formatting * [ ] Write simple arithmetic expressions ### Stretch Goals [Section titled “Stretch Goals”](#stretch-goals) * [ ] Understand shadowing vs reassignment * [ ] Know when to use each integer type * [ ] Use all comparison operators * [ ] Write documentation comments * [ ] Use advanced formatting (debug, alignment) *** ## Common First-Time Issues [Section titled “Common First-Time Issues”](#common-first-time-issues) ### ”cannot assign twice to immutable variable” [Section titled “”cannot assign twice to immutable variable””](#cannot-assign-twice-to-immutable-variable) ```rust 1 let x = 5; 2 x = 6; // Error! ``` **Solution:** Add `mut`: ```rust 1 let mut x = 5; 2 x = 6; // OK ``` ### “type annotations needed” [Section titled ““type annotations needed””](#type-annotations-needed) ```rust 1 let x = Vec::new(); // What type? ``` **Solution:** Add type annotation: ```rust 1 let x: Vec = Vec::new(); // OK ``` ### Integer overflow [Section titled “Integer overflow”](#integer-overflow) ```rust 1 let x: u8 = 255; 2 let y = x + 1; // Compile error: this arithmetic operation will overflow ``` When **both operands are constants**, the compiler catches the overflow at compile time (`error: this arithmetic operation will overflow`). To see the *runtime* behavior, the overflow has to involve a value the compiler can’t fold away: ```rust 1 let mut x: u8 = 255; 2 x += 1; // Panics in debug mode; silently wraps to 0 in release mode ``` In debug builds this panics with `attempt to add with overflow`; in release builds the check is removed and the value wraps around to `0`. **Solution:** When you *want* wraparound, use the explicit wrapping/saturating arithmetic methods (`wrapping_add`, `saturating_add`, `checked_add`); we’ll cover these later. *** ## Section Overview [Section titled “Section Overview”](#section-overview) ### What Makes This Section Important [Section titled “What Makes This Section Important”](#what-makes-this-section-important) **These are the building blocks!** Everything in Rust builds on: * Variables and mutability * The type system * Basic operations **Master these, and the rest becomes easier.** ### How This Relates to Later Sections [Section titled “How This Relates to Later Sections”](#how-this-relates-to-later-sections) * **Section 03 (Functions):** Use these types as parameters * **Section 05 (Ownership):** Immutability is key to ownership * **Section 06 (Data Structures):** Compose basic types * **Section 08 (Error Handling):** Type system enables Result/Option ### Comparison to TypeScript Fundamentals [Section titled “Comparison to TypeScript Fundamentals”](#comparison-to-typescript-fundamentals) | Concept | TypeScript Section | Rust Section | Difficulty | | ----------- | ------------------ | ------------ | ---------- | | Variables | Day 1 | Day 1 | Medium | | Types | Day 1-2 | Day 1-2 | Medium | | Operators | Day 1 | Day 1 | Easy | | Mutability | Not emphasized | Critical | Hard | | Type safety | Optional | Mandatory | Medium | **The mutability concept is the hardest part for JS/TS developers.** *** ## Ready to Begin? [Section titled “Ready to Begin?”](#ready-to-begin) Let’s start with the most important concept: variables and mutability! ### [Start with Variables and Mutability](/02-basics/00-variables/) [Section titled “Start with Variables and Mutability”](#start-with-variables-and-mutability) Or jump to: * [Basic Types](/02-basics/01-types/) * [Operators](/02-basics/02-operators/) * [Comments](/02-basics/03-comments/) * [Output and Formatting](/02-basics/04-output/) *** ## Frequently asked questions [Section titled “Frequently asked questions”](#frequently-asked-questions) ### Are Rust variables mutable like JavaScript’s `let`? [Section titled “Are Rust variables mutable like JavaScript’s let?”](#are-rust-variables-mutable-like-javascripts-let) No. `let x = 5;` is immutable, and you opt into mutation with `let mut x = 5;`. This is the reverse of JavaScript, where `let` is reassignable and only `const` locks a binding. Rust’s `const` is a separate, always-immutable, compile-time constant. See [Variables and Mutability](/02-basics/00-variables/). ### Why does Rust have so many number types instead of one `number`? [Section titled “Why does Rust have so many number types instead of one number?”](#why-does-rust-have-so-many-number-types-instead-of-one-number) JavaScript’s single `f64` loses integer precision past 2^53 and hides cost. Rust’s sized integers (`i32`, `u64`, `usize`, …) and floats (`f32`, `f64`) make range, signedness, and memory explicit. Constant overflow is rejected, debug integer overflow panics by default, and optimized release builds wrap unless overflow checks are enabled; use `checked_*`, `saturating_*`, or `wrapping_*` when the policy matters. See [Basic Types](/02-basics/01-types/). ### What is shadowing? [Section titled “What is shadowing?”](#what-is-shadowing) Re-declaring a name with `let`: `let id = "5"; let id: i32 = id.parse()?;`. The new binding can change type and reuses the name, unlike reassignment, which cannot. It is handy for refining a value through a few steps without inventing new names. See [Variables and Mutability](/02-basics/00-variables/). # Variables and Mutability > Rust variables are immutable by default, the opposite of JavaScript. See how let, let mut, shadowing, and const differ from TypeScript's let and const bindings. Understanding variables in Rust matters. Unlike JavaScript/TypeScript where everything is mutable by default, Rust makes variables **immutable by default**. This is the single biggest mindset shift you’ll need to make. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) In Rust, variables are immutable unless explicitly marked as mutable with `mut`. This forces you to think about which data needs to change and which doesn’t, leading to safer and more predictable code. **In short:** `let` creates immutable bindings, `let mut` creates mutable bindings. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // TypeScript/JavaScript - everything mutable by default 2 let x = 5; 3 x = 6; // OK - can reassign 4 5 const y = 10; 6 // y = 11; // Error - const prevents reassignment 7 8 // But const doesn't prevent mutation! 9 const arr = [1, 2, 3]; 10 arr.push(4); // OK - array itself is mutable! 11 arr[0] = 99; // OK 12 // arr = []; // Error - can't reassign 13 14 const obj = { value: 42 }; 15 obj.value = 43; // OK - object is mutable! 16 // obj = {}; // Error - can't reassign ``` **Key points:** * `let` = mutable (can reassign) * `const` = can’t reassign, but contents can mutate * Mutability is the default *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 // Rust - immutable by default! 2 let x = 5; 3 // x = 6; // Compile error: cannot assign twice to immutable variable 4 5 let mut y = 10; 6 y = 11; // OK - explicitly marked as mutable 7 8 // Arrays/vectors 9 let arr = vec![1, 2, 3]; 10 // arr.push(4); // Error - arr is immutable 11 // arr[0] = 99; // Error 12 13 let mut arr2 = vec![1, 2, 3]; 14 arr2.push(4); // OK 15 arr2[0] = 99; // OK 16 17 // Constants (compile-time constants) 18 const MAX_POINTS: u32 = 100_000; 19 // MAX_POINTS = 200_000; // Error - constants are never mutable ``` **Key points:** * `let` = immutable (cannot reassign or mutate) * `let mut` = mutable (can reassign and mutate) * `const` = compile-time constant (must have type annotation) * Immutability is the default *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Immutable Variables [Section titled “Immutable Variables”](#immutable-variables) ```rust 1 fn main() { 2 let x = 5; 3 println!("The value of x is: {}", x); 4 5 // x = 6; // This would cause a compile error: 6 // error[E0384]: cannot assign twice to immutable variable `x` 7 } ``` **Why immutable by default?** 1. **Safety:** Prevents accidental modification 2. **Concurrency:** Immutable data is safe to share across threads 3. **Optimization:** Compiler can make better optimizations 4. **Intent:** Makes your intentions explicit **This is the opposite of JavaScript!** ### Mutable Variables [Section titled “Mutable Variables”](#mutable-variables) ```rust 1 fn main() { 2 let mut x = 5; 3 println!("The value of x is: {}", x); 4 5 x = 6; // OK - x is mutable 6 println!("The value of x is: {}", x); 7 } ``` **Output:** ```plaintext 1 The value of x is: 5 2 The value of x is: 6 ``` **When to use `mut`:** * Counters in loops * Accumulators * Any value that needs to change over time ### Shadowing [Section titled “Shadowing”](#shadowing) Rust has a unique feature called **shadowing** that lets you redeclare a variable: ```rust 1 fn main() { 2 let x = 5; 3 4 let x = x + 1; // OK - shadowing 5 6 { 7 let x = x * 2; // OK - shadows in this scope 8 println!("Inner scope: {}", x); // 12 9 } 10 11 println!("Outer scope: {}", x); // 6 12 } ``` **Output:** ```plaintext 1 Inner scope: 12 2 Outer scope: 6 ``` **Shadowing vs Mutation:** ```rust 1 // Shadowing - creates new variable 2 let x = 5; 3 let x = x + 1; // New variable, can change type 4 5 // Mutation - changes existing variable 6 let mut y = 5; 7 y = y + 1; // Same variable, same type ``` **Shadowing allows type changes:** ```rust 1 let spaces = " "; // &str 2 let spaces = spaces.len(); // OK - now usize 3 4 let mut spaces2 = " "; 5 // spaces2 = spaces2.len(); // Error - type mismatch ``` ### Constants [Section titled “Constants”](#constants) ```rust 1 // Constants must: 2 // 1. Have a type annotation 3 // 2. Be assigned a constant expression (evaluated at compile time) 4 // 3. Use SCREAMING_SNAKE_CASE naming 5 6 const MAX_POINTS: u32 = 100_000; 7 const PI: f64 = 3.14159; 8 const APP_NAME: &str = "MyApp"; 9 10 fn main() { 11 println!("Max points: {}", MAX_POINTS); 12 // MAX_POINTS = 200_000; // Error - constants are always immutable 13 } ``` > **Note:** “Constant expression” does not mean “no function calls.” A `const` can call any **`const fn`** and many standard-library methods that are marked `const`, as long as everything is evaluable at compile time. For example, `const NAME_LEN: usize = "MyApp".len();` and a call to your own `const fn double(n: u32) -> u32 { n * 2 }` both work. What you cannot do is call a *non-`const`* function (anything that needs to run at runtime, like reading the clock or allocating a `Vec`). **Constants vs Immutable Variables:** | Feature | `const` | `let` | | ----------------- | ---------------------- | ------------------- | | Mutability | Never | Unless `mut` | | Type annotation | Required | Optional (inferred) | | Computation | Compile-time only | Runtime OK | | Scope | Global or local | Local only | | Naming convention | SCREAMING\_SNAKE\_CASE | snake\_case | | Can be shadowed | No | Yes | **When to use `const`:** * Configuration values * Mathematical constants * String literals used multiple times * Values that truly never change *** ## Key Differences from TypeScript/JavaScript [Section titled “Key Differences from TypeScript/JavaScript”](#key-differences-from-typescriptjavascript) ### 1. Default Mutability [Section titled “1. Default Mutability”](#1-default-mutability) **JavaScript:** ```javascript 1 let x = 5; // Can change 2 const y = 5; // Can't reassign (but contents can mutate) ``` **Rust:** ```rust 1 let x = 5; // Can't change 2 let mut y = 5; // Can change 3 const Z: i32 = 5; // Can't change, compile-time constant ``` **Mental model shift:** Rust’s `let` is *stricter* than JavaScript’s `const`. JavaScript `const` only blocks **reassignment** of the binding; the value it points to can still be mutated (`arr.push(4)`). Rust’s immutable `let` blocks **both** reassignment *and* mutation of the value. (And don’t confuse this with Rust’s own `const`, which is a separate, compile-time-only construct covered above, not the everyday tool for local bindings.) ### 2. Mutation vs Reassignment [Section titled “2. Mutation vs Reassignment”](#2-mutation-vs-reassignment) **JavaScript `const`:** ```javascript 1 const arr = [1, 2, 3]; 2 arr.push(4); // OK - array is mutable 3 arr[0] = 99; // OK 4 arr = []; // Error - can't reassign ``` **Rust `let`:** ```rust 1 let arr = vec![1, 2, 3]; 2 // arr.push(4); // Error - vector is immutable 3 // arr[0] = 99; // Error 4 // arr = vec![]; // Error ``` **In Rust, immutable means truly immutable!** ### 3. Shadowing [Section titled “3. Shadowing”](#3-shadowing) **JavaScript:** ```javascript 1 let x = 5; 2 let x = 10; // SyntaxError: Identifier 'x' has already been declared ``` **Rust:** ```rust 1 let x = 5; 2 let x = 10; // OK - shadowing ``` ### 4. Type Changes [Section titled “4. Type Changes”](#4-type-changes) **TypeScript:** ```typescript 1 let spaces = " "; 2 spaces = spaces.length; // Type error: string to number ``` **Rust with shadowing:** ```rust 1 let spaces = " "; 2 let spaces = spaces.len(); // OK - shadowing allows type change ``` **Rust with mutation:** ```rust 1 let mut spaces = " "; 2 // spaces = spaces.len(); // Error - can't change type ``` *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Forgetting `mut` [Section titled “Pitfall 1: Forgetting mut”](#pitfall-1-forgetting-mut) **Problem:** ```rust 1 fn main() { 2 let counter = 0; 3 4 for i in 1..=5 { 5 counter += 1; // Error: cannot assign to immutable variable 6 } 7 } ``` **Error** (the compiler also warns that `counter` and `i` are unused, since the code never compiles far enough to use them): ```plaintext 1 warning: variable `counter` is assigned to, but never used 2 --> src/main.rs:2:9 3 | 4 2 | let counter = 0; 5 | ^^^^^^^ 6 | 7 = note: consider using `_counter` instead 8 = note: `#[warn(unused_variables)]` on by default 9 10 warning: unused variable: `i` 11 --> src/main.rs:4:9 12 | 13 4 | for i in 1..=5 { 14 | ^ help: if this is intentional, prefix it with an underscore: `_i` 15 16 error[E0384]: cannot assign twice to immutable variable `counter` 17 --> src/main.rs:5:9 18 | 19 2 | let counter = 0; 20 | ------- first assignment to `counter` 21 ... 22 5 | counter += 1; // Error: cannot assign to immutable variable 23 | ^^^^^^^^^^^^ cannot assign twice to immutable variable 24 | 25 help: consider making this binding mutable 26 | 27 2 | let mut counter = 0; 28 | +++ ``` **Solution:** ```rust 1 fn main() { 2 let mut counter = 0; // Add 'mut' 3 4 for i in 1..=5 { 5 counter += 1; // OK 6 } 7 8 println!("Counter: {}", counter); 9 } ``` ### Pitfall 2: Thinking `const` Works Like TypeScript [Section titled “Pitfall 2: Thinking const Works Like TypeScript”](#pitfall-2-thinking-const-works-like-typescript) **Problem:** ```rust 1 const MAX_POINTS = 100_000; // Error: missing type annotation ``` **Error:** ```plaintext 1 error: missing type for `const` item 2 --> src/main.rs:1:17 3 | 4 1 | const MAX_POINTS = 100_000; // Error: missing type annotation 5 | ^ help: provide a type for the constant: `: i32` ``` **Solution:** ```rust 1 const MAX_POINTS: u32 = 100_000; // OK - type annotation required ``` ### Pitfall 3: Trying to Mutate Immutable Data [Section titled “Pitfall 3: Trying to Mutate Immutable Data”](#pitfall-3-trying-to-mutate-immutable-data) **Problem:** ```rust 1 fn main() { 2 let v = vec![1, 2, 3]; 3 v.push(4); // Error: cannot borrow as mutable 4 } ``` **Error:** ```plaintext 1 error[E0596]: cannot borrow `v` as mutable, as it is not declared as mutable 2 --> src/main.rs:3:5 3 | 4 3 | v.push(4); // Error: cannot borrow as mutable 5 | ^ cannot borrow as mutable 6 | 7 help: consider changing this to be mutable 8 | 9 2 | let mut v = vec![1, 2, 3]; 10 | +++ ``` **Solution:** ```rust 1 fn main() { 2 let mut v = vec![1, 2, 3]; // Add 'mut' 3 v.push(4); // OK 4 } ``` ### Pitfall 4: Confusing Shadowing with Mutation [Section titled “Pitfall 4: Confusing Shadowing with Mutation”](#pitfall-4-confusing-shadowing-with-mutation) **Problem thinking:** ```rust 1 let x = 5; 2 let x = 6; // "This is reassignment, right?" ``` **Reality:** It’s shadowing - a new variable with the same name. ```rust 1 fn main() { 2 let x = 5; 3 println!("Address: {:p}", &x); 4 5 let x = 6; 6 println!("Address: {:p}", &x); // a different address 7 } ``` The two `&x` print *different* addresses, which proves these are two separate variables rather than one variable being reassigned. The actual addresses and the gap between them are not specified by the language — they depend on the platform, the optimization level, and stack layout, so don’t read anything into the exact hex values or assume a fixed offset. **These are different variables in memory!** *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Prefer Immutability [Section titled “1. Prefer Immutability”](#1-prefer-immutability) ** Don’t default to `mut`:** ```rust 1 fn main() { 2 let mut x = 5; // Unnecessary mut 3 let mut y = 10; // Unnecessary mut 4 println!("{} {}", x, y); 5 } ``` ** Only use `mut` when needed:** ```rust 1 fn main() { 2 let x = 5; // Immutable - won't change 3 let mut y = 10; // Mutable - will change 4 y += 5; 5 println!("{} {}", x, y); 6 } ``` **Why:** Immutability makes code easier to reason about and enables compiler optimizations. ### 2. Use Shadowing for Transformations [Section titled “2. Use Shadowing for Transformations”](#2-use-shadowing-for-transformations) ** Don’t create new variable names:** ```rust 1 let spaces_str = " "; 2 let spaces_count = spaces_str.len(); 3 let spaces_display = format!("Count: {}", spaces_count); ``` ** Use shadowing for the same concept:** ```rust 1 let spaces = " "; 2 let spaces = spaces.len(); 3 let spaces = format!("Count: {}", spaces); ``` **When shadowing makes sense:** * Type transformations * Progressive calculations * Parsing/validation steps ### 3. Use Constants for True Constants [Section titled “3. Use Constants for True Constants”](#3-use-constants-for-true-constants) ** Don’t use variables for constants:** ```rust 1 fn main() { 2 let max_points = 100_000; // Used everywhere 3 // ... lots of code ... 4 } ``` ** Use `const` for values that never change:** ```rust 1 const MAX_POINTS: u32 = 100_000; 2 3 fn main() { 4 // MAX_POINTS available everywhere 5 } ``` ### 4. Clear Variable Names [Section titled “4. Clear Variable Names”](#4-clear-variable-names) ** Don’t use generic names:** ```rust 1 let mut x = vec![]; 2 let mut y = vec![]; ``` ** Use descriptive names:** ```rust 1 let mut users = vec![]; 2 let mut products = vec![]; ``` **Even with mutability, clarity matters!** *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) ### Calculating Running Average [Section titled “Calculating Running Average”](#calculating-running-average) **TypeScript:** ```typescript 1 function calculateRunningAverage(numbers: number[]): number[] { 2 let sum = 0; 3 const averages = []; 4 5 for (let i = 0; i < numbers.length; i++) { 6 sum += numbers[i]; 7 averages.push(sum / (i + 1)); 8 } 9 10 return averages; 11 } 12 13 const nums = [10, 20, 30, 40, 50]; 14 console.log(calculateRunningAverage(nums)); 15 // [10, 15, 20, 25, 30] ``` **Rust:** ```rust 1 fn calculate_running_average(numbers: &[i32]) -> Vec { 2 let mut sum = 0; 3 let mut averages = Vec::new(); 4 5 for (i, &num) in numbers.iter().enumerate() { 6 sum += num; 7 averages.push(sum as f64 / (i + 1) as f64); 8 } 9 10 averages 11 } 12 13 fn main() { 14 let nums = vec![10, 20, 30, 40, 50]; 15 let result = calculate_running_average(&nums); 16 println!("{:?}", result); 17 // [10.0, 15.0, 20.0, 25.0, 30.0] 18 } ``` **Notice:** * `sum` and `averages` are `mut` (they change) * `numbers` is immutable (just reading) * Clear intent about what changes > **Note:** This example sneaks in a few things from later chapters — `&[i32]` (a borrowed slice, [Section 05](/05-ownership/)), `.iter().enumerate()` ([Section 07: Iterators](/07-collections/06-iterators/)), and `as f64` casts ([Basic Types](/02-basics/01-types/)). Don’t worry about those yet; focus on which bindings are `mut` and which are not. ### Configuration with Constants [Section titled “Configuration with Constants”](#configuration-with-constants) **TypeScript:** ```typescript 1 const CONFIG = { 2 MAX_CONNECTIONS: 100, 3 TIMEOUT_MS: 5000, 4 API_URL: "https://api.example.com", 5 }; 6 7 function connect() { 8 // Use CONFIG.MAX_CONNECTIONS 9 } ``` **Rust:** ```rust 1 const MAX_CONNECTIONS: u32 = 100; 2 const TIMEOUT_MS: u64 = 5000; 3 const API_URL: &str = "https://api.example.com"; 4 5 fn connect() { 6 // Use MAX_CONNECTIONS 7 } 8 9 fn main() { 10 println!("Max connections: {}", MAX_CONNECTIONS); 11 } ``` **Constants are available globally without runtime overhead!** *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book - Variables and Mutability](https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html) * [Rust Reference - Constants](https://doc.rust-lang.org/reference/items/constant-items.html) * [Rust by Example - Variable Bindings](https://doc.rust-lang.org/rust-by-example/variable_bindings.html) ### Related Topics [Section titled “Related Topics”](#related-topics) * [Ownership](/05-ownership/) - Immutability is key to ownership * [Borrowing](/05-ownership/02-borrowing/) - Mutable vs immutable references *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Fix the Mutability [Section titled “Exercise 1: Fix the Mutability”](#exercise-1-fix-the-mutability) Fix this code: ```rust 1 fn main() { 2 let x = 5; 3 println!("x is: {}", x); 4 5 x = 6; 6 println!("x is now: {}", x); 7 } ``` Solution ```rust 1 fn main() { 2 let mut x = 5; // Add 'mut' 3 println!("x is: {}", x); 4 5 x = 6; 6 println!("x is now: {}", x); 7 } ``` ### Exercise 2: Use Shadowing [Section titled “Exercise 2: Use Shadowing”](#exercise-2-use-shadowing) Rewrite using shadowing to convert a string to its length: ```rust 1 fn main() { 2 let text = "Hello, Rust!"; 3 let text_length = text.len(); 4 println!("Text: '{}' has length {}", text, text_length); 5 } ``` Solution ```rust 1 fn main() { 2 let text = "Hello, Rust!"; 3 println!("Text: '{}'", text); 4 5 let text = text.len(); // Shadow with new type 6 println!("Length: {}", text); 7 } ``` ### Exercise 3: Constants [Section titled “Exercise 3: Constants”](#exercise-3-constants) Create constants for a game: * Maximum health: 100 * Starting gold: 50 * Player name: “Hero” ```rust 1 // Add constants here 2 3 fn main() { 4 println!("Max health: {}", /* use constant */); 5 println!("Starting gold: {}", /* use constant */); 6 println!("Player: {}", /* use constant */); 7 } ``` Solution ```rust 1 const MAX_HEALTH: u32 = 100; 2 const STARTING_GOLD: u32 = 50; 3 const PLAYER_NAME: &str = "Hero"; 4 5 fn main() { 6 println!("Max health: {}", MAX_HEALTH); 7 println!("Starting gold: {}", STARTING_GOLD); 8 println!("Player: {}", PLAYER_NAME); 9 } ``` ### Exercise 4: Counter [Section titled “Exercise 4: Counter”](#exercise-4-counter) Write a function that counts from 1 to n: ```rust 1 fn count_to(n: i32) { 2 // Implement using a mutable counter 3 } 4 5 fn main() { 6 count_to(5); 7 // Should print the numbers 1 to 5, space-separated 8 } ``` Solution ```rust 1 fn count_to(n: i32) { 2 let mut counter = 1; 3 while counter <= n { 4 print!("{} ", counter); 5 counter += 1; 6 } 7 println!(); 8 } 9 10 fn main() { 11 count_to(5); 12 } ``` *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Rust variables are immutable by default * Use `let mut` for mutable variables * Shadowing lets you reuse names and change types * Constants require type annotations and are compile-time * Immutability leads to safer, more maintainable code **Key syntax:** ```rust 1 let x = 5; // Immutable 2 let mut y = 10; // Mutable 3 const Z: i32 = 100; // Constant 4 5 let x = x + 1; // Shadowing (new variable) 6 y = y + 1; // Mutation (same variable) ``` **Mental model:** * Default to immutable * Add `mut` only when needed * Use shadowing for transformations * Use `const` for true constants **This is the foundation!** Everything else in Rust builds on this concept. # Basic Types > Rust replaces TypeScript's one number type with sized ints, floats, bool, char, and tuples, plus explicit casts and overflow checks, not silent coercion. Rust has a rich type system with explicit types for different sizes and purposes. Unlike TypeScript’s single `number` type, Rust has many numeric types optimized for different use cases. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Rust’s type system is: * **Explicit:** Different types for integers, floats, etc. * **Safe:** No implicit conversions that lose data * **Efficient:** Choose the right size for your needs **Key types:** Integers (`i32`, `u8`…), floats (`f32`, `f64`), booleans (`bool`), characters (`char`), tuples *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // TypeScript - Simple type system 2 let integer: number = 42; 3 let float: number = 3.14; 4 let negative: number = -10; 5 let big: number = 9007199254740991; // Max safe integer 6 7 let flag: boolean = true; 8 let letter: string = "a"; // No char type 9 let tuple: [number, string] = [1, "hello"]; 10 11 // All numbers are the same type 12 let x: number = 42; 13 let y: number = 3.14; 14 console.log(typeof x); // "number" 15 console.log(typeof y); // "number" ``` **Key points:** * One `number` type for all numbers * No distinction between integers and floats * No control over size/memory usage * Strings for single characters *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 // Rust - Rich type system 2 let integer: i32 = 42; // 32-bit signed integer 3 let float: f64 = 3.14; // 64-bit float 4 let negative: i32 = -10; // Signed integer 5 let small: u8 = 255; // 8-bit unsigned (0-255) 6 let big: i64 = 9_223_372_036_854_775_807; // 64-bit signed 7 8 let flag: bool = true; // Boolean 9 let letter: char = 'a'; // Unicode scalar value (4 bytes!) 10 let tuple: (i32, &str) = (1, "hello"); // Tuple 11 12 // Different types for different purposes 13 let x: i32 = 42; // Integer type 14 let y: f64 = 3.14; // Float type 15 // let z = x + y; // Error: can't add i32 and f64! 16 let z = x as f64 + y; // Explicit conversion ``` **Key points:** * Multiple integer types (by size and signedness) * Separate float types (f32, f64) * Explicit conversions required * True character type (Unicode) *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Integer Types [Section titled “Integer Types”](#integer-types) Rust has **12** integer types based on: 1. **Signedness:** Signed (`i`) or unsigned (`u`) 2. **Size:** 8, 16, 32, 64, 128 bits, or architecture-dependent **Signed integers (can be negative):** | Type | Size | Range | | ------- | -------- | ------------------------------- | | `i8` | 8 bits | -128 to 127 | | `i16` | 16 bits | -32,768 to 32,767 | | `i32` | 32 bits | -2,147,483,648 to 2,147,483,647 | | `i64` | 64 bits | -9,223,372,036,854,775,808 to … | | `i128` | 128 bits | Very large range | | `isize` | arch | Depends on CPU (32 or 64 bits) | **Unsigned integers (only positive):** | Type | Size | Range | | ------- | -------- | ------------------------ | | `u8` | 8 bits | 0 to 255 | | `u16` | 16 bits | 0 to 65,535 | | `u32` | 32 bits | 0 to 4,294,967,295 | | `u64` | 64 bits | 0 to 18,446,744,073,709… | | `u128` | 128 bits | Very large range | | `usize` | arch | Depends on CPU | **Default:** When you write `let x = 42;`, Rust infers `i32`. **Examples:** ```rust 1 // Signed integers 2 let a: i8 = -128; // Smallest i8 3 let b: i8 = 127; // Largest i8 4 let c: i32 = -2_000_000; // Default integer type 5 6 // Unsigned integers 7 let d: u8 = 255; // Largest u8 8 let e: u16 = 65_535; // Largest u16 9 let f: u32 = 4_000_000; // Common for large counts 10 11 // Architecture-dependent 12 let idx: usize = 10; // Used for array indices, sizes 13 let offset: isize = -5; // Used for pointer arithmetic ``` **Number literals:** ```rust 1 let decimal = 98_222; // Underscore for readability 2 let hex = 0xff; // Hexadecimal 3 let octal = 0o77; // Octal 4 let binary = 0b1111_0000; // Binary 5 let byte = b'A'; // u8 only (ASCII) 6 7 // Type suffix 8 let x = 42i32; // Explicitly i32 9 let y = 100_u8; // Explicitly u8 10 let z = 3.14f32; // Explicitly f32 ``` ### Floating-Point Types [Section titled “Floating-Point Types”](#floating-point-types) Rust has two floating-point types: | Type | Size | Precision | Default | | ----- | ------- | ------------- | ------- | | `f32` | 32 bits | \~7 decimals | No | | `f64` | 64 bits | \~15 decimals | Yes | **Examples:** ```rust 1 let x = 2.0; // f64 (default) 2 let y: f32 = 3.0; // f32 (explicit) 3 4 let pi: f64 = 3.14159265359; 5 let e: f32 = 2.71828; 6 7 // Scientific notation 8 let large = 1e10; // f64; prints as 10000000000 (whole-valued floats omit the .0) 9 let small = 1e-5; // f64; prints as 0.00001 ``` **When to use f32 vs f64:** * **f32:** Graphics, game engines (GPU prefers f32) * **f64:** Scientific computing, default (more precise) **Compare to TypeScript:** ```typescript 1 // TypeScript - one type 2 let x: number = 3.14; // Always IEEE-754 f64 (Rust's f64) 3 let y: number = 2.0; // Same type; JS has no f32 ``` ### Boolean Type [Section titled “Boolean Type”](#boolean-type) Simple true/false: ```rust 1 let t = true; // Inferred as bool 2 let f: bool = false; // Explicit type 3 4 // Common use in conditionals 5 let is_active = true; 6 if is_active { 7 println!("Active!"); 8 } 9 10 // Size: 1 byte (8 bits) ``` **Same as TypeScript:** ```typescript 1 let t: boolean = true; 2 let f: boolean = false; ``` ### Character Type [Section titled “Character Type”](#character-type) Rust’s `char` is a full **Unicode Scalar Value**, covering far more than ASCII! ```rust 1 let c = 'z'; // Inferred as char 2 let z: char = 'ℤ'; // Unicode 3 let heart = '\u{2764}'; // Emoji (a Unicode scalar) 4 let chinese = '中'; // Chinese character 5 6 // Size: 4 bytes (32 bits) 7 // Range: U+0000 to U+D7FF and U+E000 to U+10FFFF ``` **Compare to TypeScript:** ```typescript 1 // TypeScript - no char type, use string 2 let c: string = "z"; 3 let emoji: string = "\u{2764}"; ``` **Important:** In Rust, `'a'` is a char, `"a"` is a string! ```rust 1 let char_a = 'a'; // char type 2 let string_a = "a"; // &str type (string slice) ``` ### Tuple Type [Section titled “Tuple Type”](#tuple-type) Group multiple values of different types: ```rust 1 let tup: (i32, f64, u8) = (500, 6.4, 1); 2 3 // Destructuring 4 let (x, y, z) = tup; 5 println!("x: {}, y: {}, z: {}", x, y, z); 6 7 // Access by index 8 let five_hundred = tup.0; 9 let six_point_four = tup.1; 10 let one = tup.2; 11 12 // Empty tuple (unit type) 13 let unit: () = (); ``` **Compare to TypeScript:** ```typescript 1 // TypeScript tuples 2 let tup: [number, number, number] = [500, 6.4, 1]; 3 4 // Destructuring 5 let [x, y, z] = tup; 6 7 // Access by index 8 let five_hundred = tup[0]; ``` **Tuple use cases:** * Return multiple values from functions * Group related values temporarily * Pattern matching (we’ll see later) **Unit type `()`:** ```rust 1 fn do_something() { 2 // No return value means returns () 3 } 4 5 fn explicit_unit() -> () { 6 println!("Returns unit"); 7 } ``` This looks like TypeScript’s `void`, but the two differ. TypeScript’s `void` is a type used to say “ignore whatever this returns”; there is no `void` *value* you can hold. Rust’s `()` (the **unit type**) is a real type with exactly one value, also written `()`. A function with no `-> ...` returns `()`, and you can bind it: `let nothing: () = do_something();` is valid Rust. *** ## Key Differences from TypeScript [Section titled “Key Differences from TypeScript”](#key-differences-from-typescript) ### 1. Multiple Number Types [Section titled “1. Multiple Number Types”](#1-multiple-number-types) **TypeScript:** ```typescript 1 let x: number = 42; 2 let y: number = 3.14; 3 let z: number = x + y; // OK ``` **Rust:** ```rust 1 let x: i32 = 42; 2 let y: f64 = 3.14; 3 // let z = x + y; // Error: mismatched types 4 let z = x as f64 + y; // Explicit conversion ``` ### 2. Overflow Behavior [Section titled “2. Overflow Behavior”](#2-overflow-behavior) **TypeScript:** ```typescript 1 let x: number = 255; 2 x = x + 1; // 256 (no problem) ``` **Rust (debug mode):** ```rust 1 let mut x: u8 = 255; 2 let one = std::env::args().count() as u8; // 1 at runtime, not a constant 3 x = x + one; // Panics at runtime: "attempt to add with overflow" ``` **Rust (release mode):** ```rust 1 let mut x: u8 = 255; 2 let one = std::env::args().count() as u8; // 1 at runtime, not a constant 3 x = x + one; // Wraps to 0 (two's complement) ``` > **Note:** Both operands above are computed at runtime on purpose. If you instead write `let x: u8 = 255; x + 1` with two literals, the compiler folds the constants and rejects it outright with `error: this arithmetic operation will overflow` — it never even gets to run. > **Warning:** This release-mode wrap is a *documented logic error*, not a feature to lean on. Relying on the silent wrap is discouraged; if you genuinely want wrapping, say so explicitly with `wrapping_add` so the behavior is the same in debug and release: > > ```rust > 1 > let x: u8 = 255; > 2 > let y = x.wrapping_add(1); // 0, intentional and identical in debug + release > ``` **Explicit overflow handling:** ```rust 1 let x: u8 = 255; 2 3 // Wrapping (always wraps) 4 let y = x.wrapping_add(1); // 0 5 6 // Saturating (clamps to max) 7 let y = x.saturating_add(1); // 255 8 9 // Checked (returns Option) 10 let y = x.checked_add(1); // None 11 12 // Overflowing (returns tuple) 13 let (y, overflowed) = x.overflowing_add(1); // (0, true) ``` ### 3. No Implicit Conversions [Section titled “3. No Implicit Conversions”](#3-no-implicit-conversions) **TypeScript:** ```typescript 1 let x: number = 42; 2 let y: string = String(x); // Explicit, but common 3 let z = x + ""; // Implicit conversion ``` **Rust:** ```rust 1 let x: i32 = 42; 2 // let y: String = x; // Error: can't convert 3 let y: String = x.to_string(); // Explicit 4 5 // Type casting with 'as' 6 let a: i32 = 42; 7 let b: f64 = a as f64; 8 let c: u8 = 255; 9 let d: i32 = c as i32; ``` ### 4. Character vs String [Section titled “4. Character vs String”](#4-character-vs-string) **TypeScript:** ```typescript 1 let c = "a"; // string 2 let s = "hello"; // string (same type) ``` **Rust:** ```rust 1 let c = 'a'; // char (4 bytes) 2 let s = "hello"; // &str (string slice) 3 // Different types! ``` *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Integer Overflow [Section titled “Pitfall 1: Integer Overflow”](#pitfall-1-integer-overflow) **Problem:** ```rust 1 fn add_one(x: u8) -> u8 { 2 x + 1 // Panics in debug, wraps in release when x is 255 3 } 4 5 fn main() { 6 let y = add_one(255); 7 println!("{y}"); 8 } ``` > **Note:** Here `x` arrives as a function argument, so the overflow is only discovered at runtime: debug builds panic with `attempt to add with overflow`, release builds silently wrap to `0`. Writing the same overflow with two literals (`let x: u8 = 255; x + 1`) is caught at compile time instead. The compiler emits `error: this arithmetic operation will overflow`. **Solution:** ```rust 1 fn main() { 2 let x: u8 = 255; 3 4 // Choose appropriate method 5 let y = x.saturating_add(1); // 255 (clamps) 6 let y = x.wrapping_add(1); // 0 (wraps) 7 8 // Or use larger type 9 let x: u16 = 255; 10 let y = x + 1; // 256 11 } ``` ### Pitfall 2: Mixing Integer Types [Section titled “Pitfall 2: Mixing Integer Types”](#pitfall-2-mixing-integer-types) **Problem:** ```rust 1 let x: i32 = 10; 2 let y: i64 = 20; 3 // let z = x + y; // Error: mismatched types ``` **Solution:** ```rust 1 let x: i32 = 10; 2 let y: i64 = 20; 3 let z = (x as i64) + y; // Convert x to i64 ``` ### Pitfall 3: Division Truncation [Section titled “Pitfall 3: Division Truncation”](#pitfall-3-division-truncation) **Problem:** ```rust 1 let x = 5 / 2; // Result is 2, not 2.5! ``` **Why:** Integer division truncates. **Solution:** ```rust 1 let x = 5.0 / 2.0; // 2.5 (float division) 2 let y = 5 as f64 / 2.0; // 2.5 (convert to float) ``` ### Pitfall 4: Char vs String Confusion [Section titled “Pitfall 4: Char vs String Confusion”](#pitfall-4-char-vs-string-confusion) **Problem:** ```rust 1 let c: char = "a"; // Error: expected char, found &str ``` **Solution:** ```rust 1 let c: char = 'a'; // Single quotes for char 2 let s: &str = "a"; // Double quotes for string ``` *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Default to i32, Then Specialize [Section titled “1. Default to i32, Then Specialize”](#1-default-to-i32-then-specialize) The Rust Book’s advice: **start with `i32`** as your default integer. It is fast on modern CPUs and rarely the wrong choice. Reach for a different type only when you have a concrete reason — a domain bound, a memory budget, or an API that demands it. ** Default unless you have a reason:** ```rust 1 let count = 10; // i32 by inference — the sensible default 2 let id: u32 = 123456; // u32 because the ID is never negative 3 let size: usize = 1000; // usize because it indexes a collection ``` **Specialize when the domain or API calls for it:** ```rust 1 let pixel: u8 = 255; // a color channel really is 0-255 2 let port: u16 = 8080; // ports are 0-65535 3 let idx: usize = 10; // indexing/length is always usize ``` > **Tip:** Choosing the *smallest* type that fits is not automatically better. Picking `u8` for an age, for example, buys almost nothing and just invites overflow bugs as soon as you add to it. Optimize the size only where it matters (large arrays, packed structs, fixed protocol fields). **Rough guidance on when to deviate from `i32`:** * `u8`: bytes, color channels, raw ASCII * `u16`: ports, values bounded above (e.g. HTTP status 100-599) * `i64`/`u64`: timestamps, file sizes, values that outgrow 32 bits * `usize`/`isize`: collection indices, lengths, sizes — required by the standard library ### 2. Use Type Inference When Clear [Section titled “2. Use Type Inference When Clear”](#2-use-type-inference-when-clear) ** Don’t over-annotate:** ```rust 1 let x: i32 = 42; 2 let y: i32 = 10; 3 let z: i32 = x + y; ``` ** Let Rust infer:** ```rust 1 let x = 42; // Inferred as i32 2 let y = 10; // Inferred as i32 3 let z = x + y; // Inferred as i32 ``` **Annotate when needed:** ```rust 1 let x: u8 = 42; // Need u8, not i32 2 let y = Vec::new(); // Need to specify: Vec ``` ### 3. Use Suffixes for Clarity [Section titled “3. Use Suffixes for Clarity”](#3-use-suffixes-for-clarity) ** Ambiguous:** ```rust 1 let x = 42; // Is this i32, u32, i64? ``` ** Clear intent:** ```rust 1 let x = 42u8; // Explicitly u8 2 let y = 1_000_000u32; // Explicitly u32 3 let z = 3.14f32; // Explicitly f32 ``` ### 4. Handle Overflow Explicitly [Section titled “4. Handle Overflow Explicitly”](#4-handle-overflow-explicitly) ** Hope for the best:** ```rust 1 let x: u8 = user_input + 10; // What if it overflows? ``` ** Handle explicitly:** ```rust 1 let x: u8 = user_input.saturating_add(10); // Clamps to 255 2 // or 3 let x = user_input.checked_add(10) 4 .expect("Overflow!"); ``` *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) ### Type-Safe Configuration [Section titled “Type-Safe Configuration”](#type-safe-configuration) **TypeScript:** ```typescript 1 interface Config { 2 port: number; // Could be negative! 3 maxConnections: number; 4 timeout: number; // Milliseconds 5 name: string; 6 } 7 8 const config: Config = { 9 port: 8080, 10 maxConnections: 100, 11 timeout: 5000, 12 name: "MyServer", 13 }; ``` **Rust:** ```rust 1 struct Config { 2 port: u16, // 0-65535 (valid port range) 3 max_connections: u32, // Only positive 4 timeout_ms: u64, // Milliseconds, large range 5 name: String, 6 } 7 8 let config = Config { 9 port: 8080, 10 max_connections: 100, 11 timeout_ms: 5000, 12 name: String::from("MyServer"), 13 }; 14 15 // Can't set invalid port 16 // let bad = Config { port: -1, ... }; // Won't compile! 17 // let bad = Config { port: 70000, ... }; // Won't compile! ``` **The type system enforces validity!** ### Pixel Color Representation [Section titled “Pixel Color Representation”](#pixel-color-representation) ```rust 1 // RGB color (0-255 per channel) 2 struct Color { 3 r: u8, // Red: 0-255 4 g: u8, // Green: 0-255 5 b: u8, // Blue: 0-255 6 } 7 8 let red = Color { r: 255, g: 0, b: 0 }; 9 let white = Color { r: 255, g: 255, b: 255 }; 10 11 // Can't set invalid values 12 // let bad = Color { r: 256, g: 0, b: 0 }; // Won't compile! ``` **u8 is perfect for this!** Saves memory and enforces range. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book - Data Types](https://doc.rust-lang.org/book/ch03-02-data-types.html) * [Rust Reference - Types](https://doc.rust-lang.org/reference/types.html) * [Rust by Example - Primitives](https://doc.rust-lang.org/rust-by-example/primitives.html) ### Related Topics [Section titled “Related Topics”](#related-topics) * [Type Conversions](https://doc.rust-lang.org/rust-by-example/types/cast.html) * [Overflow Handling](https://doc.rust-lang.org/std/primitive.u8.html#method.wrapping_add) *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Choose the Right Type [Section titled “Exercise 1: Choose the Right Type”](#exercise-1-choose-the-right-type) For each value, choose the most appropriate type: ```rust 1 // 1. Person's age 2 let age: /* what type? */ = 25; 3 4 // 2. HTTP status code (100-599) 5 let status: /* what type? */ = 200; 6 7 // 3. Temperature in Celsius (-273.15 to ∞) 8 let temp: /* what type? */ = -5.5; 9 10 // 4. Array index 11 let idx: /* what type? */ = 10; 12 13 // 5. Unicode emoji 14 let emoji: /* what type? */ = '\u{1F600}'; ``` Solutions ```rust 1 let age: u8 = 25; // 0-255 is enough 2 let status: u16 = 200; // 100-599 fits in u16 3 let temp: f32 = -5.5; // Float for decimals 4 let idx: usize = 10; // usize for indices 5 let emoji: char = '\u{1F600}'; // char for Unicode ``` ### Exercise 2: Fix Type Errors [Section titled “Exercise 2: Fix Type Errors”](#exercise-2-fix-type-errors) Fix this code: ```rust 1 fn main() { 2 let x: i32 = 10; 3 let y: f64 = 3.14; 4 let z = x + y; 5 println!("Result: {}", z); 6 } ``` Solution ```rust 1 fn main() { 2 let x: i32 = 10; 3 let y: f64 = 3.14; 4 let z = x as f64 + y; // Convert x to f64 5 println!("Result: {}", z); 6 } ``` ### Exercise 3: Tuple Destructuring [Section titled “Exercise 3: Tuple Destructuring”](#exercise-3-tuple-destructuring) Create a tuple with (name, age, score) and destructure it: ```rust 1 fn main() { 2 // Create tuple 3 let student = /* create tuple */; 4 5 // Destructure 6 let (/* fill in */) = student; 7 8 println!("Name: {}, Age: {}, Score: {}", name, age, score); 9 } ``` Solution ```rust 1 fn main() { 2 let student = ("Alice", 20, 95.5); 3 let (name, age, score) = student; 4 println!("Name: {}, Age: {}, Score: {}", name, age, score); 5 } ``` ### Exercise 4: Safe Arithmetic [Section titled “Exercise 4: Safe Arithmetic”](#exercise-4-safe-arithmetic) Implement safe addition that doesn’t panic: ```rust 1 fn safe_add(a: u8, b: u8) -> u8 { 2 // Use saturating or checked addition 3 } 4 5 fn main() { 6 println!("{}", safe_add(200, 100)); // Should be 255 7 println!("{}", safe_add(10, 20)); // Should be 30 8 } ``` Solution ```rust 1 fn safe_add(a: u8, b: u8) -> u8 { 2 a.saturating_add(b) // Clamps to 255 3 } 4 5 fn main() { 6 println!("{}", safe_add(200, 100)); // 255 7 println!("{}", safe_add(10, 20)); // 30 8 } ``` *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Rust has many integer types (i8, i32, u64, etc.) * Two float types (f32, f64) * Boolean and character types * Tuples for grouping values * No implicit conversions (use `as`) * Explicit overflow handling **Key types:** ```rust 1 // Integers 2 i8, i16, i32, i64, i128, isize // Signed 3 u8, u16, u32, u64, u128, usize // Unsigned 4 5 // Floats 6 f32, f64 7 8 // Others 9 bool // true/false 10 char // Unicode scalar value 11 () // Unit type 12 (T, U) // Tuple ``` **Type conversion:** ```rust 1 let x: i32 = 42; 2 let y: f64 = x as f64; // Explicit cast 3 let z: String = x.to_string(); // Method call ``` **Choose types wisely for memory efficiency and correctness!** # Operators > Rust's operators are similar to TypeScript/JavaScript, with a few important differences. Here we cover arithmetic, comparison, logical, and bitwise operators. Rust’s operators are similar to TypeScript/JavaScript, with a few important differences. Here we cover arithmetic, comparison, logical, and bitwise operators. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Most operators work the same way, but Rust: * Has no `===` (just `==`) * Requires same types for operations * Has explicit bitwise operators * No automatic type coercion **You’ll feel right at home with 90% of operators!** *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // Arithmetic 2 let sum = 5 + 3; // 8 3 let diff = 10 - 4; // 6 4 let product = 3 * 4; // 12 5 let quotient = 10 / 3; // 3.333... 6 let remainder = 10 % 3; // 1 7 8 // Comparison 9 let equal = 5 === 5; // true 10 let not_equal = 5 !== 6; // true 11 let greater = 10 > 5; // true 12 13 // Logical 14 let and = true && false; // false 15 let or = true || false; // true 16 let not = !true; // false 17 18 // Type coercion! 19 let result = "5" + 3; // "53" (string) 20 let result2 = "5" - 3; // 2 (number!) ``` **Key characteristic:** Lots of implicit conversions *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 // Arithmetic 2 let sum = 5 + 3; // 8 3 let diff = 10 - 4; // 6 4 let product = 3 * 4; // 12 5 let quotient = 10 / 3; // 3 (integer division!) 6 let remainder = 10 % 3; // 1 7 let float_div = 10.0 / 3.0; // 3.333... 8 9 // Comparison 10 let equal = 5 == 5; // true (no ===!) 11 let not_equal = 5 != 6; // true 12 let greater = 10 > 5; // true 13 14 // Logical 15 let and = true && false; // false 16 let or = true || false; // true 17 let not = !true; // false 18 19 // No type coercion! 20 // let result = "5" + 3; // Error: can't add &str and i32 21 let result = "5".to_string() + &3.to_string(); // Explicit ``` **Key characteristic:** No implicit conversions, explicit types *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Arithmetic Operators [Section titled “Arithmetic Operators”](#arithmetic-operators) ```rust 1 let a = 10; 2 let b = 3; 3 4 // Basic arithmetic 5 let sum = a + b; // 13 6 let difference = a - b; // 7 7 let product = a * b; // 30 8 let quotient = a / b; // 3 (integer division!) 9 let remainder = a % b; // 1 10 11 // Unary minus 12 let negative = -a; // -10 13 14 // Compound assignment 15 let mut x = 5; 16 x += 3; // x = x + 3 → 8 17 x -= 2; // x = x - 2 → 6 18 x *= 4; // x = x * 4 → 24 19 x /= 3; // x = x / 3 → 8 20 x %= 5; // x = x % 5 → 3 ``` **Integer vs Float Division:** ```rust 1 // Integer division (truncates) 2 let a = 10 / 3; // 3 3 4 // Float division 5 let b = 10.0 / 3.0; // 3.333... 6 let c = 10 as f64 / 3 as f64; // 3.333... ``` **Compare to TypeScript:** ```typescript 1 let a = 10 / 3; // 3.333... (always float) 2 let b = Math.floor(10 / 3); // 3 (explicit truncation) ``` ### Comparison Operators [Section titled “Comparison Operators”](#comparison-operators) ```rust 1 let a = 5; 2 let b = 10; 3 4 // Equality 5 let eq = a == b; // false 6 let ne = a != b; // true 7 8 // Ordering 9 let gt = a > b; // false 10 let lt = a < b; // true 11 let ge = a >= b; // false 12 let le = a <= b; // true ``` **No `===` in Rust!** ```rust 1 // Rust only has == 2 let x = 5; 3 let y = 5; 4 let equal = x == y; // true 5 6 // TypeScript has == and === 7 // == does type coercion 8 // === doesn't ``` **In Rust, `==` is always strict** (no type coercion possible). ### Logical Operators [Section titled “Logical Operators”](#logical-operators) ```rust 1 let t = true; 2 let f = false; 3 4 // AND 5 let and1 = t && f; // false 6 let and2 = t && t; // true 7 8 // OR 9 let or1 = t || f; // true 10 let or2 = f || f; // false 11 12 // NOT 13 let not1 = !t; // false 14 let not2 = !f; // true ``` **Short-circuit evaluation (like TypeScript):** ```rust 1 fn expensive_check() -> bool { 2 println!("Called!"); 3 true 4 } 5 6 let x = false && expensive_check(); // "Called!" not printed 7 let y = true || expensive_check(); // "Called!" not printed ``` ### Bitwise Operators [Section titled “Bitwise Operators”](#bitwise-operators) ```rust 1 let a = 0b1100; // 12 in binary 2 let b = 0b1010; // 10 in binary 3 4 // Bitwise AND 5 let and = a & b; // 0b1000 (8) 6 7 // Bitwise OR 8 let or = a | b; // 0b1110 (14) 9 10 // Bitwise XOR 11 let xor = a ^ b; // 0b0110 (6) 12 13 // Bitwise NOT (! on integers, not just bools) 14 let not = !a; // -13: inverts all 32 bits of the i32 15 16 // Left shift 17 let left = a << 2; // 0b110000 (48) 18 19 // Right shift 20 let right = a >> 2; // 0b0011 (3) 21 22 // Compound assignment 23 let mut x = 5; 24 x &= 3; // x = x & 3 25 x |= 2; // x = x | 2 26 x ^= 1; // x = x ^ 1 27 x <<= 1; // x = x << 1 28 x >>= 1; // x = x >> 1 ``` **Compare to TypeScript:** ```typescript 1 let a = 12; 2 let b = 10; 3 4 let and = a & b; // 8 5 let or = a | b; // 14 6 let xor = a ^ b; // 6 7 let not = ~a; // -13 (two's complement) 8 let left = a << 2; // 48 9 let right = a >> 2; // 3 ``` **Same syntax, same behavior!** > **Note:** In Rust the same `!` does both **logical NOT** (on `bool`) and **bitwise NOT** (on integers): there is no separate `~` operator like JavaScript’s. For a signed integer, `!a` inverts all of its bits, which is equal to `-1 - a` (two’s complement). So with `a` inferred as `i32` and equal to `12`, `!a` is `-13`, matching JavaScript’s `~12`. On an unsigned type the result stays non-negative: `!0u8` is `255`. *** ## Key Differences from TypeScript [Section titled “Key Differences from TypeScript”](#key-differences-from-typescript) ### 1. No Type Coercion [Section titled “1. No Type Coercion”](#1-no-type-coercion) **TypeScript:** ```typescript 1 let x = "5" + 3; // "53" (string) 2 let y = "5" - 3; // 2 (number!) 3 let z = "5" == 5; // true with ==, false with === ``` **Rust:** ```rust 1 // let x = "5" + 3; // Error: can't add &str and i32 2 let x = format!("{}{}", "5", 3); // "53" 3 4 // let y = "5" - 3; // Error: can't subtract 5 let y = "5".parse::().unwrap() - 3; // 2 6 7 // let z = "5" == 5; // Error: mismatched types ``` > **Warning:** `.unwrap()` extracts the value from a `Result`/`Option`, but **panics** (crashes) if it is an error or `None`. It is fine for examples and throwaway code where you know the input is valid, but in real code you should handle the error case explicitly with `match`, the `?` operator, or `.expect("message")` (covered in the error handling section). ### 2. Integer Division [Section titled “2. Integer Division”](#2-integer-division) **TypeScript:** ```typescript 1 let x = 10 / 3; // 3.333... (always float) ``` **Rust:** ```rust 1 let x = 10 / 3; // 3 (integer division!) 2 let y = 10.0 / 3.0; // 3.333... (float division) ``` ### 3. No `===` Operator [Section titled “3. No === Operator”](#3-no--operator) **TypeScript:** ```typescript 1 5 == "5"; // true (type coercion) 2 5 === "5"; // false (strict equality) ``` **Rust:** ```rust 1 5 == 5; // true 2 // 5 == "5"; // Error: mismatched types ``` **Rust only has `==`, but it’s always strict!** ### 4. Overflow Behavior [Section titled “4. Overflow Behavior”](#4-overflow-behavior) **TypeScript:** ```typescript 1 let x: number = Number.MAX_SAFE_INTEGER; 2 x = x + 1; // Still works (but loses precision) ``` **Rust (debug mode):** ```rust 1 let mut x: i32 = i32::MAX; 2 // x = x + 1; // Panics! (overflow) 3 x = x.wrapping_add(1); // Wraps around ``` *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Integer Division Surprise [Section titled “Pitfall 1: Integer Division Surprise”](#pitfall-1-integer-division-surprise) **Problem:** ```rust 1 let average = (5 + 10) / 2; // Expected 7.5, got 7! ``` **Why:** Integer division truncates. **Solution:** ```rust 1 let average = (5.0 + 10.0) / 2.0; // 7.5 2 let average = (5 + 10) as f64 / 2.0; // 7.5 ``` ### Pitfall 2: Comparing Different Types [Section titled “Pitfall 2: Comparing Different Types”](#pitfall-2-comparing-different-types) **Problem:** ```rust 1 let x: i32 = 5; 2 let y: i64 = 5; 3 // if x == y { // Error: mismatched types ``` **Solution:** ```rust 1 let x: i32 = 5; 2 let y: i64 = 5; 3 if x as i64 == y { // Convert x to i64 4 println!("Equal!"); 5 } ``` ### Pitfall 3: Expecting `===` [Section titled “Pitfall 3: Expecting ===”](#pitfall-3-expecting-) **Problem:** ```rust 1 // if x === y { // Error: no operator === ``` **Solution:** ```rust 1 if x == y { // Use == 2 println!("Equal!"); 3 } ``` ### Pitfall 4: Overflow Without Handling [Section titled “Pitfall 4: Overflow Without Handling”](#pitfall-4-overflow-without-handling) **Problem:** ```rust 1 fn add_100(x: u8) -> u8 { 2 x + 100 // Panics in debug mode when x is 200! 3 } 4 5 fn main() { 6 println!("{}", add_100(200)); 7 } ``` Running this in debug mode panics with `thread 'main' panicked at ...: attempt to add with overflow`. > **Note:** If you instead write the overflow with two literals the compiler can see (for example `let y = 200u8 + 100;`), it does not even compile: you get `error: this arithmetic operation will overflow` at compile time. The runtime panic only happens when at least one operand isn’t a compile-time constant (here `x` arrives as a function argument). **Solution:** ```rust 1 let x: u8 = 200; 2 let y = x.saturating_add(100); // 255 (clamps) 3 let z = x.wrapping_add(100); // 44 (wraps) 4 let w = x.checked_add(100); // None (returns Option) ``` *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Use Explicit Types for Division [Section titled “1. Use Explicit Types for Division”](#1-use-explicit-types-for-division) ** Ambiguous:** ```rust 1 let result = total / count; // Integer or float division? ``` ** Explicit:** ```rust 1 let result = total as f64 / count as f64; // Clearly float division ``` ### 2. Use Checked Arithmetic for User Input [Section titled “2. Use Checked Arithmetic for User Input”](#2-use-checked-arithmetic-for-user-input) ** Risky:** ```rust 1 let result = user_a + user_b; // Could overflow! ``` ** Safe:** ```rust 1 let result = user_a.checked_add(user_b) 2 .expect("Overflow!"); ``` ### 3. Use Ranges for Bounds Checks [Section titled “3. Use Ranges for Bounds Checks”](#3-use-ranges-for-bounds-checks) Unlike Python, Rust has **no chained comparisons** (`0 < x < 100` does not parse). You either combine two comparisons with `&&`, or — often clearer — use a range’s `.contains()` method: ** Verbose:** ```rust 1 if x > 0 && x < 100 { 2 // ... 3 } ``` ** Clear (when appropriate):** ```rust 1 if (0..100).contains(&x) { 2 // ... 3 } ``` ### 4. Parenthesize Complex Expressions [Section titled “4. Parenthesize Complex Expressions”](#4-parenthesize-complex-expressions) ** Hard to read:** ```rust 1 let result = a + b * c - d / e; ``` ** Clear:** ```rust 1 let result = a + (b * c) - (d / e); ``` *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) ### Temperature Conversion [Section titled “Temperature Conversion”](#temperature-conversion) **TypeScript:** ```typescript 1 function celsiusToFahrenheit(celsius: number): number { 2 return (celsius * 9) / 5 + 32; 3 } 4 5 function fahrenheitToCelsius(fahrenheit: number): number { 6 return ((fahrenheit - 32) * 5) / 9; 7 } 8 9 console.log(celsiusToFahrenheit(0)); // 32 10 console.log(fahrenheitToCelsius(32)); // 0 ``` **Rust:** ```rust 1 fn celsius_to_fahrenheit(celsius: f64) -> f64 { 2 (celsius * 9.0) / 5.0 + 32.0 3 } 4 5 fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 { 6 (fahrenheit - 32.0) * 5.0 / 9.0 7 } 8 9 fn main() { 10 println!("{}", celsius_to_fahrenheit(0.0)); // 32 11 println!("{}", fahrenheit_to_celsius(32.0)); // 0 12 } ``` ### Calculating Percentage [Section titled “Calculating Percentage”](#calculating-percentage) **TypeScript:** ```typescript 1 function percentage(value: number, total: number): number { 2 return (value / total) * 100; 3 } 4 5 console.log(percentage(25, 100)); // 25 6 console.log(percentage(1, 3)); // 33.333... ``` **Rust:** ```rust 1 fn percentage(value: f64, total: f64) -> f64 { 2 (value / total) * 100.0 3 } 4 5 fn main() { 6 println!("{}", percentage(25.0, 100.0)); // 25 7 println!("{:.2}", percentage(1.0, 3.0)); // 33.33 8 } ``` ### Bit Manipulation (Flags) [Section titled “Bit Manipulation (Flags)”](#bit-manipulation-flags) ```rust 1 // Permission flags using bitwise operations 2 const READ: u8 = 0b0001; // 1 3 const WRITE: u8 = 0b0010; // 2 4 const EXECUTE: u8 = 0b0100; // 4 5 const DELETE: u8 = 0b1000; // 8 6 7 fn main() { 8 // Combine permissions 9 let perms = READ | WRITE; // 0b0011 (3) 10 11 // Check permission 12 let has_read = (perms & READ) != 0; // true 13 let has_exec = (perms & EXECUTE) != 0; // false 14 15 // Add permission 16 let perms = perms | EXECUTE; // 0b0111 (7) 17 18 // Remove permission 19 let perms = perms & !WRITE; // 0b0101 (5) 20 21 println!("Permissions: {:04b}", perms); // 0101 22 } ``` *** ## Operator Precedence [Section titled “Operator Precedence”](#operator-precedence) From highest to lowest: 1. Unary: `-`, `!` 2. Multiplicative: `*`, `/`, `%` 3. Additive: `+`, `-` 4. Shift: `<<`, `>>` 5. Bitwise AND: `&` 6. Bitwise XOR: `^` 7. Bitwise OR: `|` 8. Comparison: `==`, `!=`, `<`, `>`, `<=`, `>=` 9. Logical AND: `&&` 10. Logical OR: `||` **Use parentheses when in doubt!** ```rust 1 let result = a + b * c; // b * c first 2 let result = (a + b) * c; // a + b first ``` *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book - Operators](https://doc.rust-lang.org/book/appendix-02-operators.html) * [Rust Reference - Operators](https://doc.rust-lang.org/reference/expressions/operator-expr.html) * [Rust by Example - Operations](https://doc.rust-lang.org/rust-by-example/primitives.html) *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Fix Division [Section titled “Exercise 1: Fix Division”](#exercise-1-fix-division) Fix this to return a float: ```rust 1 fn average(a: i32, b: i32) -> f64 { 2 (a + b) / 2 // Returns integer! 3 } 4 5 fn main() { 6 println!("{}", average(5, 10)); // Should be 7.5 7 } ``` Solution ```rust 1 fn average(a: i32, b: i32) -> f64 { 2 (a + b) as f64 / 2.0 3 } 4 5 fn main() { 6 println!("{}", average(5, 10)); // 7.5 7 } ``` ### Exercise 2: Check Range [Section titled “Exercise 2: Check Range”](#exercise-2-check-range) Check if a number is in range 10-20 (inclusive): ```rust 1 fn in_range(x: i32) -> bool { 2 // Implement 3 } 4 5 fn main() { 6 println!("{}", in_range(15)); // true 7 println!("{}", in_range(25)); // false 8 } ``` Solution ```rust 1 fn in_range(x: i32) -> bool { 2 x >= 10 && x <= 20 3 } 4 5 fn main() { 6 println!("{}", in_range(15)); // true 7 println!("{}", in_range(25)); // false 8 } ``` ### Exercise 3: Swap Values [Section titled “Exercise 3: Swap Values”](#exercise-3-swap-values) Swap two values without a temporary variable: ```rust 1 fn main() { 2 let mut a = 5; 3 let mut b = 10; 4 5 // Swap a and b 6 // Hint: Rust gives you two idiomatic, overflow-safe ways 7 8 println!("a: {}, b: {}", a, b); // Should be a: 10, b: 5 9 } ``` Solution The classic “arithmetic swap” (`a = a + b; b = a - b; a = a - b;`) works for small numbers but **overflows and panics in debug** when the values are near the integer bounds (e.g. `a = i32::MAX`). Rust gives you two safe, idiomatic alternatives instead: ```rust 1 fn main() { 2 // Option 1: tuple destructuring assignment 3 let mut a = 5; 4 let mut b = 10; 5 (a, b) = (b, a); 6 println!("a: {}, b: {}", a, b); // a: 10, b: 5 7 8 // Option 2: std::mem::swap 9 let mut c = 5; 10 let mut d = 10; 11 std::mem::swap(&mut c, &mut d); 12 println!("c: {}, d: {}", c, d); // c: 10, d: 5 13 } ``` > **Warning:** The arithmetic trick (`a = a + b; ...`) is a classic interview puzzle, but it is fragile: `a + b` can overflow. In debug builds Rust **panics**, in release builds it **wraps** to the wrong answer. Prefer tuple destructuring or `std::mem::swap`. ### Exercise 4: Check Power of Two [Section titled “Exercise 4: Check Power of Two”](#exercise-4-check-power-of-two) Check if a number is a power of 2 using bitwise operations: ```rust 1 fn is_power_of_two(n: u32) -> bool { 2 // Hint: Powers of 2 have only one bit set 3 // n & (n - 1) == 0 for powers of 2 4 } 5 6 fn main() { 7 println!("{}", is_power_of_two(8)); // true 8 println!("{}", is_power_of_two(10)); // false 9 println!("{}", is_power_of_two(16)); // true 10 } ``` Solution ```rust 1 fn is_power_of_two(n: u32) -> bool { 2 n != 0 && (n & (n - 1)) == 0 3 } 4 5 fn main() { 6 println!("{}", is_power_of_two(8)); // true 7 println!("{}", is_power_of_two(10)); // false 8 println!("{}", is_power_of_two(16)); // true 9 } ``` *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Arithmetic operators (`+`, `-`, `*`, `/`, `%`) * Comparison operators (`==`, `!=`, `<`, `>`, etc.) * Logical operators (`&&`, `||`, `!`) * Bitwise operators (`&`, `|`, `^`, `<<`, `>>`) * No type coercion in Rust * Integer vs float division * Operator precedence **Key differences from TypeScript:** | Aspect | TypeScript | Rust | | ------------- | --------------------- | ------------------------------- | | Equality | `==`, `===` | Only `==` (strict) | | Division | Always float | Integer if integers | | Type coercion | Implicit | Never! | | Overflow | Loses precision (f64) | Panics (debug), wraps (release) | **Operators work mostly the same, but Rust is more strict!** # Comments and Documentation > Comments in Rust are similar to TypeScript/JavaScript, with built-in additions for generating documentation automatically. Comments in Rust are similar to TypeScript/JavaScript, with built-in additions for generating documentation automatically. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Rust has three types of comments: * **Regular comments:** `//` and `/* */` (like JS/TS) * **Doc comments:** `///` and `//!` (generates HTML docs!) * **No JSDoc needed:** Built into the language **Doc comments are a first-class feature in Rust!** *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // Single-line comment 2 3 /* 4 Multi-line comment 5 across several lines 6 */ 7 8 /** 9 * JSDoc documentation comment 10 * @param name - User's name 11 * @param age - User's age 12 * @returns Greeting message 13 */ 14 function greet(name: string, age: number): string { 15 return `Hello, ${name}! You are ${age} years old.`; 16 } 17 18 // Inline comment at end of line 19 let x = 5; // This is x ``` **JSDoc is a convention, not built into JavaScript.** *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 // Single-line comment 2 3 /* 4 Multi-line comment 5 across several lines 6 */ 7 8 /// Documentation comment for the function 9 /// 10 /// # Arguments 11 /// * `name` - User's name 12 /// * `age` - User's age 13 /// 14 /// # Returns 15 /// A greeting message 16 fn greet(name: &str, age: u32) -> String { 17 format!("Hello, {}! You are {} years old.", name, age) 18 } 19 20 // Inline comment at end of line 21 let x = 5; // This is x ``` **Doc comments are built into Rust and generate HTML docs!** *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Regular Comments [Section titled “Regular Comments”](#regular-comments) ```rust 1 // This is a single-line comment 2 3 // You can chain them 4 // to create multiple lines 5 // of comments 6 7 /* This is a multi-line comment */ 8 9 /* 10 Multi-line comments 11 can span multiple lines 12 and can /* nest */ too! 13 */ 14 15 fn main() { 16 let x = 5; // Inline comment 17 // let y = 10; // Commented-out code 18 } ``` **Same as TypeScript/JavaScript!** ### Documentation Comments [Section titled “Documentation Comments”](#documentation-comments) **Outer doc comments** (document the item that follows): ````rust 1 /// Adds two numbers together 2 /// 3 /// # Examples 4 /// 5 /// ``` 6 /// let result = add(2, 3); 7 /// assert_eq!(result, 5); 8 /// ``` 9 fn add(a: i32, b: i32) -> i32 { 10 a + b 11 } ```` **Inner doc comments** (document the enclosing item): ```rust 1 //! This module provides math utilities 2 //! 3 //! It includes functions for basic arithmetic operations. 4 5 fn add(a: i32, b: i32) -> i32 { 6 a + b 7 } ``` **Generate HTML docs:** ```bash 1 cargo doc --open ``` This creates beautiful HTML documentation automatically! ### Doc Comment Structure [Section titled “Doc Comment Structure”](#doc-comment-structure) **Common sections:** ````rust 1 /// Brief description (one line) 2 /// 3 /// More detailed explanation can go here 4 /// across multiple lines. 5 /// 6 /// # Arguments 7 /// * `x` - The first number 8 /// * `y` - The second number 9 /// 10 /// # Returns 11 /// The sum of x and y 12 /// 13 /// # Examples 14 /// ``` 15 /// let result = add(5, 3); 16 /// assert_eq!(result, 8); 17 /// ``` 18 /// 19 /// # Panics 20 /// This function panics if... 21 /// 22 /// # Errors 23 /// Returns an error if... 24 /// 25 /// # Safety 26 /// This function is unsafe because... 27 fn add(x: i32, y: i32) -> i32 { 28 x + y 29 } ```` **Markdown supported:** ````rust 1 /// This function uses **bold** and *italic* 2 /// 3 /// # Examples 4 /// 5 /// ``` 6 /// let x = my_func(); 7 /// ``` 8 /// 9 /// See also: [other_func] 10 fn my_func() -> i32 { 11 42 12 } ```` ### Code Examples in Docs [Section titled “Code Examples in Docs”](#code-examples-in-docs) ````rust 1 /// Multiplies two numbers 2 /// 3 /// # Examples 4 /// 5 /// ``` 6 /// let result = multiply(2, 3); 7 /// assert_eq!(result, 6); 8 /// ``` 9 /// 10 /// This example shows error handling: 11 /// ``` 12 /// let result = checked_multiply(i32::MAX, 2); 13 /// assert!(result.is_none()); 14 /// ``` 15 fn multiply(a: i32, b: i32) -> i32 { 16 a * b 17 } ```` **Examples are automatically tested!** ```bash 1 cargo test --doc ``` This runs all code examples in your documentation. > **Note:** Each example is compiled and run as its own small crate that depends on yours, so anything it references (your functions, types, etc.) must be `pub` and brought into scope with `use`. To keep that setup out of the rendered docs, prefix the line with `#` (see Pitfall 3 below). *** ## Key Differences from TypeScript/JavaScript [Section titled “Key Differences from TypeScript/JavaScript”](#key-differences-from-typescriptjavascript) ### 1. Built-in Documentation [Section titled “1. Built-in Documentation”](#1-built-in-documentation) **TypeScript:** ```typescript 1 /** 2 * JSDoc comment (convention, not part of TS) 3 * @param x - Number 4 * @returns Result 5 */ 6 function double(x: number): number { 7 return x * 2; 8 } 9 10 // Need external tool (TypeDoc) to generate docs ``` **Rust:** ```rust 1 /// Documentation comment (built into Rust!) 2 /// 3 /// # Arguments 4 /// * `x` - Number 5 /// 6 /// # Returns 7 /// Result 8 fn double(x: i32) -> i32 { 9 x * 2 10 } 11 12 // cargo doc generates HTML automatically ``` ### 2. Testable Examples [Section titled “2. Testable Examples”](#2-testable-examples) **TypeScript:** ````typescript 1 /** 2 * @example 3 * ```typescript 4 * const result = add(2, 3); 5 * // Examples are not automatically tested 6 * ``` 7 */ ```` **Rust:** ````rust 1 /// # Examples 2 /// ``` 3 /// let result = add(2, 3); 4 /// assert_eq!(result, 5); 5 /// // This example is automatically tested! 6 /// ``` ```` ### 3. Nested Comments [Section titled “3. Nested Comments”](#3-nested-comments) **JavaScript:** ```javascript 1 /* 2 /* This won't work! */ 3 Can't nest multi-line comments 4 */ ``` **Rust:** ```rust 1 /* 2 /* This works! */ 3 Rust supports nested comments 4 */ ``` *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Using JSDoc Style [Section titled “Pitfall 1: Using JSDoc Style”](#pitfall-1-using-jsdoc-style) **Problem:** ```rust 1 /** 2 * @param x - A number 3 * @returns The doubled value 4 */ 5 fn double(x: i32) -> i32 { 6 x * 2 7 } ``` **Why:** This works, but isn’t idiomatic Rust. **Solution:** ```rust 1 /// Doubles a number 2 /// 3 /// # Arguments 4 /// * `x` - A number 5 /// 6 /// # Returns 7 /// The doubled value 8 fn double(x: i32) -> i32 { 9 x * 2 10 } ``` ### Pitfall 2: Forgetting Outer Doc Comment After Inner [Section titled “Pitfall 2: Forgetting Outer Doc Comment After Inner”](#pitfall-2-forgetting-outer-doc-comment-after-inner) **Problem:** ```rust 1 //! Module documentation 2 3 fn my_func() { // No doc comment! 4 // ... 5 } ``` **Solution:** ```rust 1 //! Module documentation 2 3 /// Function documentation 4 fn my_func() { 5 // ... 6 } ``` ### Pitfall 3: Items Not in Scope in Doc Examples [Section titled “Pitfall 3: Items Not in Scope in Doc Examples”](#pitfall-3-items-not-in-scope-in-doc-examples) **Problem:** ````rust 1 /// # Examples 2 /// ``` 3 /// let result = add(2, 3); // Will fail doc test! 4 /// assert_eq!(result, 5); 5 /// ``` 6 pub fn add(a: i32, b: i32) -> i32 { 7 a + b 8 } ```` **Why:** A doc test is compiled as a **separate crate** that depends on yours, so your item is not automatically in scope. The example above fails with `error[E0425]: cannot find function add in this scope` and a help line suggesting `use your_crate::add;`. (Note: a missing `main` is *not* the cause — rustdoc automatically wraps each example in a `main` function for you.) **Solution:** ````rust 1 /// # Examples 2 /// ``` 3 /// # use your_crate::add; 4 /// let result = add(2, 3); 5 /// assert_eq!(result, 5); 6 /// ``` 7 pub fn add(a: i32, b: i32) -> i32 { 8 a + b 9 } ```` Use a `#`-prefixed line to hide setup (such as `use` imports) from the rendered docs while still compiling and running it in the test. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Use Doc Comments for Public API [Section titled “1. Use Doc Comments for Public API”](#1-use-doc-comments-for-public-api) ** Don’t document everything:** ```rust 1 // Helper function 2 fn internal_helper(x: i32) -> i32 { 3 x * 2 4 } 5 6 /// Public API should be documented 7 pub fn public_api(x: i32) -> i32 { 8 internal_helper(x) 9 } ``` ** Document public items:** ````rust 1 // Internal, no doc comment needed 2 fn internal_helper(x: i32) -> i32 { 3 x * 2 4 } 5 6 /// Doubles the input number 7 /// 8 /// # Examples 9 /// ``` 10 /// let result = public_api(5); 11 /// assert_eq!(result, 10); 12 /// ``` 13 pub fn public_api(x: i32) -> i32 { 14 internal_helper(x) 15 } ```` ### 2. Include Examples [Section titled “2. Include Examples”](#2-include-examples) ** Just describe:** ```rust 1 /// Adds two numbers 2 fn add(a: i32, b: i32) -> i32 { 3 a + b 4 } ``` ** Show how to use:** ````rust 1 /// Adds two numbers 2 /// 3 /// # Examples 4 /// ``` 5 /// let sum = add(2, 3); 6 /// assert_eq!(sum, 5); 7 /// ``` 8 fn add(a: i32, b: i32) -> i32 { 9 a + b 10 } ```` ### 3. Document Panics and Errors [Section titled “3. Document Panics and Errors”](#3-document-panics-and-errors) ** Don’t hide gotchas:** ```rust 1 /// Divides two numbers 2 fn divide(a: i32, b: i32) -> i32 { 3 a / b // Panics if b is 0! 4 } ``` ** Document behavior:** ````rust 1 /// Divides two numbers 2 /// 3 /// # Panics 4 /// Panics if `b` is zero 5 /// 6 /// # Examples 7 /// ``` 8 /// let result = divide(10, 2); 9 /// assert_eq!(result, 5); 10 /// ``` 11 fn divide(a: i32, b: i32) -> i32 { 12 a / b 13 } ```` ### 4. Use Markdown for Formatting [Section titled “4. Use Markdown for Formatting”](#4-use-markdown-for-formatting) ** Plain text:** ```rust 1 /// Returns the user's age. 2 /// Arguments: 3 /// - name: the user's name 4 /// - id: the user's ID ``` ** Use Markdown:** ```rust 1 /// Returns the user's age 2 /// 3 /// # Arguments 4 /// * `name` - The user's name 5 /// * `id` - The user's ID 6 /// 7 /// # Returns 8 /// The user's age as `u32` ``` *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) ### Documented Math Module [Section titled “Documented Math Module”](#documented-math-module) **TypeScript:** ```typescript 1 /** 2 * Math utilities module 3 */ 4 5 /** 6 * Calculates the factorial of a number 7 * @param n - The number to calculate factorial for 8 * @returns The factorial of n 9 * @throws Error if n is negative 10 */ 11 export function factorial(n: number): number { 12 if (n < 0) throw new Error("Negative number"); 13 if (n === 0) return 1; 14 return n * factorial(n - 1); 15 } 16 17 /** 18 * Checks if a number is prime 19 * @param n - The number to check 20 * @returns true if prime, false otherwise 21 */ 22 export function isPrime(n: number): boolean { 23 if (n < 2) return false; 24 for (let i = 2; i <= Math.sqrt(n); i++) { 25 if (n % i === 0) return false; 26 } 27 return true; 28 } ``` **Rust:** ````rust 1 //! Math utilities module 2 //! 3 //! This module provides common mathematical functions. 4 5 /// Calculates the factorial of a number 6 /// 7 /// # Arguments 8 /// * `n` - The number to calculate factorial for 9 /// 10 /// # Returns 11 /// The factorial of n 12 /// 13 /// # Panics 14 /// Panics if `n` is negative 15 /// 16 /// # Examples 17 /// ``` 18 /// # use math::factorial; 19 /// let result = factorial(5); 20 /// assert_eq!(result, 120); 21 /// ``` 22 pub fn factorial(n: i32) -> i32 { 23 assert!(n >= 0, "factorial is undefined for negative numbers"); 24 if n == 0 { 25 1 26 } else { 27 n * factorial(n - 1) 28 } 29 } 30 31 /// Checks if a number is prime 32 /// 33 /// # Arguments 34 /// * `n` - The number to check 35 /// 36 /// # Returns 37 /// `true` if prime, `false` otherwise 38 /// 39 /// # Examples 40 /// ``` 41 /// # use math::is_prime; 42 /// assert!(is_prime(7)); 43 /// assert!(!is_prime(8)); 44 /// ``` 45 pub fn is_prime(n: u32) -> bool { 46 if n < 2 { 47 return false; 48 } 49 // Use integer arithmetic (`i * i <= n`) for the loop bound. Converting 50 // to `f64` and calling `.sqrt()` works for small values but is a fragile 51 // idiom: float rounding can make the bound off by one for large inputs. 52 let mut i = 2; 53 while i * i <= n { 54 if n % i == 0 { 55 return false; 56 } 57 i += 1; 58 } 59 true 60 } ```` > **Note:** The TypeScript `factorial` above signals invalid input with `throw new Error(...)`, which is a *recoverable* error a caller can catch. The Rust version uses `assert!`/`panic!` to keep the comparison short, but in idiomatic Rust a recoverable failure like this is normally modeled with a return type of `Result` (covered in the error-handling section) rather than a panic. Reserve `panic!` for genuinely unrecoverable bugs. **Generate docs:** ```bash 1 cargo doc --open ``` Beautiful HTML documentation is generated automatically! *** ## Doc Comment Sections [Section titled “Doc Comment Sections”](#doc-comment-sections) Common sections in Rust doc comments: ### Standard Sections [Section titled “Standard Sections”](#standard-sections) ```rust 1 /// Brief one-line description 2 /// 3 /// # Arguments 4 /// Parameter documentation 5 /// 6 /// # Returns 7 /// Return value documentation 8 /// 9 /// # Errors 10 /// Error conditions (for Result) 11 /// 12 /// # Panics 13 /// Panic conditions 14 /// 15 /// # Safety 16 /// Safety requirements (for unsafe) 17 /// 18 /// # Examples 19 /// Usage examples 20 /// 21 /// # See Also 22 /// Related functions ``` ### Custom Sections [Section titled “Custom Sections”](#custom-sections) You can create custom sections: ```rust 1 /// My function 2 /// 3 /// # Performance 4 /// This function runs in O(n) time 5 /// 6 /// # Thread Safety 7 /// This function is thread-safe 8 fn my_func() { 9 // ... 10 } ``` *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book - Comments](https://doc.rust-lang.org/book/ch03-04-comments.html) * [Rust Reference - Comments](https://doc.rust-lang.org/reference/comments.html) * [rustdoc Book](https://doc.rust-lang.org/rustdoc/) ### Related Topics [Section titled “Related Topics”](#related-topics) * [Documentation Guidelines](https://rust-lang.github.io/api-guidelines/documentation.html) *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Add Documentation [Section titled “Exercise 1: Add Documentation”](#exercise-1-add-documentation) Add doc comments to this function: ```rust 1 fn square(x: i32) -> i32 { 2 x * x 3 } ``` Solution ````rust 1 /// Calculates the square of a number 2 /// 3 /// # Arguments 4 /// * `x` - The number to square 5 /// 6 /// # Returns 7 /// The square of `x` 8 /// 9 /// # Examples 10 /// ``` 11 /// let result = square(5); 12 /// assert_eq!(result, 25); 13 /// ``` 14 fn square(x: i32) -> i32 { 15 x * x 16 } ```` ### Exercise 2: Document Panic Condition [Section titled “Exercise 2: Document Panic Condition”](#exercise-2-document-panic-condition) Document the panic condition: ```rust 1 fn divide(a: i32, b: i32) -> i32 { 2 if b == 0 { 3 panic!("Division by zero!"); 4 } 5 a / b 6 } ``` Solution ````rust 1 /// Divides two numbers 2 /// 3 /// # Arguments 4 /// * `a` - The dividend 5 /// * `b` - The divisor 6 /// 7 /// # Returns 8 /// The result of a / b 9 /// 10 /// # Panics 11 /// Panics if `b` is zero 12 /// 13 /// # Examples 14 /// ``` 15 /// let result = divide(10, 2); 16 /// assert_eq!(result, 5); 17 /// ``` 18 fn divide(a: i32, b: i32) -> i32 { 19 if b == 0 { 20 panic!("Division by zero!"); 21 } 22 a / b 23 } ```` ### Exercise 3: Module Documentation [Section titled “Exercise 3: Module Documentation”](#exercise-3-module-documentation) Add module-level documentation: math.rs ```rust 1 pub fn add(a: i32, b: i32) -> i32 { 2 a + b 3 } 4 5 pub fn subtract(a: i32, b: i32) -> i32 { 6 a - b 7 } ``` Solution ````rust 1 //! Math utilities module 2 //! 3 //! This module provides basic arithmetic operations. 4 //! 5 //! # Examples 6 //! ``` 7 //! let sum = math::add(2, 3); 8 //! assert_eq!(sum, 5); 9 //! ``` 10 11 /// Adds two numbers 12 /// 13 /// # Examples 14 /// ``` 15 /// let result = add(2, 3); 16 /// assert_eq!(result, 5); 17 /// ``` 18 pub fn add(a: i32, b: i32) -> i32 { 19 a + b 20 } 21 22 /// Subtracts two numbers 23 /// 24 /// # Examples 25 /// ``` 26 /// let result = subtract(5, 3); 27 /// assert_eq!(result, 2); 28 /// ``` 29 pub fn subtract(a: i32, b: i32) -> i32 { 30 a - b 31 } ```` *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Regular comments (`//`, `/* */`) * Doc comments (`///`, `//!`) * Doc comment structure (Arguments, Returns, Examples, etc.) * Markdown in doc comments * Testable code examples * Generating HTML documentation **Key differences from TypeScript:** | Feature | TypeScript | Rust | | ---------------- | ------------------ | ----------------------- | | Regular comments | `//`, `/* */` | Same | | Doc comments | JSDoc (convention) | `///`, `//!` (built-in) | | Doc generation | TypeDoc (external) | `cargo doc` (built-in) | | Example testing | Manual | Automatic | | Nested comments | No | Yes | **Doc comments are a first-class feature in Rust!** # Output and Formatting > Learn how to print output in Rust using the println! macro and its rich formatting capabilities. Learn how to print output in Rust using the `println!` macro and its rich formatting capabilities. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Rust uses macros for printing: * `println!()` - Print with newline * `print!()` - Print without newline * Format strings use `{}` placeholders * Rich formatting options built-in **Similar to Python’s f-strings or C’s printf!** *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // Simple printing 2 console.log("Hello, world!"); 3 4 // With variables 5 const name = "Alice"; 6 const age = 30; 7 8 // Template literals 9 console.log(`Hello, ${name}!`); 10 console.log(`${name} is ${age} years old`); 11 12 // Multiple values 13 console.log("Name:", name, "Age:", age); 14 15 // Formatting numbers 16 const pi = 3.14159; 17 console.log(pi.toFixed(2)); // "3.14" 18 19 // Objects 20 const user = { name: "Bob", age: 25 }; 21 console.log(user); // { name: 'Bob', age: 25 } 22 console.log(JSON.stringify(user)); // {"name":"Bob","age":25} ``` **TypeScript uses template literals and method calls.** *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 // Simple printing 2 println!("Hello, world!"); 3 4 // With variables 5 let name = "Alice"; 6 let age = 30; 7 8 // Format strings 9 println!("Hello, {}!", name); 10 println!("{} is {} years old", name, age); 11 12 // Inline capture (placeholder names a variable in scope) 13 println!("Hello, {name}!"); 14 15 // Named arguments (placeholder maps to an explicit value) 16 println!("Hello, {who}!", who = name); 17 18 // Formatting numbers 19 let pi = 3.14159; 20 println!("{:.2}", pi); // "3.14" 21 22 // Debug formatting 23 let user = ("Bob", 25); 24 println!("{:?}", user); // ("Bob", 25) 25 26 // Pretty debug 27 println!("{:#?}", user); ``` **Rust uses macros with expressive format strings.** *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Basic Printing [Section titled “Basic Printing”](#basic-printing) ```rust 1 fn main() { 2 // Print with newline 3 println!("Hello!"); 4 println!("World!"); 5 6 // Print without newline 7 print!("Hello, "); 8 print!("World!\n"); 9 10 // Print to stderr 11 eprintln!("Error message!"); 12 } ``` **Output:** ```plaintext 1 Hello! 2 World! 3 Hello, World! 4 Error message! ``` ### Placeholders [Section titled “Placeholders”](#placeholders) ```rust 1 fn main() { 2 let name = "Alice"; 3 let age = 30; 4 5 // Positional 6 println!("{} is {} years old", name, age); 7 8 // Indexed 9 println!("{0} is {1} years old", name, age); 10 println!("{1} is the age of {0}", name, age); // Reorder! 11 12 // Inline capture (names a variable already in scope) 13 println!("{name} is {age} years old"); 14 15 // Named arguments (the placeholder maps to a value you pass) 16 println!("{who} is {years} years old", who = name, years = age); 17 18 // Mixed positional and named 19 println!("{0} is {years} years old", name, years = age); 20 } ``` **Compare to TypeScript:** ```typescript 1 const name = "Alice"; 2 const age = 30; 3 4 // Template literals 5 console.log(`${name} is ${age} years old`); 6 7 // Only sequential order possible ``` ### Display vs Debug [Section titled “Display vs Debug”](#display-vs-debug) **Display (`{}`)** - Human-readable output: ```rust 1 let x = 42; 2 let s = "hello"; 3 let b = true; 4 5 println!("{}", x); // 42 6 println!("{}", s); // hello 7 println!("{}", b); // true ``` **Debug (`{:?}`)** - Programmer-friendly output: ```rust 1 let tuple = (1, "hello", true); 2 let vec = vec![1, 2, 3]; 3 4 // println!("{}", tuple); // Error: tuples don't implement Display 5 println!("{:?}", tuple); // (1, "hello", true) 6 println!("{:?}", vec); // [1, 2, 3] ``` **Pretty Debug (`{:#?}`)** - Formatted debug: ```rust 1 let data = vec![ 2 ("Alice", 30), 3 ("Bob", 25), 4 ("Charlie", 35), 5 ]; 6 7 println!("{:#?}", data); ``` **Output:** ```plaintext 1 [ 2 ( 3 "Alice", 4 30, 5 ), 6 ( 7 "Bob", 8 25, 9 ), 10 ( 11 "Charlie", 12 35, 13 ), 14 ] ``` ### Number Formatting [Section titled “Number Formatting”](#number-formatting) ```rust 1 let pi = 3.14159; 2 3 // Decimal places 4 println!("{:.2}", pi); // 3.14 5 println!("{:.4}", pi); // 3.1416 6 7 // Width and alignment 8 println!("{:10}", 42); // " 42" (right-aligned) 9 println!("{:<10}", 42); // "42 " (left-aligned) 10 println!("{:^10}", 42); // " 42 " (centered) 11 12 // Zero-padding 13 println!("{:05}", 42); // 00042 14 15 // Sign 16 println!("{:+}", 42); // +42 17 println!("{:+}", -42); // -42 18 19 // Hexadecimal 20 println!("{:x}", 255); // ff 21 println!("{:X}", 255); // FF 22 println!("{:#x}", 255); // 0xff 23 24 // Binary 25 println!("{:b}", 42); // 101010 26 println!("{:#b}", 42); // 0b101010 27 28 // Octal 29 println!("{:o}", 42); // 52 30 println!("{:#o}", 42); // 0o52 ``` **Compare to TypeScript:** ```typescript 1 const pi = 3.14159; 2 3 console.log(pi.toFixed(2)); // "3.14" 4 console.log(pi.toPrecision(4)); // "3.142" 5 6 const n = 255; 7 console.log(n.toString(16)); // "ff" 8 console.log(n.toString(2)); // "11111111" ``` ### String Formatting [Section titled “String Formatting”](#string-formatting) ```rust 1 let name = "Alice"; 2 3 // Truncate 4 println!("{:.5}", name); // Alice (already < 5) 5 println!("{:.3}", name); // Ali 6 7 // Width and fill 8 println!("{:10}", name); // "Alice " 9 println!("{:<10}", name); // "Alice " (left) 10 println!("{:>10}", name); // " Alice" (right) 11 println!("{:^10}", name); // " Alice " (center) 12 println!("{:*<10}", name); // "Alice*****" (custom fill) ``` ### Escape Sequences [Section titled “Escape Sequences”](#escape-sequences) ```rust 1 println!("New line\n"); 2 println!("Tab\there"); 3 println!("Backslash: \\"); 4 println!("Quote: \""); 5 println!("Single quote: \'"); 6 println!("Unicode: \u{1F44D}"); // ``` ### Format! Macro [Section titled “Format! Macro”](#format-macro) Create formatted strings without printing: ```rust 1 let name = "Alice"; 2 let age = 30; 3 4 // Create a String 5 let s = format!("{} is {} years old", name, age); 6 println!("{}", s); // Alice is 30 years old 7 8 // Useful for building strings 9 let filename = format!("report_{}.txt", 2024); ``` **Compare to TypeScript:** ```typescript 1 const name = "Alice"; 2 const age = 30; 3 4 const s = `${name} is ${age} years old`; 5 console.log(s); ``` *** ## Key Differences from TypeScript/JavaScript [Section titled “Key Differences from TypeScript/JavaScript”](#key-differences-from-typescriptjavascript) ### 1. Macros vs Functions [Section titled “1. Macros vs Functions”](#1-macros-vs-functions) **TypeScript:** ```typescript 1 console.log("Hello"); // Function call ``` **Rust:** ```rust 1 println!("Hello"); // Macro invocation (note the !) ``` **Why macros?** They’re expanded at compile time and can check format strings! ### 2. Format String Syntax [Section titled “2. Format String Syntax”](#2-format-string-syntax) **TypeScript:** ```typescript 1 const name = "Alice"; 2 console.log(`Hello, ${name}!`); // Template literal ``` **Rust:** ```rust 1 let name = "Alice"; 2 println!("Hello, {}!", name); // Format string ``` ### 3. Debug Printing [Section titled “3. Debug Printing”](#3-debug-printing) **TypeScript:** ```typescript 1 const obj = { name: "Alice", age: 30 }; 2 console.log(obj); // Pretty print in browser/Node.js 3 console.log(JSON.stringify(obj)); // Manual serialization ``` **Rust:** ```rust 1 let tuple = ("Alice", 30); 2 // println!("{}", tuple); // Error 3 println!("{:?}", tuple); // Debug format 4 println!("{:#?}", tuple); // Pretty debug ``` ### 4. Number Formatting [Section titled “4. Number Formatting”](#4-number-formatting) **TypeScript:** ```typescript 1 const n = 42; 2 console.log(n.toString(16)); // "2a" (hex) 3 console.log(n.toString(2)); // "101010" (binary) ``` **Rust:** ```rust 1 let n = 42; 2 println!("{:x}", n); // 2a (hex) 3 println!("{:b}", n); // 101010 (binary) ``` *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Forgetting Braces [Section titled “Pitfall 1: Forgetting Braces”](#pitfall-1-forgetting-braces) **Problem:** ```rust 1 let name = "Alice"; 2 println!("Hello, name!"); // Prints literally "Hello, name!" ``` **Solution:** ```rust 1 let name = "Alice"; 2 println!("Hello, {}!", name); // "Hello, Alice!" ``` ### Pitfall 2: Type Doesn’t Implement Display [Section titled “Pitfall 2: Type Doesn’t Implement Display”](#pitfall-2-type-doesnt-implement-display) **Problem:** ```rust 1 let tuple = (1, 2); 2 println!("{}", tuple); // Error: tuple doesn't implement Display ``` **Solution:** ```rust 1 let tuple = (1, 2); 2 println!("{:?}", tuple); // Use debug format ``` ### Pitfall 3: Wrong Number of Arguments [Section titled “Pitfall 3: Wrong Number of Arguments”](#pitfall-3-wrong-number-of-arguments) **Problem:** ```rust 1 println!("{} and {}", 42); // Error: 2 placeholders, 1 argument ``` **Solution:** ```rust 1 println!("{} and {}", 42, 100); // Match placeholders ``` ### Pitfall 4: Trying to Format References [Section titled “Pitfall 4: Trying to Format References”](#pitfall-4-trying-to-format-references) **Problem:** ```rust 1 let s = String::from("hello"); 2 println!("{}", &s); // Works, but... ``` **Why it works:** Rust automatically dereferences for Display. **Better:** ```rust 1 let s = String::from("hello"); 2 println!("{}", s); // Clearer ``` *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Use Debug for Development [Section titled “1. Use Debug for Development”](#1-use-debug-for-development) ** Implement Display for everything:** ```rust 1 struct Point { x: i32, y: i32 } 2 // Lots of boilerplate to implement Display... ``` ** Use derive Debug:** ```rust 1 #[derive(Debug)] 2 struct Point { x: i32, y: i32 } 3 4 let p = Point { x: 10, y: 20 }; 5 println!("{:?}", p); // Point { x: 10, y: 20 } ``` ### 2. Use Inline Captures for Clarity [Section titled “2. Use Inline Captures for Clarity”](#2-use-inline-captures-for-clarity) ** Unclear:** ```rust 1 println!("User {} (ID: {}) logged in at {}", name, id, time); ``` ** Clear:** ```rust 1 println!("User {name} (ID: {id}) logged in at {time}"); ``` > **Tip:** Since Rust 2021, named placeholders capture identifiers from the surrounding scope directly. The older explicit form `println!("{name}", name = name)` is exactly what `clippy::uninlined_format_args` flags, with the suggestion to drop the redundant `name = name` argument. ### 3. Use format! for String Building [Section titled “3. Use format! for String Building”](#3-use-format-for-string-building) ** Concatenation:** ```rust 1 let s = "Hello, ".to_string() + name + "!"; ``` ** Use format!:** ```rust 1 let s = format!("Hello, {}!", name); ``` ### 4. Pretty Print Complex Data [Section titled “4. Pretty Print Complex Data”](#4-pretty-print-complex-data) ** Hard to read:** ```rust 1 println!("{:?}", complex_data); ``` ** Pretty print:** ```rust 1 println!("{:#?}", complex_data); ``` *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) ### Logging with Formatting [Section titled “Logging with Formatting”](#logging-with-formatting) **TypeScript:** ```typescript 1 function log(level: string, message: string) { 2 const timestamp = new Date().toISOString(); 3 console.log(`[${timestamp}] ${level}: ${message}`); 4 } 5 6 log("INFO", "Server started"); 7 log("ERROR", "Connection failed"); ``` **Rust:** ```rust 1 fn log(level: &str, message: &str) { 2 let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S"); 3 println!("[{}] {}: {}", timestamp, level, message); 4 } 5 6 fn main() { 7 log("INFO", "Server started"); 8 log("ERROR", "Connection failed"); 9 } ``` ### Displaying Table Data [Section titled “Displaying Table Data”](#displaying-table-data) ```rust 1 fn print_table(users: &[(&str, u8, f64)]) { 2 println!("{:<15} {:>5} {:>10}", "Name", "Age", "Score"); 3 println!("{:-<32}", ""); // Separator 4 5 for (name, age, score) in users { 6 println!("{:<15} {:>5} {:>10.2}", name, age, score); 7 } 8 } 9 10 fn main() { 11 let users = [ 12 ("Alice", 30, 95.5), 13 ("Bob", 25, 87.3), 14 ("Charlie", 35, 92.0), 15 ]; 16 17 print_table(&users); 18 } ``` **Output:** ```plaintext 1 Name Age Score 2 -------------------------------- 3 Alice 30 95.50 4 Bob 25 87.30 5 Charlie 35 92.00 ``` *** ## Format Specifiers Reference [Section titled “Format Specifiers Reference”](#format-specifiers-reference) | Specifier | Description | Example | Output | | --------- | --------------------- | ----------------------- | ---------- | | `{}` | Display | `println!("{}", 42)` | 42 | | `{:?}` | Debug | `println!("{:?}", x)` | (1, 2) | | `{:#?}` | Pretty debug | `println!("{:#?}", x)` | Multi-line | | `{:b}` | Binary | `println!("{:b}", 42)` | 101010 | | `{:x}` | Lowercase hex | `println!("{:x}", 42)` | 2a | | `{:X}` | Uppercase hex | `println!("{:X}", 42)` | 2A | | `{:o}` | Octal | `println!("{:o}", 42)` | 52 | | `{:p}` | Pointer | `println!("{:p}", &x)` | 0x7fff… | | `{:e}` | Lowercase exponential | `println!("{:e}", pi)` | 3.14159e0 | | `{:E}` | Uppercase exponential | `println!("{:E}", pi)` | 3.14159E0 | | `{:.N}` | Precision (N digits) | `println!("{:.2}", pi)` | 3.14 | | `{:W}` | Width (W characters) | `println!("{:5}", 42)` | ” 42” | | `{:W}` | Right align | `println!("{:>5}", 42)` | ” 42” | | `{:^W}` | Center align | `println!("{:^5}", 42)` | ” 42 “ | | `{:0W}` | Zero pad | `println!("{:05}", 42)` | 00042 | | `{:+}` | Always show sign | `println!("{:+}", 42)` | +42 | *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [std::fmt module](https://doc.rust-lang.org/std/fmt/) * [The Rust Book - Output](https://doc.rust-lang.org/book/ch03-05-control-flow.html) * [println! macro](https://doc.rust-lang.org/std/macro.println.html) *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Basic Printing [Section titled “Exercise 1: Basic Printing”](#exercise-1-basic-printing) Print “Hello, Rust!” three times: ```rust 1 fn main() { 2 // Your code here 3 } ``` Solution ```rust 1 fn main() { 2 println!("Hello, Rust!"); 3 println!("Hello, Rust!"); 4 println!("Hello, Rust!"); 5 } ``` ### Exercise 2: Variable Printing [Section titled “Exercise 2: Variable Printing”](#exercise-2-variable-printing) Print a person’s name and age in format: “Alice is 30 years old” ```rust 1 fn main() { 2 let name = "Alice"; 3 let age = 30; 4 // Your code here 5 } ``` Solution ```rust 1 fn main() { 2 let name = "Alice"; 3 let age = 30; 4 println!("{} is {} years old", name, age); 5 } ``` ### Exercise 3: Number Formatting [Section titled “Exercise 3: Number Formatting”](#exercise-3-number-formatting) Format π (3.14159) with 2 decimal places: ```rust 1 fn main() { 2 let pi = 3.14159; 3 // Your code here 4 } ``` Solution ```rust 1 fn main() { 2 let pi = 3.14159; 3 println!("{:.2}", pi); // 3.14 4 } ``` ### Exercise 4: Debug Printing [Section titled “Exercise 4: Debug Printing”](#exercise-4-debug-printing) Print a vector using debug format: ```rust 1 fn main() { 2 let numbers = vec![1, 2, 3, 4, 5]; 3 // Your code here 4 } ``` Solution ```rust 1 fn main() { 2 let numbers = vec![1, 2, 3, 4, 5]; 3 println!("{:?}", numbers); // [1, 2, 3, 4, 5] 4 } ``` ### Exercise 5: Create Formatted String [Section titled “Exercise 5: Create Formatted String”](#exercise-5-create-formatted-string) Create a string “User: Alice, Score: 95.5” using format!: ```rust 1 fn main() { 2 let name = "Alice"; 3 let score = 95.5; 4 let s = /* your code */; 5 println!("{}", s); 6 } ``` Solution ```rust 1 fn main() { 2 let name = "Alice"; 3 let score = 95.5; 4 let s = format!("User: {}, Score: {}", name, score); 5 println!("{}", s); 6 } ``` *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * `println!` and `print!` macros * Format placeholders (`{}`, `{:?}`, etc.) * Number formatting (precision, width, padding) * Debug vs Display formatting * `format!` macro for string building * Named and positional parameters **Key macros:** ```rust 1 println!() // Print with newline 2 print!() // Print without newline 3 eprintln!() // Print to stderr 4 format!() // Create formatted String ``` **Common patterns:** ```rust 1 println!("{}", x); // Display 2 println!("{:?}", x); // Debug 3 println!("{:#?}", x); // Pretty debug 4 println!("{:.2}", pi); // Precision 5 println!("{:10}", n); // Width ``` **Formatting is expressive and type-safe in Rust!** # Rust Functions & Closures > Map every TypeScript function piece to Rust: signatures, parameters, returns, closures, higher-order functions, fn pointers, and recursion. Functions are the workhorse of any program, and you already write them every day in TypeScript and JavaScript. This section maps each piece you know — declarations, parameters, return values, arrow functions, higher-order functions, function pointers, and recursion — onto its idiomatic Rust counterpart. The syntax is close enough to feel familiar, but a few deeper ideas (typed signatures as hard contracts, the expression-oriented body, the `Fn`/`FnMut`/`FnOnce` closure traits, and the absence of default/rest parameters and guaranteed tail-call optimization) reshape how you read and write Rust code. *** ## What’s in This Section [Section titled “What’s in This Section”](#whats-in-this-section) * **[Basic Functions and Signatures](/03-functions/00-basic-functions/)** - `fn` definitions, typed parameters, return types, statements vs expressions * **[Function Parameters](/03-functions/01-parameters/)** - no default or rest parameters; idiomatic alternatives (`Option`, slices, structs, traits) * **[Return Values](/03-functions/02-return-values/)** - return types, tail expressions, the unit type `()`, early return, returning tuples * **[Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/)** - `|args|` syntax, `Fn`/`FnMut`/`FnOnce`, capture by reference vs `move` * **[Higher-Order Functions](/03-functions/04-higher-order/)** - `map`/`filter`/`reduce` equivalents, taking and returning closures * **[Function Pointers](/03-functions/05-function-pointers/)** - the `fn` type, passing named functions, function items vs closures * **[Recursion](/03-functions/06-recursion/)** - recursive functions, stack depth, iterative alternatives, recursive enums with `Box` *** ## What You’ll Learn [Section titled “What You’ll Learn”](#what-youll-learn) By the end of this section, you will be able to: * Write Rust functions with `fn`, `snake_case` names, mandatory typed parameters, and a `-> Type` return arrow * Use the **tail expression** (last line, no semicolon) as the return value, and reserve `return` for early exits * Reproduce default, optional, and rest parameters with `Option`, slices `&[T]`, `Default` structs, and the builder pattern * Return multiple values as a **tuple** (or a named struct) instead of mutating out-parameters * Translate arrow functions into Rust closures and understand capture by `&`, `&mut`, and `move` * Distinguish the three closure traits — `Fn`, `FnMut`, `FnOnce` — and accept the weakest one that works * Take closures as parameters (`impl Fn`) and return them (`impl Fn` or `Box`) * Pass named functions and constructors as function pointers (`fn(T) -> R`) * Write recursion safely, knowing Rust has no guaranteed tail-call optimization, and reach for iteration or `Box` when appropriate *** ## Topics [Section titled “Topics”](#topics) | # | Topic | What it covers | | - | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | 1 | [Basic Functions and Signatures](/03-functions/00-basic-functions/) | `fn` definitions, typed parameters, the `->` return type, statements vs expressions; vs TS declarations | | 2 | [Function Parameters](/03-functions/01-parameters/) | No default/rest params; `Option`, slices `&[T]`, `Default` structs, builders, “overloading” via traits | | 3 | [Return Values](/03-functions/02-return-values/) | Return types, implicit tail-expression return, the unit type `()`, early `return`, returning tuples | | 4 | [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/) | `(args) =>` becomes `\|args\|`; `Fn`/`FnMut`/`FnOnce`; capture by reference vs move; the `move` keyword | | 5 | [Higher-Order Functions](/03-functions/04-higher-order/) | `map`/`filter`/`reduce` (lazy iterators); taking `impl Fn` and returning `impl Fn` / `Box` | | 6 | [Function Pointers](/03-functions/05-function-pointers/) | The `fn` type, passing named functions, function items vs `fn` pointers vs closures | | 7 | [Recursion](/03-functions/06-recursion/) | Recursive functions, no guaranteed TCO, stack depth, iterative alternatives, recursive enums with `Box` | *** ## Learning Objectives [Section titled “Learning Objectives”](#learning-objectives) After completing this section, a TypeScript/JavaScript developer should be able to: 1. Read any Rust function signature and explain its parameters and return type as a compiler-enforced contract. 2. Explain why a stray semicolon changes a function’s return value to `()`, and fix the resulting `mismatched types` error. 3. Choose the right idiom for “flexible” arguments: `Option`, a slice, a `Default` struct, a builder, or a trait bound. 4. Write closures with inferred types, decide when `move` is required, and pick the correct `Fn`/`FnMut`/`FnOnce` bound. 5. Build lazy iterator pipelines (`iter().filter().map().collect()`) as the replacement for eager `Array.prototype` chains. 6. Decide between a `fn` pointer and a generic `impl Fn` bound for a callback parameter. 7. Recognize when recursion is safe (shallow, recursive data) versus when iteration is the safer Rust default. *** ## Prerequisites [Section titled “Prerequisites”](#prerequisites) This section assumes you have completed: * **[Section 02: Basics](/02-basics/)** — variables and mutability (`let mut` is essential for `FnMut`), the basic types you will pass as parameters, and especially the **statement-vs-expression** distinction that underpins tail-expression returns. If the expression-oriented model (`let x = { ...; a + b };`) feels unfamiliar, re-read the Basics section before starting here. > **Note:** Several topics in this section *preview* concepts covered fully later: ownership and `move` ([Section 05](/05-ownership/)), `Option`/`Result` and the `?` operator ([Section 08](/08-error-handling/)), generics and traits ([Section 09](/09-generics-traits/)), and `Box`/`Rc` for recursive types ([Section 10](/10-smart-pointers/)). You do not need those sections first — the links are there for when you want to go deeper. *** ## Estimated Time [Section titled “Estimated Time”](#estimated-time) * **Reading:** 3-4 hours * **Hands-on Practice & Exercises:** 3-4 hours * **Total:** 6-8 hours A reasonable order is the list order above: basics → parameters → return values → closures → higher-order → function pointers → recursion. Closures (topic 4) are the conceptual heart of the section, so do not skip them. *** ## Frequently asked questions [Section titled “Frequently asked questions”](#frequently-asked-questions) ### How do I write a default or optional parameter? [Section titled “How do I write a default or optional parameter?”](#how-do-i-write-a-default-or-optional-parameter) Rust has neither. Model an optional argument as `Option` and pass `None`, or use the builder pattern when there are many optionals. For overloading-like flexibility, take a generic parameter bounded by a trait. See [Function Parameters](/03-functions/01-parameters/). ### What is the difference between a function and a closure? [Section titled “What is the difference between a function and a closure?”](#what-is-the-difference-between-a-function-and-a-closure) A `fn` is a plain named function. A closure (`|x| x + 1`) can capture variables from its surroundings, and the compiler sorts closures into the `Fn`, `FnMut`, and `FnOnce` traits by how they use what they capture. See [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/). ### Why doesn’t my function need a `return`? [Section titled “Why doesn’t my function need a return?”](#why-doesnt-my-function-need-a-return) The last expression in a block is its value, so `fn add(a: i32, b: i32) -> i32 { a + b }` returns `a + b` with no `return` and no trailing semicolon. An explicit `return` is reserved for early exits. See [Return Values](/03-functions/02-return-values/). # Basic Functions and Signatures > Rust functions resemble TypeScript's, but every parameter is typed and the last expression returns with no return keyword. Learn fn signatures and unit. Functions are the workhorse of any program, and you already write them every day in TypeScript. The Rust syntax is close enough to feel familiar, but two ideas reshape how you read Rust code: **every parameter and return value is explicitly typed**, and **the function body is an expression** whose final line is the return value. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) A Rust function is declared with the `fn` keyword, takes **fully typed** parameters, and declares its return type after a `->` arrow. Unlike TypeScript, Rust never infers parameter types or return types from usage: the signature is a hard contract. And because Rust is **expression-oriented**, the last expression in the body (with no trailing semicolon) is automatically returned. > **Note:** This file covers function definitions, signatures, typed parameters, return types, and the statement-vs-expression model. Parameter patterns (no default or rest parameters), return-value details, and closures each get their own file — see [Further Reading](#further-reading). *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // TypeScript - function declarations with optional type annotations 2 function greet(name: string): string { 3 return `Hello, ${name}!`; 4 } 5 6 function add(a: number, b: number): number { 7 return a + b; 8 } 9 10 // Return type can be inferred and omitted 11 function logMessage(level: string, message: string) { 12 console.log(`[${level}] ${message}`); 13 } 14 15 const message = greet("Ada"); 16 console.log(message); // "Hello, Ada!" 17 18 console.log(`2 + 3 = ${add(2, 3)}`); // "2 + 3 = 5" 19 logMessage("INFO", "service started"); // "[INFO] service started" ``` **Key points:** * `function` keyword, `camelCase` names by convention. * Parameter types are optional in plain JavaScript and only enforced by TypeScript at compile time. * The return type can be omitted and TypeScript will infer it. * `return` is always required to produce a value. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 // Rust - every parameter and the return value is explicitly typed 2 fn greet(name: &str) -> String { 3 format!("Hello, {name}!") 4 } 5 6 fn add(a: i32, b: i32) -> i32 { 7 a + b // tail expression: no semicolon, this is the return value 8 } 9 10 fn log_message(level: &str, message: &str) { 11 println!("[{level}] {message}"); 12 } 13 14 fn main() { 15 let message = greet("Ada"); 16 println!("{message}"); 17 18 let sum = add(2, 3); 19 println!("2 + 3 = {sum}"); 20 21 log_message("INFO", "service started"); 22 } ``` **Output (verified):** ```text 1 Hello, Ada! 2 2 + 3 = 5 3 [INFO] service started ``` **Key points:** * `fn` keyword, `snake_case` names by convention (Rust will warn if you use `camelCase`). * Every parameter **must** have a type; there is no inference for parameters. * The return type follows `->`. Omitting it means the function returns the **unit type** `()` (Rust’s “returns nothing”). * The last expression with **no semicolon** is the return value. No `return` keyword needed. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### The anatomy of a Rust function [Section titled “The anatomy of a Rust function”](#the-anatomy-of-a-rust-function) ```rust 1 fn add(a: i32, b: i32) -> i32 { 2 // ^ ^ ^ ^ 3 // | | | └─ return type (after the arrow) 4 // | | └───────── second typed parameter 5 // | └─────────────────── first typed parameter 6 // └──────────────────────── the `fn` keyword + function name 7 a + b 8 } ``` Compare this to the TypeScript signature `function add(a: number, b: number): number`. The pieces map almost one-to-one: | Piece | TypeScript | Rust | | --------------- | ---------------------- | --------------------------- | | Keyword | `function` | `fn` | | Name convention | `camelCase` | `snake_case` | | Parameter type | `a: number` (optional) | `a: i32` (mandatory) | | Return type | `: number` (optional) | `-> i32` (defaults to `()`) | | Produce a value | `return a + b;` | `a + b` (tail expression) | ### Parameter types are never inferred [Section titled “Parameter types are never inferred”](#parameter-types-are-never-inferred) In TypeScript you can lean on inference for return types, and in plain JavaScript you can skip types entirely. Rust draws a firm line. **Parameter types are always required**, because a function signature is a public contract the compiler checks at every call site. Return types may be omitted only when the function returns `()` (nothing). ```rust 1 // This is the full, idiomatic form. Nothing here is optional except 2 // the return type — and only because returning `()` is a real choice. 3 fn multiply(a: f64, b: f64) -> f64 { 4 a * b 5 } ``` > **Tip:** If you find yourself wishing for “inferred parameter types,” you usually want **generics** instead — a single function that works across many types. That is covered in [Section 09: Generics & Traits](/09-generics-traits/). ### Statements vs expressions: the core mental shift [Section titled “Statements vs expressions: the core mental shift”](#statements-vs-expressions-the-core-mental-shift) This is the idea that trips up most TypeScript developers. Rust distinguishes between: * **Statements** perform an action and return *nothing*. `let x = 5;` is a statement. * **Expressions** evaluate to a *value*. `5 + 3`, a function call, and even an `if`/`match` block are expressions. A function body is a **block**, and a block is itself an expression: its value is the value of its **final expression**, provided that expression has no trailing semicolon. ```rust 1 // statements vs expressions 2 fn classify(score: u32) -> &'static str { 3 // `if` is an expression in Rust; it evaluates to a value. 4 let grade = if score >= 90 { 5 "A" 6 } else if score >= 80 { 7 "B" 8 } else { 9 "C or below" 10 }; 11 12 grade // tail expression 13 } 14 15 fn main() { 16 // A block is an expression; its value is the last expression in it. 17 let x = { 18 let a = 3; 19 let b = 4; 20 a * a + b * b // no semicolon -> this is the block's value 21 }; 22 println!("x = {x}"); 23 24 println!("{}", classify(95)); 25 println!("{}", classify(83)); 26 println!("{}", classify(40)); 27 } ``` **Output (verified):** ```text 1 x = 25 2 A 3 B 4 C or below ``` In TypeScript, an `if`/`else` is a statement; it cannot be assigned to a variable directly. You reach for the ternary operator (`cond ? a : b`) or an immediately-invoked function for anything bigger. In Rust, `if`, `match`, `loop`, and `{ ... }` blocks are all expressions, so you assign them straight to a binding. (Control-flow expressions are explored in depth in [Section 04: Control Flow](/04-control-flow/).) ### Semicolons matter [Section titled “Semicolons matter”](#semicolons-matter) The trailing semicolon is not optional decoration. It changes meaning: * `a + b` (no semicolon) → an expression that becomes the block’s value. * `a + b;` (semicolon) → a statement that throws the value away and evaluates to `()`. So a function whose last line is `a + b;` returns `()`, not the sum. That is one of the most common early mistakes; see [Common Pitfalls](#common-pitfalls). ### Functions are order-independent (no “hoisting” needed) [Section titled “Functions are order-independent (no “hoisting” needed)”](#functions-are-order-independent-no-hoisting-needed) JavaScript hoists `function` declarations so you can call them before they appear in the file. Rust does not need hoisting because items in a module are resolved regardless of order. You can call a function defined later in the same file (or even later in the same module) freely. ```rust 1 // Functions can be called before their definition (order-independent). 2 fn main() { 3 println!("{}", double(21)); 4 } 5 6 fn double(n: i32) -> i32 { 7 n * 2 8 } ``` **Output (verified):** ```text 1 42 ``` > **Note:** This applies to **items** (functions, structs, constants) at module scope. It does *not* apply to local `let` bindings inside a function body, which must be declared before use, exactly like `let`/`const` in JavaScript. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript/JavaScript | Rust | | ---------------------- | --------------------------------------- | ---------------------------------------------------- | | Keyword | `function` | `fn` | | Naming convention | `camelCase` | `snake_case` (lint-enforced) | | Parameter types | Optional (TS) / absent (JS) | **Always required** | | Return type | Optional, inferred | Declared with `->`; defaults to `()` | | Returning a value | `return` keyword required | Tail expression (no semicolon); `return` is optional | | `if` / blocks | Statements (use ternary to get a value) | Expressions (assign directly) | | Call before definition | Hoisted `function` declarations | Order-independent for module items | | ”Returns nothing” | `void` | `()` (the unit type — a real, zero-size value) | | Overloading | Allowed (multiple signatures) | Not allowed; use generics/traits instead | ### `void` vs the unit type `()` [Section titled “void vs the unit type ()”](#void-vs-the-unit-type-) TypeScript’s `void` is a “don’t use this value” marker. Rust’s `()` is a genuine value (the empty tuple) that every expression-with-no-other-value produces. A function with no `-> Type` returns `()`, and you can even bind it: ```rust 1 // A function with no `-> Type` returns the unit type `()`. 2 fn print_banner(title: &str) { 3 println!("==== {title} ===="); 4 } 5 6 // This is exactly equivalent — explicit unit return type. 7 fn print_banner_explicit(title: &str) -> () { 8 println!("==== {title} ===="); 9 } 10 11 fn main() { 12 print_banner("Report"); 13 print_banner_explicit("Report"); 14 15 // The value of calling a unit-returning function IS `()`. 16 let nothing: () = print_banner("Again"); 17 println!("{nothing:?}"); 18 } ``` **Output (verified):** ```text 1 ==== Report ==== 2 ==== Report ==== 3 ==== Again ==== 4 () ``` You will almost never write `-> ()` explicitly; idiomatic Rust omits it. But understanding that “no return type” means “returns `()`” explains the error messages in the next section. ### No function overloading [Section titled “No function overloading”](#no-function-overloading) TypeScript lets you declare multiple signatures for one name. Rust does not. Instead you parameterize over types with **generics and traits**, or accept an `enum`. We point to the idiomatic alternatives in [Function Parameters](/03-functions/01-parameters/) and [Section 09](/09-generics-traits/). *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Accidental semicolon on the return expression [Section titled “Pitfall 1: Accidental semicolon on the return expression”](#pitfall-1-accidental-semicolon-on-the-return-expression) This is *the* classic. You write what looks like a return, add a semicolon out of habit, and the function suddenly returns `()`. ```rust 1 fn add(a: i32, b: i32) -> i32 { 2 a + b; // accidental semicolon turns this into a statement 3 } ``` **Real compiler error:** ```text 1 error[E0308]: mismatched types 2 --> err_semicolon.rs:1:27 3 | 4 1 | fn add(a: i32, b: i32) -> i32 { 5 | --- ^^^ expected `i32`, found `()` 6 | | 7 | implicitly returns `()` as its body has no tail or `return` expression 8 2 | a + b; // accidental semicolon turns this into a statement 9 | - help: remove this semicolon to return this value ``` Notice the compiler points directly at the stray semicolon and tells you to remove it. **Fix:** drop the `;`: ```rust 1 fn add(a: i32, b: i32) -> i32 { 2 a + b 3 } ``` ### Pitfall 2: Omitting a parameter type [Section titled “Pitfall 2: Omitting a parameter type”](#pitfall-2-omitting-a-parameter-type) TypeScript tolerates `function add(a, b)` (with implicit `any` in loose mode). Rust does not: every parameter needs a type. ```rust 1 fn add(a, b) { 2 a + b 3 } ``` **Real compiler error (first message):** ```text 1 error: expected one of `:`, `@`, or `|`, found `,` 2 --> err_noparamtype.rs:1:9 3 | 4 1 | fn add(a, b) { 5 | ^ expected one of `:`, `@`, or `|` 6 | 7 = note: anonymous parameters are removed in the 2018 edition (see RFC 1685) 8 help: if this is a `self` type, give it a parameter name 9 | 10 1 | fn add(self: a, b) { 11 | +++++ 12 help: if this is a parameter name, give it a type 13 | 14 1 | fn add(a: TypeName, b) { 15 | ++++++++++ 16 help: if this is a type, explicitly ignore the parameter name 17 | 18 1 | fn add(_: a, b) { 19 | ++ ``` **Fix:** annotate both parameters and the return type: ```rust 1 fn add(a: i32, b: i32) -> i32 { 2 a + b 3 } ``` ### Pitfall 3: Returning a value without declaring a return type [Section titled “Pitfall 3: Returning a value without declaring a return type”](#pitfall-3-returning-a-value-without-declaring-a-return-type) If you forget the `-> Type`, the compiler assumes the function returns `()` and then complains that your tail expression has the wrong type. ```rust 1 fn square(n: i32) { 2 n * n 3 } ``` **Real compiler error:** ```text 1 error[E0308]: mismatched types 2 --> err_noreturntype.rs:2:5 3 | 4 1 | fn square(n: i32) { 5 | - help: try adding a return type: `-> i32` 6 2 | n * n 7 | ^^^^^ expected `()`, found `i32` ``` The compiler even suggests the exact return type to add. **Fix:** ```rust 1 fn square(n: i32) -> i32 { 2 n * n 3 } ``` ### Pitfall 4: Reaching for `return` everywhere [Section titled “Pitfall 4: Reaching for return everywhere”](#pitfall-4-reaching-for-return-everywhere) Coming from TypeScript, you will instinctively write `return a + b;`. It compiles and works, but it is not idiomatic for the *final* expression of a function, and Clippy (Rust’s linter) will tell you so. ```rust 1 fn add(a: i32, b: i32) -> i32 { 2 return a + b; 3 } ``` **Real `cargo clippy` warning:** ```text 1 warning: unneeded `return` statement 2 --> src/bin/clippy_return.rs:2:5 3 | 4 2 | return a + b; 5 | ^^^^^^^^^^^^ 6 | 7 = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return 8 = note: `#[warn(clippy::needless_return)]` on by default 9 help: remove `return` 10 | 11 2 - return a + b; 12 2 + a + b 13 | ``` `return` is still useful for **early returns** in the middle of a function; that is covered in [Return Values](/03-functions/02-return-values/). For the final value, use the tail expression. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Use tail expressions for the final value [Section titled “1. Use tail expressions for the final value”](#1-use-tail-expressions-for-the-final-value) ```rust 1 // Idiomatic: tail expression 2 fn area(width: f64, height: f64) -> f64 { 3 width * height 4 } 5 6 // Works, but Clippy flags the needless `return` 7 fn area_verbose(width: f64, height: f64) -> f64 { 8 return width * height; 9 } ``` ### 2. Name functions in `snake_case` [Section titled “2. Name functions in snake\_case”](#2-name-functions-in-snake_case) Rust’s compiler warns (`non_snake_case`) if you use `camelCase`. Embrace `snake_case`: `calculate_total`, not `calculateTotal`. ### 3. Borrow instead of taking ownership for read-only parameters [Section titled “3. Borrow instead of taking ownership for read-only parameters”](#3-borrow-instead-of-taking-ownership-for-read-only-parameters) For a parameter you only need to read, take a **borrow** (`&str`, `&[T]`) rather than an owned `String` or `Vec`. This avoids forcing the caller to give up (or clone) their data. This connects directly to [Section 05: Ownership](/05-ownership/). ```rust 1 // Reads the string without taking ownership 2 fn word_count(text: &str) -> usize { 3 text.split_whitespace().count() 4 } ``` ### 4. Keep signatures honest [Section titled “4. Keep signatures honest”](#4-keep-signatures-honest) The signature is documentation the compiler enforces. Prefer precise types (`u32` for a count that can’t be negative, `f64` for money-free math) over a single catch-all, just as you would prefer precise TypeScript types over `any`. ### 5. Let `()` be implicit [Section titled “5. Let () be implicit”](#5-let--be-implicit) Do not write `-> ()`. Omitting the return type for side-effecting functions is the convention every Rust reader expects. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small order-pricing module that uses several typed functions, tail expressions, and `if`/`match` as expressions. This is the shape of code you would find in a real billing service. ```rust 1 // Real-world example: a tiny order-pricing module. 2 3 /// A line item in a shopping cart. 4 struct LineItem { 5 name: String, 6 unit_price_cents: u32, 7 quantity: u32, 8 } 9 10 /// Returns the subtotal in cents for a single line item. 11 fn line_total_cents(item: &LineItem) -> u32 { 12 item.unit_price_cents * item.quantity 13 } 14 15 /// Applies a percentage discount (0-100) to an amount in cents, 16 /// rounding to the nearest cent. 17 fn apply_discount(amount_cents: u32, percent_off: u8) -> u32 { 18 // `if` is an expression, so we can bind its result directly. 19 let percent_off = if percent_off > 100 { 100 } else { percent_off }; 20 let kept = 100 - u32::from(percent_off); 21 (amount_cents * kept + 50) / 100 // tail expression: rounded result 22 } 23 24 /// Formats an amount of cents as a dollar string, e.g. 1299 -> "$12.99". 25 fn format_price(amount_cents: u32) -> String { 26 let dollars = amount_cents / 100; 27 let cents = amount_cents % 100; 28 format!("${dollars}.{cents:02}") 29 } 30 31 fn main() { 32 let cart = [ 33 LineItem { name: "Keyboard".to_string(), unit_price_cents: 7999, quantity: 1 }, 34 LineItem { name: "Cable".to_string(), unit_price_cents: 599, quantity: 3 }, 35 ]; 36 37 // Sum subtotals across the cart. 38 let mut subtotal = 0; 39 for item in &cart { 40 let total = line_total_cents(item); 41 println!("{:<10} x{} = {}", item.name, item.quantity, format_price(total)); 42 subtotal += total; 43 } 44 45 let total = apply_discount(subtotal, 10); 46 println!("Subtotal: {}", format_price(subtotal)); 47 println!("Total (10% off): {}", format_price(total)); 48 } ``` **Output (verified):** ```text 1 Keyboard x1 = $79.99 2 Cable x3 = $17.97 3 Subtotal: $97.96 4 Total (10% off): $88.16 ``` **What to notice:** * Each function has a precise typed signature. The compiler enforces that `quantity` is a `u32`, so a negative quantity is simply unrepresentable. * `line_total_cents` and `format_price` end in a tail expression; no `return` keyword. * `apply_discount` clamps the percentage with an `if` **expression** bound to a `let`, then returns the computed value as the tail expression. * `for item in &cart` *borrows* each item, so `cart` is still usable afterward — a preview of ownership in [Section 05](/05-ownership/). *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book – Functions](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html) * [The Rust Book – Statements and Expressions](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html#statements-and-expressions) * [Rust by Example – Functions](https://doc.rust-lang.org/rust-by-example/fn.html) * [Rust Reference – Functions](https://doc.rust-lang.org/reference/items/functions.html) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Parameters](/03-functions/01-parameters/) — why Rust has no default or rest parameters, and the idiomatic alternatives. * [Return Values](/03-functions/02-return-values/) — tail returns, early `return`, the unit type, and returning tuples. * [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/) — `|args|` syntax and the `Fn`/`FnMut`/`FnOnce` traits. * [Higher-Order Functions](/03-functions/04-higher-order/) — passing and returning closures. * [Function Pointers](/03-functions/05-function-pointers/) — the `fn` type and passing named functions. * [Section 02: Variables and Mutability](/02-basics/00-variables/) — the expression-oriented model starts here. * [Section 04: Control Flow](/04-control-flow/) — `if`, `match`, and `loop` as expressions. * [Section 05: Ownership](/05-ownership/) — why read-only parameters should borrow. * [Section 09: Generics & Traits](/09-generics-traits/) — the replacement for parameter-type inference and overloading. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Convert Celsius to Fahrenheit [Section titled “Exercise 1: Convert Celsius to Fahrenheit”](#exercise-1-convert-celsius-to-fahrenheit) **Difficulty:** Easy **Objective:** Write a function with a typed signature that returns a value via a tail expression. **Instructions:** 1. Write a function `celsius_to_fahrenheit` that takes one `f64` parameter and returns an `f64`. 2. Use the formula `c * 9 / 5 + 32`. 3. Do not use the `return` keyword — use a tail expression. 4. Call it from `main` with `100.0`, `0.0`, and `37.0` and print the results. Solution ```rust 1 fn celsius_to_fahrenheit(c: f64) -> f64 { 2 c * 9.0 / 5.0 + 32.0 3 } 4 5 fn main() { 6 println!("{}", celsius_to_fahrenheit(100.0)); // 212 7 println!("{}", celsius_to_fahrenheit(0.0)); // 32 8 println!("{}", celsius_to_fahrenheit(37.0)); // 98.6 9 } ``` **Verified output:** ```text 1 212 2 32 3 98.6 ``` Note the literals are `9.0`, `5.0`, `32.0` — using integer literals like `9` would be a type mismatch against the `f64` value `c`. Rust does not implicitly convert between numeric types. ### Exercise 2: Password strength check returning a `bool` [Section titled “Exercise 2: Password strength check returning a bool”](#exercise-2-password-strength-check-returning-a-bool) **Difficulty:** Medium **Objective:** Combine several boolean sub-checks and return the result as a tail expression. **Instructions:** 1. Write `is_strong_password(password: &str) -> bool`. 2. A password is strong when it is at least 8 characters long, contains at least one digit, and contains at least one uppercase letter. 3. Compute three booleans and combine them with `&&` as the tail expression — no `return`. 4. Test it with `"abc"`, `"password"`, and `"Password1"`. > **Tip:** `str` has handy iterator methods: `password.chars().count()`, and `password.chars().any(|c| c.is_ascii_digit())`. (Closures like `|c| ...` are detailed in [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/).) Solution ```rust 1 fn is_strong_password(password: &str) -> bool { 2 let long_enough = password.chars().count() >= 8; 3 let has_digit = password.chars().any(|c| c.is_ascii_digit()); 4 let has_upper = password.chars().any(|c| c.is_ascii_uppercase()); 5 6 // Tail expression returns the combined boolean — no `return` needed. 7 long_enough && has_digit && has_upper 8 } 9 10 fn main() { 11 println!("{}", is_strong_password("abc")); // false 12 println!("{}", is_strong_password("password")); // false 13 println!("{}", is_strong_password("Password1")); // true 14 } ``` **Verified output:** ```text 1 false 2 false 3 true ``` ### Exercise 3: Price tier with a `match` expression [Section titled “Exercise 3: Price tier with a match expression”](#exercise-3-price-tier-with-a-match-expression) **Difficulty:** Medium **Objective:** Use a `match` expression as the body of a function and return a `&'static str`. **Instructions:** 1. Write `price_tier(amount_cents: u32) -> &'static str`. 2. Return `"budget"` for `0`–`999`, `"standard"` for `1000`–`9999`, and `"premium"` for anything higher. 3. Use a `match` expression (a single expression body) rather than a chain of `if`/`else if`. 4. Test it with `500`, `2500`, and `50000`. Solution ```rust 1 /// Returns the price tier name for a given amount in cents. 2 fn price_tier(amount_cents: u32) -> &'static str { 3 // `match` is an expression; the matched arm's value becomes the result. 4 let tier = match amount_cents { 5 0..=999 => "budget", 6 1000..=9999 => "standard", 7 _ => "premium", 8 }; 9 10 tier // tail expression 11 } 12 13 fn main() { 14 println!("{}", price_tier(500)); // budget 15 println!("{}", price_tier(2500)); // standard 16 println!("{}", price_tier(50000)); // premium 17 } ``` **Verified output:** ```text 1 budget 2 standard 3 premium ``` You could shorten this further by making the `match` itself the tail expression (dropping the `let tier` binding). Both are idiomatic; the named binding can aid readability. `match` is covered fully in [Section 04: Control Flow](/04-control-flow/). *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Rust functions use `fn`, `snake_case` names, **mandatory** parameter types, and a `-> Type` return arrow. * Omitting the return type means the function returns the unit type `()`, Rust’s “nothing.” * Rust is **expression-oriented**: the final expression (no trailing semicolon) is the return value. * A stray semicolon turns a return value into `()`, producing a `mismatched types` error. * Functions are order-independent at module scope; no hoisting needed. * `return` is reserved for early exits; idiomatic final values use the tail expression. # Function Parameters > Rust has no default, optional, or rest parameters like TypeScript. Map each one to its idiomatic replacement: Option, slices, Default structs, builders, and traits. In TypeScript and JavaScript you reach for default parameters, rest parameters, and optional `?` arguments without a second thought. Rust has **none** of these features at the language level, and yet every one of those patterns has a clean, idiomatic Rust equivalent. This page maps each familiar TypeScript parameter trick to the Rust way of doing it. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Rust functions take a fixed number of typed, **positional** parameters: no defaults, no optional `?` markers, no `...rest`, and no overloading by signature. Instead, Rust expresses those needs through the type system: `Option` for “maybe a value,” slices (`&[T]`) for “zero or more,” dedicated structs (often with `Default`) and the **builder pattern** for “lots of optional knobs,” and **traits** for “the same operation on different argument types.” Once you internalize this, function signatures become a precise, self-documenting contract. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) Here is a typical TypeScript module that leans on every flexible-parameter feature the language offers: notifications.ts ```typescript 1 // 1. Default parameter 2 function greet(name: string, greeting: string = "Hello"): string { 3 return `${greeting}, ${name}!`; 4 } 5 6 // 2. Optional parameter (`?`) 7 function connect(host: string, port?: number): void { 8 const resolvedPort = port ?? 8080; 9 console.log(`Connecting to ${host}:${resolvedPort}`); 10 } 11 12 // 3. Rest parameters — "zero or more" 13 function sumAll(...numbers: number[]): number { 14 return numbers.reduce((acc, n) => acc + n, 0); 15 } 16 17 // 4. Options object — a big bag of optional settings 18 interface EmailOptions { 19 cc?: string[]; 20 replyTo?: string; 21 priority?: "low" | "normal" | "high"; 22 } 23 24 function sendEmail( 25 to: string[], 26 subject: string, 27 body: string, 28 options: EmailOptions = {}, 29 ): void { 30 const priority = options.priority ?? "normal"; 31 console.log(`To: ${to.join(", ")} | ${subject} [${priority}]`); 32 } 33 34 greet("Ada"); // "Hello, Ada!" 35 greet("Ada", "Welcome"); // "Welcome, Ada!" 36 connect("localhost"); // port defaults to 8080 37 sumAll(1, 2, 3, 4, 5); // 15 38 sendEmail(["ada@example.com"], "Hi", "Body"); // options optional ``` **What this relies on:** * A **default parameter** (`greeting = "Hello"`). * An **optional parameter** (`port?`) plus the nullish-coalescing operator `??`. * **Rest parameters** (`...numbers`) gathering a variable number of arguments. * An **options object** so the caller only sets the fields they care about. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) Rust has no syntax for any of those. Each one maps to a deliberate, type-driven pattern instead: ```rust 1 // 1. Default parameter -> Option + unwrap_or 2 fn greet(name: &str, greeting: Option<&str>) -> String { 3 let greeting = greeting.unwrap_or("Hello"); 4 format!("{greeting}, {name}!") 5 } 6 7 // 2. Optional parameter -> Option (same idea) 8 fn connect(host: &str, port: Option) { 9 let port = port.unwrap_or(8080); 10 println!("Connecting to {host}:{port}"); 11 } 12 13 // 3. Rest parameters -> a slice &[T] 14 fn sum_all(numbers: &[i32]) -> i32 { 15 numbers.iter().sum() 16 } 17 18 // 4. Options object -> a struct with Default 19 #[derive(Debug, Default)] 20 struct EmailOptions { 21 cc: Vec, 22 reply_to: Option, 23 priority: Priority, 24 } 25 26 #[derive(Debug, Default)] 27 enum Priority { 28 Low, 29 #[default] 30 Normal, 31 High, 32 } 33 34 fn send_email(to: &[&str], subject: &str, body: &str, options: EmailOptions) { 35 let _ = body; 36 println!("To: {} | {subject} [{:?}]", to.join(", "), options.priority); 37 } 38 39 fn main() { 40 println!("{}", greet("Ada", None)); // "Hello, Ada!" 41 println!("{}", greet("Ada", Some("Welcome")));// "Welcome, Ada!" 42 43 connect("localhost", None); // port defaults to 8080 44 45 println!("{}", sum_all(&[1, 2, 3, 4, 5])); // 15 46 47 send_email( 48 &["ada@example.com"], 49 "Hi", 50 "Body", 51 EmailOptions::default(), // all options at their defaults 52 ); 53 } ``` **Verified output:** ```text 1 Hello, Ada! 2 Welcome, Ada! 3 Connecting to localhost:8080 4 15 5 To: ada@example.com | Hi [Normal] ``` > **Note:** Rust forces the caller to *say something* for every parameter, even if that something is `None` or `Default::default()`. This verbosity is the price of having no hidden “magic” defaults; in exchange, a function’s full set of inputs is always visible at the call site. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Default and optional parameters become `Option` [Section titled “Default and optional parameters become Option\”](#default-and-optional-parameters-become-optiont) In TypeScript, `port?: number` and `port: number = 8080` both let the caller omit the argument. Rust has no “omit,” so the absence is encoded *in the type* with `Option`. The caller passes `None` to mean “not provided” and `Some(value)` otherwise, and the function decides the fallback: ```rust 1 fn connect(retries: Option, timeout: Option) { 2 // Match handles the present/absent cases explicitly... 3 let retries = match retries { 4 Some(n) => n, 5 None => 3, 6 }; 7 // ...or use a combinator for the common "default value" case. 8 let timeout = timeout.unwrap_or(30); 9 println!("retries={retries}, timeout={timeout}"); 10 } 11 12 fn main() { 13 connect(None, None); // retries=3, timeout=30 14 connect(Some(5), Some(60)); // retries=5, timeout=60 15 connect(Some(0), None); // retries=0, timeout=30 16 } ``` This runs and prints exactly: ```text 1 retries=3, timeout=30 2 retries=5, timeout=60 3 retries=0, timeout=30 ``` Two things worth noticing: * `unwrap_or(30)` is the closest thing to TypeScript’s `port ?? 8080`. (We cover `Option` and its combinators in depth in [Error Handling](/08-error-handling/).) * `Some(0)` is meaningfully different from `None` — the third call keeps `retries = 0`. In JavaScript, `port ?? 8080` correctly preserves `0` too (because `0 != null`), but a sloppy `port || 8080` would wrongly replace `0` with `8080`. Rust’s `Option` has **no** such footgun: only `None` triggers the default. ### Rest parameters become slices [Section titled “Rest parameters become slices”](#rest-parameters-become-slices) TypeScript’s `...numbers: number[]` collects trailing arguments into an array. Rust passes a **slice** — a borrowed view `&[T]` over contiguous elements — so any array or `Vec` works without copying: ```rust 1 fn sum_all(numbers: &[i32]) -> i32 { 2 numbers.iter().sum() 3 } 4 5 fn main() { 6 println!("{}", sum_all(&[1, 2, 3, 4, 5])); // array literal -> 15 7 let v = vec![10, 20, 30]; 8 println!("{}", sum_all(&v)); // &Vec coerces to &[i32] -> 60 9 let arr = [7; 3]; 10 println!("{}", sum_all(&arr)); // [7, 7, 7] -> 21 11 } ``` The trade-off is at the call site: instead of `sumAll(1, 2, 3)` you write `sum_all(&[1, 2, 3])`. You give up the comma-separated ergonomics but gain a single, explicit, allocation-free parameter. Slices are central to Rust; we explore them more in [Collections](/07-collections/). ### Options objects become structs with `Default` [Section titled “Options objects become structs with Default”](#options-objects-become-structs-with-default) When a function has many optional settings, bundle them into a struct and derive `Default`. The caller constructs the struct, overriding only the fields they care about and filling the rest with **struct update syntax** (`..Default::default()`): ```rust 1 #[derive(Debug)] 2 struct ServerConfig { 3 host: String, 4 port: u16, 5 max_connections: u32, 6 tls: bool, 7 } 8 9 impl Default for ServerConfig { 10 fn default() -> Self { 11 ServerConfig { 12 host: "127.0.0.1".to_string(), 13 port: 8080, 14 max_connections: 100, 15 tls: false, 16 } 17 } 18 } 19 20 fn start_server(config: ServerConfig) { 21 println!( 22 "Listening on {}:{} (max={}, tls={})", 23 config.host, config.port, config.max_connections, config.tls 24 ); 25 } 26 27 fn main() { 28 // Override just two fields, keep the rest at their defaults. 29 start_server(ServerConfig { 30 port: 3000, 31 tls: true, 32 ..Default::default() 33 }); 34 35 start_server(ServerConfig::default()); 36 } ``` Output: ```text 1 Listening on 127.0.0.1:3000 (max=100, tls=true) 2 Listening on 127.0.0.1:8080 (max=100, tls=false) ``` The `..Default::default()` line is the Rust analog of spreading defaults into an options object (`{ ...defaults, port: 3000 }`). Here we wrote `Default` by hand to show non-trivial defaults; when every field’s default is also *its type’s* default (empty `Vec`, `None`, `0`, `false`), you can simply `#[derive(Default)]` instead, which is exactly what the `EmailOptions` struct above does. > **Tip:** `#[default]` on an enum variant (as on `Priority::Normal`) lets enums participate in `#[derive(Default)]`. This has been stable since Rust 1.62. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript / JavaScript | Rust | | ------------------------- | --------------------------------- | ---------------------------------------------------------- | | Default value | `function f(x = 10)` | `Option` + `unwrap_or(10)`, or a `Default` struct field | | Optional argument | `function f(x?: number)` | `Option` parameter; caller passes `None` | | Nullish fallback | `x ?? fallback` | `opt.unwrap_or(fallback)` / `unwrap_or_else(..)` | | Variable arity (“rest”) | `...args: T[]` | `&[T]` slice (or `Vec` to take ownership) | | Options bag | optional object literal | `struct` + `#[derive(Default)]` + `..Default::default()` | | Many-step construction | options object | **builder pattern** (chained methods returning `Self`) | | Overload by argument type | declaration merging / union types | **trait** bound (`fn log(x: T)`) | | Named arguments | object destructuring `{ a, b }` | not supported; use a struct | | True variadics | `...args` | `macro_rules!` (e.g. `println!`), not functions | The unifying idea: **TypeScript bends the call syntax; Rust bends the type.** Optionality, variadicity, and overloading are all encoded into a parameter’s *type* rather than into special calling conventions. That keeps every Rust function a plain, monomorphic, statically-dispatched target with one obvious signature. ### ”Overloading” via traits [Section titled “”Overloading” via traits”](#overloading-via-traits) Rust has no function overloading: you cannot declare `fn log(x: i32)` and `fn log(x: &str)` in the same scope. The idiomatic substitute is a **trait** that both types implement, plus one generic function: ```rust 1 trait Describe { 2 fn describe(&self) -> String; 3 } 4 5 impl Describe for i32 { 6 fn describe(&self) -> String { 7 format!("the integer {self}") 8 } 9 } 10 11 impl Describe for &str { 12 fn describe(&self) -> String { 13 format!("the string {self:?}") 14 } 15 } 16 17 impl Describe for bool { 18 fn describe(&self) -> String { 19 format!("the boolean {self}") 20 } 21 } 22 23 // One generic function accepts any type that implements Describe. 24 fn log(value: T) { 25 println!("Logging {}", value.describe()); 26 } 27 28 fn main() { 29 log(42); 30 log("hello"); 31 log(true); 32 } ``` Output: ```text 1 Logging the integer 42 2 Logging the string "hello" 3 Logging the boolean true ``` Unlike TypeScript overloads (which are erased at runtime and resolved purely by the type checker), Rust **monomorphizes** `log`: the compiler generates a separate specialized copy for each concrete `T` actually used. Traits and generics get a full treatment in [Generics & Traits](/09-generics-traits/). ### Accepting “either type” with `impl Into` [Section titled “Accepting “either type” with impl Into\”](#accepting-either-type-with-impl-intot) A lighter-weight form of polymorphic parameter is `impl Trait` in argument position. The classic case is “accept either a `&str` or an owned `String`”: ```rust 1 // `impl Into` accepts anything convertible into a String. 2 fn add_tag(tags: &mut Vec, tag: impl Into) { 3 tags.push(tag.into()); 4 } 5 6 fn main() { 7 let mut tags: Vec = Vec::new(); 8 add_tag(&mut tags, "rust"); // &str 9 add_tag(&mut tags, String::from("web")); // String 10 println!("{tags:?}"); // ["rust", "web"] 11 } ``` `tag: impl Into` is shorthand for a generic `>` parameter. It is a common ergonomic choice in library APIs because the caller never has to think about whether they’re holding a borrowed or owned string. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Expecting default parameters to exist [Section titled “Pitfall 1: Expecting default parameters to exist”](#pitfall-1-expecting-default-parameters-to-exist) A TypeScript developer’s muscle memory says “I’ll just leave that argument off.” Rust rejects the call: ```rust 1 fn greet(name: &str, greeting: &str) -> String { 2 format!("{greeting}, {name}!") 3 } 4 5 fn main() { 6 println!("{}", greet("Ada")); // missing the second argument 7 } ``` Real compiler error: ```text 1 error[E0061]: this function takes 2 arguments but 1 argument was supplied 2 --> src/main.rs:6:20 3 | 4 6 | println!("{}", greet("Ada")); 5 | ^^^^^------- argument #2 of type `&str` is missing 6 | 7 note: function defined here 8 --> src/main.rs:1:4 9 | 10 1 | fn greet(name: &str, greeting: &str) -> String { 11 | ^^^^^ -------------- 12 help: provide the argument 13 | 14 6 | println!("{}", greet("Ada", /* &str */)); 15 | ++++++++++++ ``` The fix is to design the parameter as `Option<&str>` (and pass `None`) or to give the caller an overload-free default via a wrapper function. ### Pitfall 2: Writing a default value in the signature [Section titled “Pitfall 2: Writing a default value in the signature”](#pitfall-2-writing-a-default-value-in-the-signature) Trying to port `greeting: &str = "Hello"` directly does not even parse: ```rust 1 fn greet(name: &str, greeting: &str = "Hello") -> String { 2 format!("{greeting}, {name}!") 3 } ``` Real compiler error (first of several): ```text 1 error: expected parameter name, found `=` 2 --> src/main.rs:1:37 3 | 4 1 | fn greet(name: &str, greeting: &str = "Hello") -> String { 5 | ^ expected parameter name ``` There is no syntax for default argument values in Rust. Reach for `Option`, a `Default` struct, or a builder instead. ### Pitfall 3: Forgetting to borrow when passing a `Vec` to a slice parameter [Section titled “Pitfall 3: Forgetting to borrow when passing a Vec to a slice parameter”](#pitfall-3-forgetting-to-borrow-when-passing-a-vec-to-a-slice-parameter) A slice parameter (`&[T]`) needs a *reference*. Passing a `Vec` by value is a type mismatch: ```rust 1 fn sum_all(numbers: &[i32]) -> i32 { 2 numbers.iter().sum() 3 } 4 5 fn main() { 6 let v = vec![1, 2, 3]; 7 println!("{}", sum_all(v)); // forgot the & 8 } ``` Real compiler error: ```text 1 error[E0308]: mismatched types 2 --> src/main.rs:7:28 3 | 4 7 | println!("{}", sum_all(v)); // forgot the & 5 | ------- ^ expected `&[i32]`, found `Vec<{integer}>` 6 | | 7 | arguments to this function are incorrect 8 | 9 = note: expected reference `&[i32]` 10 found struct `Vec<{integer}>` 11 note: function defined here 12 --> src/main.rs:1:4 13 | 14 1 | fn sum_all(numbers: &[i32]) -> i32 { 15 | ^^^^^^^ --------------- 16 help: consider borrowing here 17 | 18 7 | println!("{}", sum_all(&v)); // forgot the & 19 | + ``` The compiler even suggests the fix: add `&`. Rust will *deref-coerce* `&Vec` to `&[i32]`, but it will not insert the `&` for you. (Why borrowing matters is the subject of [Ownership](/05-ownership/).) ### Pitfall 4: Treating `None` and `Some(0)` the same [Section titled “Pitfall 4: Treating None and Some(0) the same”](#pitfall-4-treating-none-and-some0-the-same) Because `Option` is a real enum, a present-but-zero value is distinct from an absent value, exactly the distinction JavaScript’s `||` operator loses. Decide up front whether you want `unwrap_or(default)` (only `None` falls back) or genuinely want to coalesce zero too. The former is almost always correct and is one reason `Option` is safer than nullable numbers. *** ## Best Practices [Section titled “Best Practices”](#best-practices) * **Use `Option` for one or two optional parameters.** It is the lightest tool and reads clearly at the call site (`f(x, None)`). * **Prefer `unwrap_or` / `unwrap_or_else` / `unwrap_or_default`** over a hand-written `match` when you just need a fallback value. Use `unwrap_or_else` when the default is expensive to compute, so it is only built when actually needed. * **Reach for a `struct` with `#[derive(Default)]`** once a function grows three or more optional settings. Name the struct after the operation (`EmailOptions`, `ServerConfig`); it doubles as documentation. * **Use the builder pattern** when construction is multi-step, has interdependent fields, or benefits from validation in `build()`. Builders read fluently and keep required-vs-optional fields obvious. * **Use slices `&[T]`** for “borrow zero-or-more” and `Vec` only when the function must *own* the elements. A `&[T]` parameter is strictly more flexible: it accepts arrays, `Vec`s, and sub-slices alike. * **Use `impl Into` (or `impl AsRef`)** in public APIs that take strings, so callers can pass either `&str` or `String` without ceremony. * **Model overloading with a trait**, not with several same-named functions (which Rust forbids anyway). One generic function plus per-type `impl` blocks is the idiom. * **Avoid the temptation to overuse `impl Trait` everywhere**: concrete types keep error messages and signatures simpler. Add abstraction only where callers genuinely need the flexibility. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small email-sending API that combines several techniques: required fields are positional parameters, recipients use a slice (the “rest params” stand-in), the subject accepts both `&str` and `String` via `impl Into`, and the grab-bag of optional settings lives in a `Default` struct. ```rust 1 #[derive(Debug, Default)] 2 struct EmailOptions { 3 cc: Vec, 4 reply_to: Option, 5 priority: Priority, 6 } 7 8 #[derive(Debug, Default)] 9 enum Priority { 10 Low, 11 #[default] 12 Normal, 13 High, 14 } 15 16 /// Send an email. 17 /// 18 /// `to` is one-or-more recipients (slice = "rest" parameter). 19 /// `subject` accepts either a `&str` or an owned `String`. 20 /// `options` bundles everything optional, defaulting via `EmailOptions::default()`. 21 fn send_email(to: &[&str], subject: impl Into, body: &str, options: EmailOptions) { 22 let subject = subject.into(); 23 println!("To: {}", to.join(", ")); 24 println!("Subject: {subject}"); 25 if let Some(reply_to) = &options.reply_to { 26 println!("Reply-To: {reply_to}"); 27 } 28 if !options.cc.is_empty() { 29 println!("Cc: {}", options.cc.join(", ")); 30 } 31 println!("Priority: {:?}", options.priority); 32 println!("Body: {body}"); 33 } 34 35 fn main() { 36 // Minimal call: defaults for everything optional. 37 send_email( 38 &["ada@example.com"], 39 "Welcome!", 40 "Thanks for signing up.", 41 EmailOptions::default(), 42 ); 43 44 println!("---"); 45 46 // Full call: multiple recipients + overridden options. 47 send_email( 48 &["ada@example.com", "grace@example.com"], 49 String::from("Deploy finished"), 50 "Build #128 is live.", 51 EmailOptions { 52 cc: vec!["ops@example.com".to_string()], 53 priority: Priority::High, 54 reply_to: Some("noreply@example.com".to_string()), 55 }, 56 ); 57 } ``` Verified output: ```text 1 To: ada@example.com 2 Subject: Welcome! 3 Priority: Normal 4 Body: Thanks for signing up. 5 --- 6 To: ada@example.com, grace@example.com 7 Subject: Deploy finished 8 Reply-To: noreply@example.com 9 Cc: ops@example.com 10 Priority: High 11 Body: Build #128 is live. ``` > **Note:** For long-lived or interdependent configuration (say, an `HttpRequest` with method, headers, timeout, and retries), promote `EmailOptions` to a full builder so each setting gets its own validating method. See Exercise 3 for a worked builder. ### What about *true* variadics? [Section titled “What about true variadics?”](#what-about-true-variadics) If you genuinely want `println!`-style “any number of arguments of mixed types,” that is the job of **macros**, not functions. `macro_rules!` can match a repetition of expressions: ```rust 1 // A variadic sum macro (one or more arguments). 2 macro_rules! sum { 3 ($($x:expr),+ $(,)?) => {{ 4 let mut total = 0; 5 $( total += $x; )+ 6 total 7 }}; 8 } 9 10 fn main() { 11 println!("{}", sum!(1, 2, 3)); // 6 12 println!("{}", sum!(10, 20, 30, 40)); // 100 13 } ``` This compiles and prints `6` then `100`. Macros can do a lot, but they are a different tool with different rules; they are covered in [Macros](/14-macros/). For ordinary “many values of one type,” a slice is simpler and almost always the right choice. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Functions](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html) * [The Rust Book — `Option`](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html#the-option-enum-and-its-advantages-over-null-values) * [`Option::unwrap_or` (std docs)](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or) * [The `Default` trait (std docs)](https://doc.rust-lang.org/std/default/trait.Default.html) * [Rust by Example — Slices](https://doc.rust-lang.org/rust-by-example/primitives/array.html) * [Rust Design Patterns — Builder](https://rust-unofficial.github.io/patterns/patterns/creational/builder.html) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Basic Functions](/03-functions/00-basic-functions/) — `fn` definitions, typed parameters, and return types * [Return Values](/03-functions/02-return-values/) — return types, tail expressions, returning tuples * [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/) — passing behavior as a parameter * [Higher-Order Functions](/03-functions/04-higher-order/) — functions that take closures (`impl Fn`) as parameters * [Function Pointers](/03-functions/05-function-pointers/) — the `fn` type as a parameter * [Basics — Types](/02-basics/01-types/) — the parameter types you will be passing * [Generics & Traits](/09-generics-traits/) — trait bounds and `impl Trait` parameters * [Ownership](/05-ownership/) — why slice parameters are borrowed * [Control Flow](/04-control-flow/) — the next section, building on functions *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Optional parameter with a default [Section titled “Exercise 1: Optional parameter with a default”](#exercise-1-optional-parameter-with-a-default) **Difficulty:** Easy **Objective:** Translate a TypeScript default parameter into idiomatic Rust using `Option`. **Instructions:** Port the following TypeScript function to Rust. The `exponent` should default to `2` when not provided. Make `power(5, None)` return `25` and `power(2, Some(10))` return `1024`. ```typescript 1 function power(base: number, exponent: number = 2): number { 2 return base ** exponent; 3 } ``` Solution ```rust 1 fn power(base: i64, exponent: Option) -> i64 { 2 let exponent = exponent.unwrap_or(2); 3 base.pow(exponent) 4 } 5 6 fn main() { 7 assert_eq!(power(5, None), 25); 8 assert_eq!(power(2, Some(10)), 1024); 9 println!("ok"); 10 } ``` `unwrap_or(2)` supplies the default exactly when the caller passes `None`. Note the integer method `i64::pow`, which takes a `u32` exponent; there is no `**` operator in Rust. ### Exercise 2: Rest parameters as a slice [Section titled “Exercise 2: Rest parameters as a slice”](#exercise-2-rest-parameters-as-a-slice) **Difficulty:** Medium **Objective:** Replace TypeScript rest parameters with a slice parameter, and handle the empty case safely. **Instructions:** Port `average` to Rust. It must accept any number of `f64` values via a slice and return the arithmetic mean. For an empty input, return `None` instead of dividing by zero (so the signature is `fn average(values: &[f64]) -> Option`). `average(&[2.0, 4.0, 6.0])` should be `Some(4.0)`; `average(&[])` should be `None`. ```typescript 1 function average(...values: number[]): number | null { 2 if (values.length === 0) return null; 3 return values.reduce((a, b) => a + b, 0) / values.length; 4 } ``` Solution ```rust 1 fn average(values: &[f64]) -> Option { 2 if values.is_empty() { 3 return None; 4 } 5 let sum: f64 = values.iter().sum(); 6 Some(sum / values.len() as f64) 7 } 8 9 fn main() { 10 assert_eq!(average(&[2.0, 4.0, 6.0]), Some(4.0)); 11 assert_eq!(average(&[]), None); 12 println!("ok"); 13 } ``` Returning `Option` makes the “no values” case impossible to ignore: the caller must handle `None`, which is far safer than TypeScript’s `number | null`. `values.len() as f64` performs the explicit `usize`-to-`f64` cast Rust requires. ### Exercise 3: A builder for many optional settings [Section titled “Exercise 3: A builder for many optional settings”](#exercise-3-a-builder-for-many-optional-settings) **Difficulty:** Hard **Objective:** Replace an options object with the builder pattern. **Instructions:** Implement a `QueryBuilder` for a `Query { table, limit, ascending }`. `QueryBuilder::new(table)` should start with `limit = 100` and `ascending = true`. Provide chained methods `.limit(n)` and `.descending()`, and a `.build()` that returns the `Query`. The expression `QueryBuilder::new("users").limit(10).descending().build()` should equal `Query { table: "users".into(), limit: 10, ascending: false }`. Solution ```rust 1 #[derive(Debug, PartialEq)] 2 struct Query { 3 table: String, 4 limit: u32, 5 ascending: bool, 6 } 7 8 struct QueryBuilder { 9 table: String, 10 limit: u32, 11 ascending: bool, 12 } 13 14 impl QueryBuilder { 15 fn new(table: impl Into) -> Self { 16 QueryBuilder { table: table.into(), limit: 100, ascending: true } 17 } 18 19 fn limit(mut self, n: u32) -> Self { 20 self.limit = n; 21 self 22 } 23 24 fn descending(mut self) -> Self { 25 self.ascending = false; 26 self 27 } 28 29 fn build(self) -> Query { 30 Query { 31 table: self.table, 32 limit: self.limit, 33 ascending: self.ascending, 34 } 35 } 36 } 37 38 fn main() { 39 let q = QueryBuilder::new("users").limit(10).descending().build(); 40 assert_eq!( 41 q, 42 Query { table: "users".into(), limit: 10, ascending: false } 43 ); 44 println!("{q:?}"); 45 } ``` Each setter takes `mut self` by value and returns `Self`, which is what enables method chaining: ownership flows through the chain and the final `build()` consumes the builder. `impl Into` lets `new` accept both `&str` and `String`. This pattern scales to dozens of optional settings far better than a function with a dozen `Option` parameters. # Return Values > Rust functions return their final expression with no return keyword, use the unit type instead of void, and hand back multiple values as tuples, unlike TypeScript. How a function hands a result back to its caller looks superficially similar in Rust and TypeScript, but the mechanics are different. Rust functions return the value of their final **expression** (usually with no `return` keyword at all), and a function that returns “nothing” actually returns a real value called the **unit type**. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) In Rust, the last expression in a function body is its return value (no `return`, no semicolon). The `return` keyword exists but is reserved for **early returns**. Functions that produce no meaningful value return `()` (the **unit type**), and when you need to hand back several values at once you return a **tuple** rather than mutating out-parameters. **In short:** A semicolon turns an expression into a statement that evaluates to `()`. Whether your function returns a useful value or `()` often comes down to whether that last semicolon is present. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // Explicit `return` on every path is the norm in TS/JS. 2 function square(n: number): number { 3 return n * n; 4 } 5 6 // A "void" function still returns something: `undefined`. 7 function logMessage(msg: string): void { 8 console.log(`[log] ${msg}`); 9 } 10 11 // Early return (guard clause) to avoid deep nesting. 12 function classify(score: number): string { 13 if (score < 0) return "invalid"; 14 if (score >= 90) return "excellent"; 15 if (score >= 60) return "passing"; 16 return "failing"; 17 } 18 19 // Returning "multiple values" means returning an array or an object, 20 // then destructuring at the call site. 21 function minMax(values: number[]): [number, number] { 22 return [Math.min(...values), Math.max(...values)]; 23 } 24 25 const [lo, hi] = minMax([7, 2, 9, 4, 1]); 26 console.log(lo, hi); // 1 9 27 28 const result = logMessage("hi"); 29 console.log(result); // undefined <-- void really means `undefined` ``` **Key points:** * Every value-producing path needs an explicit `return`. * A `void` function implicitly returns `undefined`. * “Multiple return values” are an array (positional) or object (named) plus destructuring. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 // The last expression IS the return value: no `return`, no semicolon. 2 fn square(n: i32) -> i32 { 3 n * n 4 } 5 6 // No `-> Type` means the function returns `()`, the unit type. 7 fn log_message(msg: &str) { 8 println!("[log] {msg}"); 9 } 10 11 // `return` is kept for EARLY returns (guard clauses). 12 fn classify(score: i32) -> &'static str { 13 if score < 0 { 14 return "invalid"; 15 } 16 if score >= 90 { 17 return "excellent"; 18 } 19 if score >= 60 { 20 return "passing"; 21 } 22 "failing" // final value: tail expression, no semicolon 23 } 24 25 // Multiple values come back as a tuple. 26 fn min_max(values: &[i32]) -> (i32, i32) { 27 let mut min = values[0]; 28 let mut max = values[0]; 29 for &v in &values[1..] { 30 if v < min { 31 min = v; 32 } 33 if v > max { 34 max = v; 35 } 36 } 37 (min, max) 38 } 39 40 fn main() { 41 println!("{}", square(5)); // 25 42 log_message("hi"); 43 44 let (lo, hi) = min_max(&[7, 2, 9, 4, 1]); 45 println!("min={lo}, max={hi}"); // min=1, max=9 46 } ``` Running the full version of this program prints: ```plaintext 1 25 2 [log] hi 3 min=1, max=9 ``` **Key points:** * The **tail expression** (last expression, no semicolon) is the return value. * Omitting `-> Type` is the same as `-> ()`. * `return` is for leaving early, not for the normal final result. * A **tuple** is the idiomatic way to return several values. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### The tail expression is the return value [Section titled “The tail expression is the return value”](#the-tail-expression-is-the-return-value) Rust is an **expression-oriented** language. A function body is a block, and a block evaluates to its final expression, provided that expression has no trailing semicolon. ```rust 1 fn square(n: i32) -> i32 { 2 n * n // tail expression: this is what the function returns 3 } ``` The `-> i32` after the parameter list is the **return type annotation** (TypeScript writes it after a colon: `(n: number): number`). The body’s last expression must produce that type. You *can* write `return`, and it compiles: ```rust 1 fn square_explicit(n: i32) -> i32 { 2 return n * n; // legal, but not idiomatic 3 } ``` But in idiomatic Rust the `return` keyword is reserved for leaving a function *before* the end. Using it on the last line is so unidiomatic that Clippy flags it (see [Common Pitfalls](#common-pitfalls)). > **Tip:** “No semicolon means return” is the single most useful rule to internalize from this page. If you find yourself writing `return x;` as the very last line, delete the `return` and the `;`. ### Statements vs. expressions: the role of the semicolon [Section titled “Statements vs. expressions: the role of the semicolon”](#statements-vs-expressions-the-role-of-the-semicolon) This is where TypeScript intuition can mislead you. In Rust: * An **expression** evaluates to a value (`n * n`, `if c { a } else { b }`, a block `{ ... }`). * A **statement** performs an action and evaluates to `()` (a `let` binding, or *any expression followed by a semicolon*). Adding a semicolon to the tail expression discards its value and substitutes `()`: ```rust 1 fn square(n: i32) -> i32 { 2 n * n; // BUG: now this is a statement; the function "falls off the end" with () 3 } ``` That mismatch (`()` where `i32` is expected) is a compile error, covered below. The fix is to remove the semicolon. > **Note:** This statement/expression distinction was introduced in [Section 02 — Basics](/02-basics/). Functions are where it bites hardest, so it is worth re-reading if it feels shaky. See also [Basic Functions and Signatures](/03-functions/00-basic-functions/) for the statement-vs-expression fundamentals. ### `if` (and `match`, and blocks) are expressions [Section titled “if (and match, and blocks) are expressions”](#if-and-match-and-blocks-are-expressions) Because `if` is an expression, you can use it directly as the tail expression; no need for a temporary mutable variable: ```rust 1 fn abs_diff(a: i32, b: i32) -> i32 { 2 if a > b { 3 a - b 4 } else { 5 b - a 6 } 7 } ``` Every branch must produce the same type, and there must be an `else` (otherwise the value of the `if` would be `()` when the condition is false). A plain block is also an expression: ```rust 1 fn block_expr_demo() -> i32 { 2 let y = { 3 let a = 3; 4 let b = 4; 5 a * a + b * b // tail expression of the inner block -> 25 6 }; 7 y 8 } ``` Even `loop` is an expression: `break value` makes the whole `loop` evaluate to `value`. ```rust 1 fn first_power_over(limit: u32) -> u32 { 2 let mut n = 1; 3 loop { 4 n *= 2; 5 if n > limit { 6 break n; // the value of the whole `loop` expression 7 } 8 } 9 } ``` > Control-flow-as-expression is explored fully in [Section 04 — Control Flow](/04-control-flow/). ### The unit type `()` [Section titled “The unit type ()”](#the-unit-type-) When a function has no `-> Type`, its return type is `()`, pronounced “unit”. Unit is a real type with exactly one value, also written `()`. These two signatures are identical: ```rust 1 fn log_message(msg: &str) { 2 println!("[log] {msg}"); 3 } 4 5 // Exactly the same thing, written explicitly: 6 fn log_message_explicit(msg: &str) -> () { 7 println!("[log] {msg}"); 8 } ``` `()` is the closest Rust analogue to TypeScript’s `void`, but they differ in an important way: * TypeScript’s `void` is a *type-system marker*; at runtime such functions return the value `undefined`. * Rust’s `()` is a genuine zero-size value. It has no runtime representation (it takes up zero bytes), yet you can store it, pass it, and pattern-match on it like any other value. You almost never write `-> ()` explicitly; idiomatic Rust just omits the return type. ### Early return with `return` [Section titled “Early return with return”](#early-return-with-return) Guard clauses translate directly. Use `return value;` (with a semicolon — it is a statement) to leave early, and let the final value be a tail expression: ```rust 1 fn classify(score: i32) -> &'static str { 2 if score < 0 { 3 return "invalid"; // early return 4 } 5 if score >= 90 { 6 return "excellent"; 7 } 8 if score >= 60 { 9 return "passing"; 10 } 11 "failing" // tail expression for the remaining case 12 } ``` Note the asymmetry that trips people up: early `return` statements end with a semicolon, but the final tail expression does **not**. They are different syntactic roles, not an inconsistency. ### Returning multiple values: tuples [Section titled “Returning multiple values: tuples”](#returning-multiple-values-tuples) JavaScript reaches for an array (`[a, b]`) or object (`{ a, b }`); Rust returns a **tuple**, then you destructure it: ```rust 1 fn min_max(values: &[i32]) -> (i32, i32) { 2 // ... 3 (min, max) 4 } 5 6 // Destructure at the call site: 7 let (lo, hi) = min_max(&[7, 2, 9, 4, 1]); 8 9 // Or access positionally if you didn't destructure: 10 let pair = min_max(&[7, 2, 9, 4, 1]); 11 println!("{} {}", pair.0, pair.1); ``` Tuple fields are accessed by position (`.0`, `.1`, …), and the type `(i32, i32)` is checked at compile time: there is no risk of reading `pair[2]` and getting `undefined`. Tuples are statically sized and typed, unlike a JS array. Once you have more than two or three fields, or the positions stop being self-explanatory, prefer a named **struct**, the Rust analogue of returning an object with named keys: ```rust 1 struct Stats { 2 min: i32, 3 max: i32, 4 sum: i32, 5 } 6 7 fn stats(values: &[i32]) -> Stats { 8 let mut s = Stats { min: values[0], max: values[0], sum: 0 }; 9 for &v in values { 10 if v < s.min { s.min = v; } 11 if v > s.max { s.max = v; } 12 s.sum += v; 13 } 14 s 15 } ``` (Structs get full treatment in [Section 06 — Data Structures](/06-data-structures/).) ### Bonus: the never type `!` [Section titled “Bonus: the never type !”](#bonus-the-never-type-) A function that *never returns normally* (it loops forever or always panics) has the special return type `!`, the **never type**. `!` coerces to any type, which is why a panicking branch can sit alongside an `i32` branch: ```rust 1 fn bail(msg: &str) -> ! { 2 panic!("fatal: {msg}"); 3 } 4 5 fn get_config(present: bool) -> i32 { 6 if present { 7 42 8 } else { 9 bail("config missing"); // type `!` coerces to i32 10 } 11 } ``` TypeScript has a direct counterpart: the `never` type, used for functions that always throw or loop forever. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript/JavaScript | Rust | | ----------------------------- | --------------------------------------------- | ------------------------------------------ | | Normal return | `return value;` on every path | Tail expression (no `return`, no `;`) | | `return` keyword | The only way to return a value | Reserved for **early** returns | | ”No value” function | `void` → returns `undefined` at runtime | `()` (unit), a zero-size real value | | Return type position | After params: `(x: number): number` | After params with arrow: `(x: i32) -> i32` | | Multiple values | Array `[a, b]` or object `{ a, b }` | Tuple `(a, b)` or a named `struct` | | Index access on returned pair | `arr[0]`, may be `undefined` if out of bounds | `tuple.0`, checked at compile time | | Block produces a value | No (needs IIFE or explicit `return`) | Yes — a block is an expression | | Never-returns marker | `never` | `!` (the never type) | ### Why does Rust make the last expression the return value? [Section titled “Why does Rust make the last expression the return value?”](#why-does-rust-make-the-last-expression-the-return-value) It is a consequence of being expression-oriented. Once `if`, `match`, blocks, and `loop` all produce values, “the function returns its body’s value” falls out naturally and removes a whole category of mistakes (forgetting `return` on one branch). TypeScript can flag missing returns with `noImplicitReturns`, but it remains an opt-in lint; in Rust the type system enforces it structurally: every path must produce the declared type or it does not compile. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Accidental semicolon on the tail expression [Section titled “Pitfall 1: Accidental semicolon on the tail expression”](#pitfall-1-accidental-semicolon-on-the-tail-expression) **Problem:** ```rust 1 fn square(n: i32) -> i32 { 2 n * n; // BUG: trailing semicolon makes this a statement, returns () 3 } ``` **Real compiler error:** ```plaintext 1 error[E0308]: mismatched types 2 --> src/main.rs:1:22 3 | 4 1 | fn square(n: i32) -> i32 { 5 | ------ ^^^ expected `i32`, found `()` 6 | | 7 | implicitly returns `()` as its body has no tail or `return` expression 8 2 | n * n; // BUG: trailing semicolon makes this a statement, not the return value 9 | - help: remove this semicolon to return this value 10 11 For more information about this error, try `rustc --explain E0308`. ``` **Solution:** Remove the semicolon. The compiler even points at it (`help: remove this semicolon to return this value`). ```rust 1 fn square(n: i32) -> i32 { 2 n * n 3 } ``` ### Pitfall 2: Forgetting the return type annotation [Section titled “Pitfall 2: Forgetting the return type annotation”](#pitfall-2-forgetting-the-return-type-annotation) Rust does **not** infer the return type of a top-level function from its body (unlike closures, and unlike TypeScript inferring `: number`). If the body produces a value, you must declare the type. **Problem:** ```rust 1 fn add(a: i32, b: i32) { 2 a + b 3 } ``` **Real compiler error:** ```plaintext 1 error[E0308]: mismatched types 2 --> src/main.rs:3:5 3 | 4 2 | fn add(a: i32, b: i32) { 5 | - help: try adding a return type: `-> i32` 6 3 | a + b 7 | ^^^^^ expected `()`, found `i32` 8 9 For more information about this error, try `rustc --explain E0308`. ``` The compiler assumed `-> ()` because none was given, then found an `i32`. **Solution:** add `-> i32`. ```rust 1 fn add(a: i32, b: i32) -> i32 { 2 a + b 3 } ``` ### Pitfall 3: Mismatched `if`/`else` branch types [Section titled “Pitfall 3: Mismatched if/else branch types”](#pitfall-3-mismatched-ifelse-branch-types) Because `if` is an expression used as the return value, every branch must yield the **same** type. TypeScript would happily widen to a union (`number | string`); Rust will not. **Problem:** ```rust 1 fn describe(n: i32) -> i32 { 2 if n > 0 { 3 n 4 } else { 5 "negative or zero" // wrong type! 6 } 7 } ``` **Real compiler error:** ```plaintext 1 error[E0308]: mismatched types 2 --> src/main.rs:6:9 3 | 4 2 | fn describe(n: i32) -> i32 { 5 | --- expected `i32` because of return type 6 ... 7 6 | "negative or zero" // wrong type! 8 | ^^^^^^^^^^^^^^^^^^ expected `i32`, found `&str` 9 10 For more information about this error, try `rustc --explain E0308`. ``` **Solution:** make both arms the same type (e.g. return a `&str` describing the number, or an `enum`). If you genuinely need “either a number or a message,” model it with an `enum` or `Result`; see [Section 08 — Error Handling](/08-error-handling/). ### Pitfall 4: Writing `return` as the last line [Section titled “Pitfall 4: Writing return as the last line”](#pitfall-4-writing-return-as-the-last-line) It compiles and runs, but it is not idiomatic, and Clippy will tell you so. **Code:** ```rust 1 fn square(n: i32) -> i32 { 2 return n * n; 3 } ``` **Real `cargo clippy` warning:** ```plaintext 1 warning: unneeded `return` statement 2 --> src/main.rs:2:5 3 | 4 2 | return n * n; // clippy will flag this needless return 5 | ^^^^^^^^^^^^ 6 | 7 = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return 8 = note: `#[warn(clippy::needless_return)]` on by default 9 help: remove `return` ``` **Solution:** drop the `return` and the trailing semicolon, leaving the bare tail expression `n * n`. ### Pitfall 5: Expecting `void`/`()` to behave like `undefined` [Section titled “Pitfall 5: Expecting void/() to behave like undefined”](#pitfall-5-expecting-void-to-behave-like-undefined) In TypeScript you might write `const r = doThing();` and then check `if (r === undefined)`. In Rust, a function returning `()` gives you a value you cannot meaningfully test; there is exactly one `()`. Trying to print or compare it is almost always a sign you wanted a real return type (often `Option` or `Result`). If a function “might not produce a value,” return `Option`, not `()`. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### Prefer tail expressions over `return` [Section titled “Prefer tail expressions over return”](#prefer-tail-expressions-over-return) ```rust 1 // Idiomatic 2 fn fahrenheit(c: f64) -> f64 { 3 c * 9.0 / 5.0 + 32.0 4 } 5 6 // Not idiomatic (Clippy: needless_return) 7 fn fahrenheit_verbose(c: f64) -> f64 { 8 return c * 9.0 / 5.0 + 32.0; 9 } ``` ### Use `return` for genuine early exits / guard clauses [Section titled “Use return for genuine early exits / guard clauses”](#use-return-for-genuine-early-exits--guard-clauses) Guard clauses keep the happy path un-nested and are the canonical reason to reach for `return`: ```rust 1 fn paginate(total_items: u32, page: u32, per_page: u32) -> (u32, u32) { 2 if per_page == 0 { 3 return (0, 0); // guard clause 4 } 5 let offset = page.saturating_sub(1) * per_page; 6 (offset, per_page) 7 } ``` ### Return tuples for 2-3 unnamed values; use a struct beyond that [Section titled “Return tuples for 2-3 unnamed values; use a struct beyond that”](#return-tuples-for-2-3-unnamed-values-use-a-struct-beyond-that) A `(width, height)` tuple is clear. A `(u32, u32, u32, bool, String)` tuple is not: give the fields names with a struct. When absence is possible, return `Option<(A, B)>` rather than a sentinel value like `(-1, -1)`. ### Let the type system enforce totality [Section titled “Let the type system enforce totality”](#let-the-type-system-enforce-totality) Don’t disable return checking. Every path producing the declared type is a feature: it is impossible to forget a branch, the way you can in JavaScript without `noImplicitReturns`. ### Don’t annotate `-> ()` [Section titled “Don’t annotate -> ()”](#dont-annotate---) Omit the return type for unit-returning functions; `fn f() {}` is preferred over `fn f() -> () {}`. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small slice of an API layer: computing pagination metadata (early returns + a tuple result) and parsing a `key=value` line (early return + `Option` of a tuple). ```rust 1 /// Compute pagination metadata for a list endpoint. 2 /// 3 /// Returns `(offset, limit, total_pages)`. Uses early returns to clamp 4 /// invalid input instead of nesting the happy path inside `if` blocks. 5 fn paginate(total_items: u32, page: u32, per_page: u32) -> (u32, u32, u32) { 6 // Guard clause: a zero page size has no sensible offset/limit. 7 if per_page == 0 { 8 return (0, 0, 0); 9 } 10 11 // Pages are 1-based for the caller; treat 0 as "first page". 12 let page = if page == 0 { 1 } else { page }; 13 14 let total_pages = total_items.div_ceil(per_page); 15 let offset = (page - 1) * per_page; 16 let limit = per_page; 17 18 (offset, limit, total_pages) 19 } 20 21 /// Parse a `key=value` configuration line. 22 /// 23 /// Returns `None` for malformed input — the absence is modeled with 24 /// `Option`, not a sentinel string. The `?` propagates the `None` from 25 /// `split_once` as an early return. 26 fn parse_kv(input: &str) -> Option<(String, String)> { 27 let (key, value) = input.split_once('=')?; 28 let key = key.trim(); 29 let value = value.trim(); 30 if key.is_empty() { 31 return None; // explicit early return for the empty-key case 32 } 33 Some((key.to_string(), value.to_string())) 34 } 35 36 fn main() { 37 let (offset, limit, pages) = paginate(95, 3, 20); 38 println!("offset={offset}, limit={limit}, total_pages={pages}"); 39 40 let (offset, limit, pages) = paginate(95, 0, 0); 41 println!("offset={offset}, limit={limit}, total_pages={pages}"); 42 43 println!("{:?}", parse_kv(" host = localhost ")); 44 println!("{:?}", parse_kv("=oops")); 45 println!("{:?}", parse_kv("no-equals")); 46 } ``` **Output:** ```plaintext 1 offset=40, limit=20, total_pages=5 2 offset=0, limit=0, total_pages=0 3 Some(("host", "localhost")) 4 None 5 None ``` Notice three return-value techniques working together: a **tuple** for `(offset, limit, total_pages)`, **early returns** for guard clauses, and `?` — which is itself an early return that yields `None` from a function returning `Option`. The `?` operator is covered in depth in [Section 08 — Error Handling](/08-error-handling/). > **Note:** `u32::div_ceil` (ceiling division) is a stable standard-library method, handy for computing page counts without floating point. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Functions](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html): statements, expressions, and return values * [Rust Reference — Functions](https://doc.rust-lang.org/reference/items/functions.html): the formal definition of function items and return types * [Rust Reference — The `()` type](https://doc.rust-lang.org/std/primitive.unit.html): the unit type * [Rust Reference — The `!` type](https://doc.rust-lang.org/std/primitive.never.html): the never type * [Clippy: `needless_return`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_return): why `return` on the last line is flagged ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Basic Functions](/03-functions/00-basic-functions/): `fn` signatures, statements vs. expressions * [Parameters](/03-functions/01-parameters/): passing data in (the counterpart to this page) * [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/): closures *do* infer their return type * [Higher-Order Functions](/03-functions/04-higher-order/): returning closures with `impl Fn` / `Box` * [Recursion](/03-functions/06-recursion/): returning from recursive calls * [Section 02 — Basics](/02-basics/) — the expression/statement distinction * [Section 04 — Control Flow](/04-control-flow/) — `if`/`match`/`loop` as expressions * [Section 06 — Data Structures](/06-data-structures/) — structs for named multi-value returns * [Section 08 — Error Handling](/08-error-handling/) — `Option`/`Result` and the `?` operator *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: From explicit `return` to tail expression [Section titled “Exercise 1: From explicit return to tail expression”](#exercise-1-from-explicit-return-to-tail-expression) **Difficulty:** Beginner **Objective:** Internalize that the last expression is the return value. **Instructions:** Rewrite this function in idiomatic Rust style (no `return`, no trailing semicolon on the result). It converts Celsius to Fahrenheit. ```rust 1 fn celsius_to_fahrenheit(c: f64) -> f64 { 2 return c * 9.0 / 5.0 + 32.0; 3 } 4 5 fn main() { 6 println!("{}", celsius_to_fahrenheit(100.0)); // 212 7 } ``` Solution ```rust 1 fn celsius_to_fahrenheit(c: f64) -> f64 { 2 c * 9.0 / 5.0 + 32.0 3 } 4 5 fn main() { 6 println!("{}", celsius_to_fahrenheit(100.0)); // 212 7 } ``` Running it prints `212`. Dropping both the `return` keyword and the trailing semicolon leaves a bare tail expression, which is what the function returns. ### Exercise 2: Return multiple values with a tuple [Section titled “Exercise 2: Return multiple values with a tuple”](#exercise-2-return-multiple-values-with-a-tuple) **Difficulty:** Intermediate **Objective:** Return more than one value and destructure it at the call site. **Instructions:** Implement `divmod`, which returns both the integer quotient and the remainder of `a / b` as a tuple `(quotient, remainder)`. Destructure the result in `main` and print both parts. ```rust 1 fn divmod(a: i32, b: i32) -> (i32, i32) { 2 // TODO: return (quotient, remainder) 3 } 4 5 fn main() { 6 let (q, r) = divmod(17, 5); 7 println!("q={q}, r={r}"); // q=3, r=2 8 } ``` Solution ```rust 1 fn divmod(a: i32, b: i32) -> (i32, i32) { 2 (a / b, a % b) // tuple tail expression 3 } 4 5 fn main() { 6 let (q, r) = divmod(17, 5); 7 println!("q={q}, r={r}"); // q=3, r=2 8 } ``` Running it prints `q=3, r=2`. The tuple `(a / b, a % b)` is the function’s value, and `let (q, r) = ...` destructures it. ### Exercise 3: Early return + `Option` of a tuple [Section titled “Exercise 3: Early return + Option of a tuple”](#exercise-3-early-return--option-of-a-tuple) **Difficulty:** Advanced **Objective:** Combine guard clauses, the `?` operator, and a tuple return modeled with `Option`. **Instructions:** Implement `parse_kv` so it parses a `key=value` string into `Some((key, value))` with both parts trimmed. Return `None` if there is no `=` **or** if the key is empty after trimming. Use `str::split_once` and the `?` operator. ```rust 1 fn parse_kv(input: &str) -> Option<(String, String)> { 2 // TODO: split on '=', trim both sides, reject empty keys 3 } 4 5 fn main() { 6 println!("{:?}", parse_kv(" host = localhost ")); // Some(("host", "localhost")) 7 println!("{:?}", parse_kv("=oops")); // None 8 println!("{:?}", parse_kv("no-equals")); // None 9 } ``` Solution ```rust 1 fn parse_kv(input: &str) -> Option<(String, String)> { 2 let (key, value) = input.split_once('=')?; // `?` returns None early if no '=' 3 let key = key.trim(); 4 let value = value.trim(); 5 if key.is_empty() { 6 return None; // explicit early return 7 } 8 Some((key.to_string(), value.to_string())) 9 } 10 11 fn main() { 12 println!("{:?}", parse_kv(" host = localhost ")); 13 println!("{:?}", parse_kv("=oops")); 14 println!("{:?}", parse_kv("no-equals")); 15 } ``` Output: ```plaintext 1 Some(("host", "localhost")) 2 None 3 None ``` The `?` after `split_once` is an early return that yields `None` when there is no `=`; the explicit `return None` handles the empty-key case; and `Some((..., ...))` is the tuple tail expression for the success path. *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * A function returns its **tail expression**: last line, no semicolon, no `return`. * A trailing semicolon turns that expression into a statement of type `()`. * `return` is reserved for **early** exits (guard clauses, `?`). * Functions with no `-> Type` return `()`, the unit type (Rust’s `void`-like value). * Return several values as a **tuple** (or a named **struct** when the fields deserve names). * Every code path must produce the declared type, enforced by the compiler. **Key syntax:** ```rust 1 fn f(x: i32) -> i32 { x + 1 } // tail expression 2 fn g() { /* ... */ } // returns () 3 fn h(n: i32) -> i32 { // early return + tail expression 4 if n < 0 { return 0; } 5 n * 2 6 } 7 fn pair() -> (i32, i32) { (1, 2) } // tuple of multiple values ``` # Arrow Functions vs Closures > Rust closures are JavaScript arrow functions with |args| syntax, but the compiler tracks how they capture state via the Fn, FnMut, and FnOnce traits. If there is one Rust feature that feels immediately *familiar* to a TypeScript/JavaScript developer, it is the **closure**. Rust closures are the direct counterpart of JavaScript arrow functions: small, anonymous functions that capture variables from the surrounding scope. The syntax is different (`|args|` instead of `(args) =>`), and Rust’s ownership system adds rules that JavaScript never enforces, but the mental model transfers almost perfectly. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) A **closure** is an anonymous function that can **capture** values from the scope in which it is defined. In TypeScript/JavaScript you write these as arrow functions (`(x) => x * 2`); in Rust you write them with vertical-bar parameter lists (`|x| x * 2`). The big new idea is *how* a closure captures its environment: Rust tracks whether a closure reads, mutates, or takes ownership of captured values, and encodes that in three traits: `Fn`, `FnMut`, and `FnOnce`. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // A "counter factory" — a classic use of closures in JavaScript. 2 // The returned arrow function closes over the `count` variable. 3 function makeCounter(): () => number { 4 let count = 0; 5 return () => { 6 count += 1; // mutates a captured variable 7 return count; 8 }; 9 } 10 11 const next = makeCounter(); 12 console.log(next()); // 1 13 console.log(next()); // 2 14 console.log(next()); // 3 15 16 // Arrow functions capturing a value from the enclosing scope: 17 const factor = 3; 18 const scale = (n: number) => n * factor; // closes over `factor` 19 console.log(scale(5)); // 15 20 21 // Closures are also the everyday argument to array methods: 22 const numbers = [1, 2, 3, 4, 5, 6]; 23 const threshold = 3; 24 const big = numbers.filter((n) => n > threshold); // closes over `threshold` 25 console.log(big); // [4, 5, 6] ``` **Key points:** * Arrow functions can read **and** mutate variables from the enclosing scope. * Capture is implicit and always by reference to the variable. * There is no notion of “ownership”; the garbage collector keeps captured values alive as long as the closure exists. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 fn main() { 2 // The same closures, written the Rust way. 3 let double = |n: i32| n * 2; 4 println!("{}", double(10)); // 20 5 6 let add = |a: i32, b: i32| a + b; 7 println!("{}", add(3, 4)); // 7 8 9 // A multi-line body uses braces, just like a block expression: 10 let describe = |n: i32| { 11 let parity = if n % 2 == 0 { "even" } else { "odd" }; 12 format!("{n} is {parity}") 13 }; 14 println!("{}", describe(7)); // 7 is odd 15 16 // A zero-argument closure has empty bars: 17 let greet = || println!("Hello from a closure!"); 18 greet(); 19 20 // Capturing a value from the environment (by reference): 21 let factor = 3; 22 let scale = |n: i32| n * factor; // borrows `factor` 23 println!("{}", scale(5)); // 15 24 println!("factor still usable: {factor}"); // 3 25 26 // Capturing and MUTATING a value (an FnMut closure): 27 let mut count = 0; 28 let mut increment = || { 29 count += 1; 30 count 31 }; 32 println!("{}", increment()); // 1 33 println!("{}", increment()); // 2 34 35 // Taking OWNERSHIP of a captured value with `move` (an FnOnce closure here): 36 let name = String::from("Ada"); 37 let consume = move || name.to_uppercase(); // `name` was moved in 38 println!("{}", consume()); // ADA 39 } ``` **Output:** ```plaintext 1 20 2 7 3 7 is odd 4 Hello from a closure! 5 15 6 factor still usable: 3 7 1 8 2 9 ADA ``` **Key points:** * Parameters go between `|bars|`; the body is a single expression or a `{ block }`. * Parameter and return types are usually *inferred* — you rarely annotate them. * How a closure captures (`borrow`, `mutable borrow`, or `move`) is decided by the compiler based on what the body does, and you can force a move with the `move` keyword. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Syntax: `|args|` is the new `(args) =>` [Section titled “Syntax: |args| is the new (args) =>”](#syntax-args-is-the-new-args-) The translation is mechanical: | TypeScript/JavaScript | Rust | | --------------------------- | ------------------ | | `(n) => n * 2` | `\|n\| n * 2` | | `(a, b) => a + b` | `\|a, b\| a + b` | | `() => doThing()` | `\|\| do_thing()` | | `(n) => { ...; return x; }` | `\|n\| { ...; x }` | In Rust the body after `|args|` is an expression. A single expression needs no braces (`|n| n * 2`). For multiple statements you use a `{ }` block, and (as everywhere in Rust) the final expression *without* a semicolon is the return value. (See [Statements vs expressions](/03-functions/00-basic-functions/) and [return values](/03-functions/02-return-values/) for the full story.) > **Note:** Closure parameter types are usually inferred, unlike a top-level `fn`, which *must* annotate every parameter. When inference needs help you can annotate: `|n: i32| -> i32 { n * 2 }`. The `-> ReturnType` is only allowed when the body is a braced block. ### How capture works: the three traits [Section titled “How capture works: the three traits”](#how-capture-works-the-three-traits) This is the part with no JavaScript equivalent. When a closure uses a variable from its surroundings, the compiler picks the *least invasive* way to capture it, and that choice determines which of three **traits** the closure implements: * **`Fn`**: the closure captures values **by immutable reference** (`&T`). It only reads them, so it can be called many times and even from several places at once. * **`FnMut`**: the closure captures **by mutable reference** (`&mut T`). It changes captured state, so it can be called many times but needs a `mut` binding. * **`FnOnce`**: the closure captures **by value** (it takes ownership / moves things in). It may only be guaranteed to be callable **once**, because calling it might consume the captured values. These form a hierarchy: every `Fn` is also an `FnMut`, and every `FnMut` is also an `FnOnce`. (Anything you can call repeatedly, you can certainly call once.) When you *accept* a closure as a parameter, ask for the **weakest** trait that works: `FnOnce` if you call it once, `FnMut` if you call it repeatedly and it mutates, `Fn` if it only reads. ```rust 1 // Reads its environment only — call it as many times as you like. 2 fn apply_twice i32>(f: F, x: i32) -> i32 { 3 f(f(x)) 4 } 5 6 // Mutates state across calls — note `mut f`. 7 fn repeat(mut f: F, times: u32) { 8 for _ in 0..times { 9 f(); 10 } 11 } 12 13 // May consume captured values — called exactly once. 14 fn call_once String>(f: F) -> String { 15 f() 16 } 17 18 fn main() { 19 println!("{}", apply_twice(|n| n + 3, 10)); // 16 20 21 let mut total = 0; 22 repeat(|| total += 1, 5); 23 println!("{total}"); // 5 24 25 let greeting = String::from("hi"); 26 let msg = call_once(move || greeting + " there"); // moves `greeting` in 27 println!("{msg}"); // hi there 28 } ``` **Output:** ```plaintext 1 16 2 5 3 hi there ``` The compiler chose the trait for each closure automatically: `|n| n + 3` only reads, so it is `Fn`; `|| total += 1` mutates `total`, so it is `FnMut`; `move || greeting + " there"` consumes `greeting` (the `+` operator on `String` takes ownership of the left side), so it is only `FnOnce`. ### Capture by reference is the default [Section titled “Capture by reference is the default”](#capture-by-reference-is-the-default) In the earlier `scale` example, `|n: i32| n * factor` only *reads* `factor`, so the closure borrows it immutably. That is why `factor` is still usable on the next line: the closure holds a shared reference, not the value itself. This is exactly like JavaScript closing over a `const`, except Rust’s borrow checker enforces that nobody mutates `factor` while the closure’s borrow is alive. ### `move`: forcing capture by value [Section titled “move: forcing capture by value”](#move-forcing-capture-by-value) The `move` keyword forces a closure to take **ownership** of everything it captures, instead of borrowing. You need it whenever the closure must outlive the scope that created the captured values, most importantly when spawning threads or returning a closure from a function: ```rust 1 use std::thread; 2 3 fn main() { 4 let data = vec![1, 2, 3]; 5 6 // `move` transfers ownership of `data` into the new thread, so the 7 // closure can safely outlive `main`'s stack frame. 8 let handle = thread::spawn(move || { 9 let sum: i32 = data.iter().sum(); 10 println!("sum in thread: {sum}"); 11 }); 12 13 handle.join().unwrap(); 14 } ``` **Output:** ```plaintext 1 sum in thread: 6 ``` Without `move`, the closure would try to *borrow* `data`, but the new thread might run after `main` returns and frees `data`: a use-after-free that the borrow checker refuses to allow. The `move` keyword resolves this by handing ownership of `data` to the closure. > **Tip:** `move` does not automatically make a closure `FnOnce`. A `move` closure that only *reads* its captured values (or captures only `Copy` types) is still `Fn`. `move` controls *how* values are captured (by value vs by reference); the `Fn`/`FnMut`/`FnOnce` traits describe what *calling* the closure does to those captured values. For `Copy` types (like integers), `move` copies the value in, leaving the original usable: ```rust 1 fn main() { 2 let x = 10; // i32 is Copy 3 let add_x = move |n: i32| n + x; // copies x into the closure 4 println!("{}", add_x(5)); // 15 5 println!("x still usable: {x}"); // 10 — Copy types are duplicated, not moved away 6 } ``` **Output:** ```plaintext 1 15 2 10 ``` *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript/JavaScript | Rust | | ------------------------- | --------------------------------------- | ---------------------------------------------------------------- | | Syntax | `(a, b) => a + b` | `\|a, b\| a + b` | | Parameter types | Optional annotations; erased at runtime | Usually inferred; can annotate `\|n: i32\|` | | How capture is decided | Always by reference to the variable | Compiler picks `&`, `&mut`, or by-value (override with `move`) | | Mutating captured state | Always allowed | Closure must be `FnMut` and bound with `let mut` | | Lifetime of captured data | GC keeps it alive forever if needed | Borrow checker enforces captured borrows do not outlive the data | | Calling repeatedly | Always allowed | `Fn`/`FnMut` yes; `FnOnce` only guaranteed once | | Each closure’s type | Structural: same signature = same type | Every closure has a unique, anonymous, compiler-generated type | ### Every closure has its own unique type [Section titled “Every closure has its own unique type”](#every-closure-has-its-own-unique-type) In TypeScript, two arrow functions with the same signature share the type `(n: number) => number`. In Rust, **each closure has its own anonymous type** generated by the compiler — even two closures with identical signatures are different types. This is why functions that take closures use generics (`F: Fn(...)`) or trait objects (`Box`), a topic covered in detail in [Higher-Order Functions](/03-functions/04-higher-order/) and [Function Pointers](/03-functions/05-function-pointers/). ### Closures vs. plain functions [Section titled “Closures vs. plain functions”](#closures-vs-plain-functions) A Rust closure can capture its environment; a named `fn` cannot. That captured environment is stored inline in the closure value, so closures are *not* the same as a `fn` pointer. The relationship between the two, and when a closure can coerce to a function pointer, is the subject of [Function Pointers](/03-functions/05-function-pointers/). *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: A closure’s parameter type is locked in at first use [Section titled “Pitfall 1: A closure’s parameter type is locked in at first use”](#pitfall-1-a-closures-parameter-type-is-locked-in-at-first-use) Because closure types are inferred, the compiler fixes the parameter and return types the **first time** the closure is called (or otherwise constrained). Calling it later with a different type is an error; there is no implicit polymorphism like a JavaScript function would have. ```rust 1 fn main() { 2 let identity = |x| x; 3 let a = identity(5); 4 let b = identity("hello"); // ← different type! 5 println!("{a} {b}"); 6 } ``` **Real compiler error:** ```plaintext 1 error[E0308]: mismatched types 2 --> src/main.rs:4:22 3 | 4 4 | let b = identity("hello"); 5 | -------- ^^^^^^^ expected integer, found `&str` 6 | | 7 | arguments to this function are incorrect 8 | 9 note: expected because the closure was earlier called with an argument of type `{integer}` 10 --> src/main.rs:3:22 11 | 12 3 | let a = identity(5); 13 | -------- ^ expected because this argument is of type `{integer}` 14 | | 15 | in this closure call 16 note: closure parameter defined here 17 --> src/main.rs:2:21 18 | 19 2 | let identity = |x| x; 20 | ^ ``` If you genuinely need “one function, many types,” that is what *generics* are for — use a generic `fn` (see [Generics & Traits](/09-generics-traits/)), not a closure. ### Pitfall 2: Using a value after a `move` closure took it [Section titled “Pitfall 2: Using a value after a move closure took it”](#pitfall-2-using-a-value-after-a-move-closure-took-it) A `move` closure that captures a non-`Copy` value (like a `String` or `Vec`) takes ownership. The original variable is then gone: ```rust 1 fn main() { 2 let name = String::from("Ada"); 3 let consume = move || println!("{name}"); 4 consume(); 5 println!("{name}"); // ← `name` was moved into the closure 6 } ``` **Real compiler error:** ```plaintext 1 error[E0382]: borrow of moved value: `name` 2 --> src/main.rs:5:16 3 | 4 2 | let name = String::from("Ada"); 5 | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait 6 3 | let consume = move || println!("{name}"); 7 | ------- ---- variable moved due to use in closure 8 | | 9 | value moved into closure here 10 4 | consume(); 11 5 | println!("{name}"); // ← `name` was moved into the closure 12 | ^^^^ value borrowed here after move 13 | 14 = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) 15 help: consider cloning the value before moving it into the closure 16 | 17 3 ~ let value = name.clone(); 18 4 ~ let consume = move || println!("{value}"); 19 | ``` As the compiler suggests, clone the value first if you need both copies, or simply do not use `move` if a borrow is sufficient. This is ownership in action; see [Ownership](/05-ownership/) for the full model. ### Pitfall 3: Forgetting `let mut` for an `FnMut` closure [Section titled “Pitfall 3: Forgetting let mut for an FnMut closure”](#pitfall-3-forgetting-let-mut-for-an-fnmut-closure) A closure that mutates captured state must itself be stored in a `mut` binding, because *calling* it mutably borrows the closure: ```rust 1 fn main() { 2 let mut count = 0; 3 let increment = || { 4 count += 1; 5 }; 6 increment(); // ← needs a mutable binding 7 println!("{count}"); 8 } ``` **Real compiler error:** ```plaintext 1 error[E0596]: cannot borrow `increment` as mutable, as it is not declared as mutable 2 --> src/main.rs:6:5 3 | 4 4 | count += 1; 5 | ----- calling `increment` requires mutable binding due to mutable borrow of `count` 6 5 | }; 7 6 | increment(); // ← needs a mutable binding 8 | ^^^^^^^^^ cannot borrow as mutable 9 | 10 help: consider changing this to be mutable 11 | 12 3 | let mut increment = || { 13 | +++ ``` The fix is exactly what the compiler says: `let mut increment = || { ... };`. This trips up TypeScript/JavaScript developers because in JavaScript a mutating closure is indistinguishable from a non-mutating one: there is no `mut` to forget. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Let types be inferred [Section titled “1. Let types be inferred”](#1-let-types-be-inferred) Idiomatic Rust closures are terse. Prefer `|n| n * 2` over `|n: i32| -> i32 { n * 2 }` unless the compiler cannot infer the types or an annotation genuinely aids readability. ### 2. Only use `move` when you need it [Section titled “2. Only use move when you need it”](#2-only-use-move-when-you-need-it) Reach for `move` when the closure must outlive its defining scope: spawning threads, returning a closure, or storing one in a struct or `async` task. For ordinary inline use with iterator adapters, the default borrowing capture is usually what you want and avoids unnecessary clones. ### 3. Accept the weakest trait that works [Section titled “3. Accept the weakest trait that works”](#3-accept-the-weakest-trait-that-works) When writing a function that takes a closure, choose `FnOnce` if you call it once, `FnMut` if it mutates and you call it repeatedly, and `Fn` if it only reads. Asking for less makes your function callable with more closures. (Details and the `impl Fn` / `Box` return patterns live in [Higher-Order Functions](/03-functions/04-higher-order/).) ### 4. Use closures with iterator adapters [Section titled “4. Use closures with iterator adapters”](#4-use-closures-with-iterator-adapters) The single most common place you will write closures is in iterator chains: `map`, `filter`, `fold`, and friends. This replaces the `Array.prototype` methods you know from JavaScript: ```rust 1 fn main() { 2 let numbers = vec![1, 2, 3, 4, 5, 6]; 3 let threshold = 3; 4 // `into_iter` consumes the vec; the closure captures `threshold` by reference. 5 let big: Vec = numbers.into_iter().filter(|&n| n > threshold).collect(); 6 println!("{big:?}"); // [4, 5, 6] 7 } ``` **Output:** ```plaintext 1 [4, 5, 6] ``` > **Tip:** `|&n|` here is *pattern matching* in the parameter position: `filter` hands the closure a `&i32`, and `&n` destructures it so `n` is a plain `i32`. Without it you would write `|n| *n > threshold`. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A retry helper is a production-flavored use of an `FnMut` closure: real retries often mutate state between attempts (bumping a counter, rotating an endpoint). This mirrors a hand-rolled `retry` utility you might write in TypeScript. **TypeScript:** ```typescript 1 function retry(operation: () => T, maxAttempts: number): T { 2 let lastErr: unknown; 3 for (let i = 0; i < maxAttempts; i++) { 4 try { 5 return operation(); 6 } catch (e) { 7 lastErr = e; 8 } 9 } 10 throw lastErr; 11 } 12 13 let attempt = 0; 14 const result = retry(() => { 15 attempt += 1; // the closure mutates captured state 16 if (attempt < 3) throw new Error(`attempt ${attempt} failed`); 17 return `succeeded on attempt ${attempt}`; 18 }, 5); 19 20 console.log(result); // "succeeded on attempt 3" ``` **Rust:** ```rust 1 /// Runs `operation` up to `max_attempts` times, stopping at the first `Ok`. 2 /// The operation is `FnMut` because real-world retries often mutate state 3 /// (bump an attempt counter, rotate an endpoint, etc.). 4 fn retry(mut operation: F, max_attempts: u32) -> Result 5 where 6 F: FnMut() -> Result, 7 { 8 let mut last_err = None; 9 for _ in 0..max_attempts { 10 match operation() { 11 Ok(value) => return Ok(value), 12 Err(e) => last_err = Some(e), 13 } 14 } 15 Err(last_err.expect("max_attempts must be > 0")) 16 } 17 18 fn main() { 19 // Simulate a flaky service that fails twice, then succeeds. 20 let mut attempt = 0; 21 let result = retry( 22 || { 23 attempt += 1; // the closure mutates captured state -> FnMut 24 if attempt < 3 { 25 Err(format!("attempt {attempt} failed")) 26 } else { 27 Ok(format!("succeeded on attempt {attempt}")) 28 } 29 }, 30 5, 31 ); 32 33 match result { 34 Ok(msg) => println!("OK: {msg}"), 35 Err(e) => println!("gave up: {e}"), 36 } 37 } ``` **Output:** ```plaintext 1 OK: succeeded on attempt 3 ``` Notice the contrasts with the TypeScript version. Rust uses `Result` instead of exceptions (no `try`/`catch`), the closure parameter is constrained with `F: FnMut() -> Result` in a `where` clause, and `operation` is declared `mut` so it can be called repeatedly while mutating `attempt`. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Closures: Anonymous Functions that Capture Their Environment](https://doc.rust-lang.org/book/ch13-01-closures.html) * [Rust by Example — Closures](https://doc.rust-lang.org/rust-by-example/fn/closures.html) * [Rust Reference — Closure expressions](https://doc.rust-lang.org/reference/expressions/closure-expr.html) * [`std::ops::Fn`](https://doc.rust-lang.org/std/ops/trait.Fn.html), [`FnMut`](https://doc.rust-lang.org/std/ops/trait.FnMut.html), [`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) ### Related Topics in This Guide [Section titled “Related Topics in This Guide”](#related-topics-in-this-guide) * [Basic Functions](/03-functions/00-basic-functions/) — `fn` definitions, signatures, statements vs expressions * [Function Parameters](/03-functions/01-parameters/) — Rust’s alternatives to default and rest parameters * [Return Values](/03-functions/02-return-values/) — tail expressions, the unit type, returning tuples * [Higher-Order Functions](/03-functions/04-higher-order/) — taking and returning closures (`impl Fn`, `Box`); `map`/`filter`/`fold` * [Function Pointers](/03-functions/05-function-pointers/) — the `fn` type, named functions, closures vs function items * [Ownership](/05-ownership/) — the model behind `move` and capture * [Variables and Mutability](/02-basics/00-variables/) — why `let mut` matters for `FnMut` * [Control Flow](/04-control-flow/) — the iterator chains where closures live *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Translate the arrow functions [Section titled “Exercise 1: Translate the arrow functions”](#exercise-1-translate-the-arrow-functions) **Difficulty:** Easy **Objective:** Get comfortable with `|args|` syntax and capture-by-reference. **Instructions:** Rewrite this TypeScript snippet in Rust. Keep `threshold` as a captured variable; do not hardcode it into the closure. ```typescript 1 const numbers = [1, 2, 3, 4, 5, 6]; 2 const threshold = 3; 3 const big = numbers.filter((n) => n > threshold); 4 console.log(big); // [4, 5, 6] ``` Solution ```rust 1 fn main() { 2 let numbers = vec![1, 2, 3, 4, 5, 6]; 3 let threshold = 3; 4 let big: Vec = numbers.into_iter().filter(|&n| n > threshold).collect(); 5 println!("{big:?}"); // [4, 5, 6] 6 } ``` The closure `|&n| n > threshold` captures `threshold` by immutable reference (so the closure is `Fn`) and uses a `&n` pattern to destructure the `&i32` that `filter` provides. ### Exercise 2: A counter factory [Section titled “Exercise 2: A counter factory”](#exercise-2-a-counter-factory) **Difficulty:** Medium **Objective:** Return a closure that owns and mutates its own state — the Rust version of JavaScript’s `makeCounter`. **Instructions:** Implement `make_counter` so that it returns a closure which yields `1`, then `2`, then `3` on successive calls. The closure must own its count (it outlives `make_counter`), so you will need `move` and an `FnMut` return type. ```rust 1 fn make_counter() -> /* ??? */ { 2 // ... 3 } 4 5 fn main() { 6 let mut counter = make_counter(); 7 println!("{}", counter()); // 1 8 println!("{}", counter()); // 2 9 println!("{}", counter()); // 3 10 } ``` Solution ```rust 1 // `impl FnMut() -> u32` means "some concrete type that implements FnMut". 2 fn make_counter() -> impl FnMut() -> u32 { 3 let mut count = 0; 4 move || { 5 count += 1; 6 count 7 } 8 } 9 10 fn main() { 11 let mut counter = make_counter(); 12 println!("{}", counter()); // 1 13 println!("{}", counter()); // 2 14 println!("{}", counter()); // 3 15 } ``` **Output:** ```plaintext 1 1 2 2 3 3 ``` `move` is required because `count` is local to `make_counter` and must travel out with the returned closure. The binding in `main` must be `let mut counter`, because calling an `FnMut` closure mutably borrows it. Returning closures with `impl Fn` is explored further in [Higher-Order Functions](/03-functions/04-higher-order/). ### Exercise 3: Consume a captured value once [Section titled “Exercise 3: Consume a captured value once”](#exercise-3-consume-a-captured-value-once) **Difficulty:** Medium **Objective:** Build intuition for `FnOnce` and `move` capturing an owned `String`. **Instructions:** Write a function `run_once` that accepts a closure returning a `String` and calls it a single time, returning its result. Then call it with a `move` closure that consumes a captured `String` by concatenating to it. Concatenating with `+` on a `String` takes ownership of the left operand, which forces the closure to be `FnOnce`. ```rust 1 fn run_once(f: F) -> String { 2 // ... 3 } 4 5 fn main() { 6 let banner = String::from("DEPLOYING"); 7 // Build a closure that produces "DEPLOYING... done" and pass it to run_once. 8 } ``` Solution ```rust 1 fn run_once String>(f: F) -> String { 2 f() 3 } 4 5 fn main() { 6 let banner = String::from("DEPLOYING"); 7 let job = move || format!("{banner}... done"); // captures `banner` by value 8 let report = run_once(job); 9 println!("{report}"); // DEPLOYING... done 10 } ``` **Output:** ```plaintext 1 DEPLOYING... done ``` Because `run_once` only ever calls `f` once, `FnOnce` is the correct (weakest) bound — it accepts closures that consume their captured values, as well as any `Fn`/`FnMut` closure. This is the best-practice “ask for the weakest trait” rule in action. *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Rust closures are arrow functions with `|args|` syntax instead of `(args) =>`. * Parameter and return types are usually inferred; bodies are expressions or `{ }` blocks. * Closures capture their environment by `&`, `&mut`, or by value — the compiler picks the least invasive option. * `Fn` reads, `FnMut` mutates, `FnOnce` consumes; they form a hierarchy, and you accept the weakest one that works. * `move` forces capture by value, needed when a closure must outlive its scope (threads, returned closures). * Every closure has a unique anonymous type — unlike TypeScript, where any two functions with the same structural signature share a type. **The mental model:** A closure is a small struct holding its captured variables plus a call method. JavaScript hides that machinery and lets the garbage collector sort out lifetimes; Rust makes capture explicit and proves at compile time that no captured borrow outlives its data. # Higher-Order Functions > Rust's map, filter, and fold mirror TypeScript array methods but run lazily on iterators. Pass closures with impl Fn and return them via impl Fn or Box. Higher-order functions are functions that take other functions as parameters or return functions as results. If you write TypeScript, you already use them constantly: `array.map`, `array.filter`, `array.reduce`, event handlers, and middleware are all higher-order functions. Rust has the same toolkit, but the type system makes the relationships explicit. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) A **higher-order function** either accepts a function (or closure) as an argument, returns one, or both. In TypeScript these are everywhere and untyped at runtime; in Rust the compiler needs to know *how* a function is passed — by generic bound (`impl Fn`), or behind a pointer (`Box`) — because closures have no single fixed size or type. This matters to a TypeScript/JavaScript developer because the iterator methods you reach for daily (`map`, `filter`, `reduce`) exist in Rust too, just lazier and with a few extra rules. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // TypeScript - higher-order functions are the bread and butter of array work 2 const numbers = [1, 2, 3, 4, 5]; 3 4 // map: transform every element 5 const doubled = numbers.map((n) => n * 2); 6 console.log(doubled); // [2, 4, 6, 8, 10] 7 8 // filter: keep elements that pass a predicate 9 const evens = numbers.filter((n) => n % 2 === 0); 10 console.log(evens); // [2, 4] 11 12 // reduce: fold everything into a single value 13 const sum = numbers.reduce((acc, n) => acc + n, 0); 14 console.log(sum); // 15 15 16 // Chaining is idiomatic 17 const sumOfOddSquares = numbers 18 .filter((n) => n % 2 === 1) 19 .map((n) => n * n) 20 .reduce((acc, n) => acc + n, 0); 21 console.log(sumOfOddSquares); // 35 22 23 // A function that RETURNS a function (a closure factory) 24 function makeAdder(n: number): (x: number) => number { 25 return (x) => x + n; 26 } 27 const add5 = makeAdder(5); 28 console.log(add5(10)); // 15 29 30 // A function that TAKES a function 31 function applyTwice(f: (x: number) => number, x: number): number { 32 return f(f(x)); 33 } 34 console.log(applyTwice((x) => x + 3, 10)); // 16 ``` **Key points:** * `map`/`filter`/`reduce` are methods on `Array`, and they run **eagerly** — each one builds a new array immediately. * A function type is just `(args) => ret`; you can store it, pass it, and return it without ceremony. * At runtime there is no difference between a closure that captures variables and one that does not — they are all just JavaScript functions (objects). *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 fn main() { 2 let numbers = vec![1, 2, 3, 4, 5]; 3 4 // map: transform every element (note: .iter() then .collect()) 5 let doubled: Vec = numbers.iter().map(|n| n * 2).collect(); 6 println!("{:?}", doubled); // [2, 4, 6, 8, 10] 7 8 // filter: keep elements that pass a predicate 9 let evens: Vec = numbers.iter().filter(|&&n| n % 2 == 0).copied().collect(); 10 println!("{:?}", evens); // [2, 4] 11 12 // reduce -> fold (with an explicit initial accumulator) 13 let sum: i32 = numbers.iter().fold(0, |acc, &n| acc + n); 14 println!("{}", sum); // 15 15 16 // Chaining is idiomatic here too 17 let sum_of_odd_squares: i32 = numbers 18 .iter() 19 .filter(|&&n| n % 2 == 1) 20 .map(|&n| n * n) 21 .sum(); 22 println!("{}", sum_of_odd_squares); // 35 23 } 24 25 // A function that RETURNS a closure (impl Fn) 26 fn make_adder(n: i32) -> impl Fn(i32) -> i32 { 27 move |x| x + n 28 } 29 30 // A function that TAKES a closure (generic over F: Fn) 31 fn apply_twice i32>(f: F, x: i32) -> i32 { 32 f(f(x)) 33 } ``` Calling those last two: ```rust 1 fn main() { 2 let add5 = make_adder(5); 3 println!("{}", add5(10)); // 15 4 5 println!("{}", apply_twice(|x| x + 3, 10)); // 16 6 } ``` **Key points:** * The iterator adapters (`map`, `filter`) are **lazy**: they build a description of work, and nothing happens until a *consumer* like `collect`, `sum`, or `fold` drives them. * `reduce` in JavaScript with an initial value maps to Rust’s `fold`. (Rust also has a `reduce` method, but it has no seed and returns an `Option`.) * Passing a closure uses a **generic bound** (`F: Fn(i32) -> i32`); returning one uses `impl Fn(...) -> ...` or, when the concrete type can vary, `Box ...>`. > **Note:** `Fn`, `FnMut`, and `FnOnce` are the three closure traits. They are covered in depth in [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/); here we focus on *using* them in higher-order signatures. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Why `.iter()` and `.collect()`? [Section titled “Why .iter() and .collect()?”](#why-iter-and-collect) In JavaScript, `numbers.map(...)` is a method on the array that returns a brand-new array. In Rust, `map` is a method on an **iterator**, not on the `Vec` itself. So you first turn the collection into an iterator, then adapt it, then collect the results back into a concrete collection: ```rust 1 fn main() { 2 let numbers = vec![1, 2, 3, 4, 5]; 3 let doubled: Vec = numbers.iter().map(|n| n * 2).collect(); 4 // ^^^^^^^ ^^^ ^^^^^^^ 5 // make iterator transform materialize 6 println!("{:?}", doubled); 7 } ``` The `: Vec` annotation on the left is usually required because `collect` is generic over *what* it collects into (it could build a `Vec`, a `HashSet`, a `String`, etc.). The annotation tells the compiler which one you want. This is one of the few places Rust’s inference needs a hint that TypeScript would not. > **Tip:** `iter()` borrows each element (`&i32`), `iter_mut()` borrows mutably (`&mut i32`), and `into_iter()` *consumes* the collection and yields owned values (`i32`). Choosing the right one is an ownership decision, covered in [Section 05: Ownership](/05-ownership/). ### Laziness: the big difference from JavaScript [Section titled “Laziness: the big difference from JavaScript”](#laziness-the-big-difference-from-javascript) JavaScript’s array methods are eager. Each `.map(...)` allocates a new array on the spot. Rust’s iterator adapters are **lazy**: they do nothing until consumed. This is closer to a generator pipeline than to `Array.prototype.map`. ```rust 1 fn main() { 2 let numbers = vec![1, 2, 3, 4, 5]; 3 4 // This line allocates NOTHING and runs NO closures yet: 5 let pipeline = numbers.iter().filter(|&&n| n % 2 == 1).map(|&n| n * n); 6 7 // The work happens here, when a consumer drives the iterator: 8 let total: i32 = pipeline.sum(); 9 println!("{}", total); // 35 10 } ``` The payoff is that a long chain of adapters fuses into a single pass with no intermediate `Vec` allocations: `filter().map().sum()` walks the data once. In JavaScript the same chain would allocate one array per step. ### The `|&&n|` double-reference pattern [Section titled “The |&\&n| double-reference pattern”](#the-n-double-reference-pattern) You will see odd-looking patterns like `|&&n|` in `filter`. Here is the why: `numbers.iter()` yields `&i32`. `filter`’s closure receives a *reference to the item*, so it gets `&&i32`. The pattern `|&&n|` destructures both references, binding `n` to a plain `i32` you can compare: ```rust 1 fn main() { 2 let numbers = vec![1, 2, 3, 4, 5]; 3 let evens: Vec = numbers 4 .iter() // yields &i32 5 .filter(|&&n| n % 2 == 0) // closure gets &&i32; &&n -> n: i32 6 .copied() // &i32 -> i32 7 .collect(); 8 println!("{:?}", evens); // [2, 4] 9 } ``` `map`, by contrast, receives the item by value-of-the-iterator (`&i32` here), so `|&n| n * n` destructures one layer. This referencing dance has no parallel in TypeScript, where everything is a reference under the hood and you never write it out. ### Passing a closure: `impl Fn` vs generic bound [Section titled “Passing a closure: impl Fn vs generic bound”](#passing-a-closure-impl-fn-vs-generic-bound) There are three equivalent ways to write “this function takes a closure.” All three monomorphize: the compiler stamps out a specialized copy of the function for each closure type you pass, with zero indirection at runtime (like how TypeScript generics work in your head, except TypeScript erases them and Rust keeps them). ```rust 1 // 1. Generic with an inline trait bound 2 fn apply_twice i32>(f: F, x: i32) -> i32 { 3 f(f(x)) 4 } 5 6 // 2. The same thing, using `impl Trait` in argument position (sugar for #1) 7 fn apply_twice_impl(f: impl Fn(i32) -> i32, x: i32) -> i32 { 8 f(f(x)) 9 } 10 11 // 3. The same thing again, moving the bound into a `where` clause 12 fn apply_twice_where(f: F, x: i32) -> i32 13 where 14 F: Fn(i32) -> i32, 15 { 16 f(f(x)) 17 } ``` Use `Fn` when the closure only reads captured state, `FnMut` when it mutates captured state, and `FnOnce` when it consumes captured state. Asking for the *least* demanding trait you need makes your function accept the *most* callers. ```rust 1 // FnMut: the closure mutates state it captured 2 fn call_n_times(mut f: F, n: usize) { 3 for _ in 0..n { 4 f(); 5 } 6 } 7 8 // FnOnce: the closure may consume what it captured (called at most once) 9 fn consume String>(f: F) -> String { 10 f() 11 } 12 13 fn main() { 14 let mut count = 0; 15 call_n_times(|| count += 1, 5); 16 println!("count = {}", count); // count = 5 17 18 let owned = String::from("hello"); 19 let joined = consume(move || owned + " world"); // moves `owned` in 20 println!("{}", joined); // hello world 21 } ``` > **Note:** Notice `mut f` in `call_n_times`. Calling an `FnMut` closure requires a mutable binding, so the parameter itself is declared `mut`. This is a small ownership detail TypeScript never surfaces. ### Returning a closure: `impl Fn` vs `Box` [Section titled “Returning a closure: impl Fn vs Box\”](#returning-a-closure-impl-fn-vs-boxdyn-fn) Returning a function is where Rust diverges most sharply from TypeScript, because every closure has a unique, compiler-generated, anonymous type, and you cannot name it. You have two options. **Option 1 — `impl Fn`** when there is exactly one closure shape returned. This is zero-cost (no heap allocation, no indirection): ```rust 1 fn make_adder(n: i32) -> impl Fn(i32) -> i32 { 2 move |x| x + n 3 } ``` The `move` keyword forces the closure to *take ownership* of `n` so the closure can outlive the `make_adder` call. Without it, the closure would try to borrow a local that is about to be destroyed (see Pitfall 3). **Option 2 — `Box`** when the concrete closure type can differ between branches. Because `impl Fn` means “one specific hidden type,” you cannot return two different closures from two `if`/`match` arms with it. Boxing them behind a trait object (`dyn Fn`) erases the difference, at the cost of a heap allocation and dynamic dispatch: ```rust 1 fn make_op(kind: &str) -> Box i32> { 2 match kind { 3 "double" => Box::new(|x| x * 2), 4 "negate" => Box::new(|x| -x), 5 _ => Box::new(|x| x), 6 } 7 } 8 9 fn main() { 10 let double = make_op("double"); 11 let negate = make_op("negate"); 12 println!("{} {}", double(21), negate(7)); // 42 -7 13 } ``` This `dyn` (dynamic dispatch through a pointer) is the closest analogy to how a TypeScript function value works at runtime: a pointer you call indirectly. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript/JavaScript | Rust | | ---------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------- | | `map` / `filter` | Methods on `Array`, **eager** | Methods on `Iterator`, **lazy** until consumed | | `reduce(fn, init)` | `Array.prototype.reduce` | `Iterator::fold(init, fn)` | | `reduce(fn)` (no init) | Uses the first element as the seed; throws `TypeError` on an empty array | `Iterator::reduce(fn)` returns `Option` | | Building the result | Returns a new array automatically | `.collect()` / `.sum()` / `.count()` etc. | | Taking a function | `(x) => T` param, untyped at runtime | `impl Fn` / `F: Fn` (monomorphized, zero-cost) | | Returning a function | `(): (x) => T` | `impl Fn` (one type) or `Box` (varying types) | | Capturing variables | Always by reference to the closure scope | By ref / by mut ref / by move; `Fn`/`FnMut`/`FnOnce` | | Cost | Heap-allocated function objects, GC | `impl Fn`: zero-cost; `Box`: one allocation + dynamic dispatch | The mental model shift: in TypeScript a function is *a value of one type*. In Rust, every closure has its *own* type, so “a function parameter” must be expressed as a generic bound or a trait object. There is no single `Function` type that covers them all. > **Note:** Unlike TypeScript, where `array.map` always produces a fresh array, a Rust adapter chain produces nothing until consumed and then runs in a single fused pass. If you write a `map` and never collect or otherwise consume it, the closure never runs at all (see Pitfall 1). *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Treating iterators as eager (forgetting to consume) [Section titled “Pitfall 1: Treating iterators as eager (forgetting to consume)”](#pitfall-1-treating-iterators-as-eager-forgetting-to-consume) A TypeScript developer expects `map` to *do something*. In Rust, an unconsumed adapter does nothing, and the compiler warns you: ```rust 1 fn main() { 2 let names = vec!["alice", "bob"]; 3 names.iter().map(|n| println!("Hello, {n}!")); 4 println!("done"); 5 } ``` The “Hello” lines never print. The real compiler warning: ```plaintext 1 warning: unused `Map` that must be used 2 --> src/main.rs:3:5 3 | 4 3 | names.iter().map(|n| println!("Hello, {n}!")); 5 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | 7 = note: iterators are lazy and do nothing unless consumed 8 = note: `#[warn(unused_must_use)]` on by default 9 help: use `let _ = ...` to ignore the resulting value 10 | 11 3 | let _ = names.iter().map(|n| println!("Hello, {n}!")); 12 | +++++++ 13 14 warning: `Iterator::map` call that discard the iterator's values 15 --> src/main.rs:3:18 16 | 17 3 | names.iter().map(|n| println!("Hello, {n}!")); 18 | ^^^^---------------------------^ 19 ... 20 help: you might have meant to use `Iterator::for_each` 21 | 22 3 - names.iter().map(|n| println!("Hello, {n}!")); 23 3 + names.iter().for_each(|n| println!("Hello, {n}!")); 24 | ``` **Fix:** if you only want side effects, use `for_each` (or a plain `for` loop); if you want results, `collect` them. ```rust 1 fn main() { 2 let names = vec!["alice", "bob"]; 3 names.iter().for_each(|n| println!("Hello, {n}!")); 4 } ``` ### Pitfall 2: Returning two different closures from one `impl Fn` [Section titled “Pitfall 2: Returning two different closures from one impl Fn”](#pitfall-2-returning-two-different-closures-from-one-impl-fn) This looks fine to a TypeScript eye but does not compile, because the two `move` closures capture `n` and are therefore *distinct anonymous types*: ```rust 1 fn make_op(double: bool, n: i32) -> impl Fn(i32) -> i32 { 2 if double { 3 move |x| x * n 4 } else { 5 move |x| x + n 6 } 7 } ``` The real error: ```plaintext 1 error[E0308]: `if` and `else` have incompatible types 2 --> src/main.rs:5:9 3 | 4 2 | / if double { 5 3 | | move |x| x * n 6 | | -------------- 7 | | | 8 | | the expected closure 9 | | expected because of this 10 4 | | } else { 11 5 | | move |x| x + n 12 | | ^^^^^^^^^^^^^^ expected closure, found a different closure 13 6 | | } 14 | |_____- `if` and `else` have incompatible types 15 | 16 = note: expected closure `{closure@src/main.rs:3:9: 3:17}` 17 found closure `{closure@src/main.rs:5:9: 5:17}` 18 = note: no two closures, even if identical, have the same type 19 = help: consider boxing your closure and/or using it as a trait object 20 help: you could change the return type to be a boxed trait object 21 | 22 1 - fn make_op(double: bool, n: i32) -> impl Fn(i32) -> i32 { 23 1 + fn make_op(double: bool, n: i32) -> Box i32> { 24 | ``` **Fix:** box them, exactly as the compiler suggests: ```rust 1 fn make_op(double: bool, n: i32) -> Box i32> { 2 if double { 3 Box::new(move |x| x * n) 4 } else { 5 Box::new(move |x| x + n) 6 } 7 } ``` ### Pitfall 3: Returning a closure that borrows a local (missing `move`) [Section titled “Pitfall 3: Returning a closure that borrows a local (missing move)”](#pitfall-3-returning-a-closure-that-borrows-a-local-missing-move) ```rust 1 fn make_greeter(name: String) -> impl Fn() -> String { 2 || format!("Hello, {name}!") 3 } ``` The closure borrows `name`, but `name` is dropped when `make_greeter` returns. The real error: ```plaintext 1 error[E0373]: closure may outlive the current function, but it borrows `name`, which is owned by the current function 2 --> src/main.rs:2:5 3 | 4 2 | || format!("Hello, {name}!") 5 | ^^ ---- `name` is borrowed here 6 | | 7 | may outlive borrowed value `name` 8 | 9 note: closure is returned here 10 --> src/main.rs:2:5 11 | 12 2 | || format!("Hello, {name}!") 13 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 help: to force the closure to take ownership of `name` (and any other referenced variables), use the `move` keyword 15 | 16 2 | move || format!("Hello, {name}!") 17 | ++++ ``` **Fix:** add `move` so the closure owns `name`: ```rust 1 fn make_greeter(name: String) -> impl Fn() -> String { 2 move || format!("Hello, {name}!") 3 } ``` ### Pitfall 4: Reaching for `reduce` when you mean `fold` [Section titled “Pitfall 4: Reaching for reduce when you mean fold”](#pitfall-4-reaching-for-reduce-when-you-mean-fold) JavaScript’s `reduce(fn, init)` is Rust’s `fold(init, fn)`. Rust’s `reduce` is the *seedless* variant and returns `Option` (because an empty iterator has no first element to start from): ```rust 1 fn main() { 2 let numbers = vec![1, 2, 3, 4, 5]; 3 4 // fold: has a seed, returns the accumulator type directly 5 let sum: i32 = numbers.iter().fold(0, |acc, &n| acc + n); 6 println!("{}", sum); // 15 7 8 // reduce: no seed, returns Option 9 let max = numbers.iter().copied().reduce(|a, b| if a > b { a } else { b }); 10 println!("{:?}", max); // Some(5) 11 } ``` Mixing up the argument order (`fold(fn, init)`) or forgetting that `reduce` yields an `Option` are the two most common slips. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### Prefer `impl Fn` in arguments and returns; box only when forced [Section titled “Prefer impl Fn in arguments and returns; box only when forced”](#prefer-impl-fn-in-arguments-and-returns-box-only-when-forced) `impl Fn` (and the generic `F: Fn` form) is zero-cost. Reach for `Box` only when you genuinely need to return or store closures of *different* concrete types (e.g., from different `match` arms, or in a `Vec` of callbacks). ```rust 1 // Good: zero-cost, single closure shape 2 fn scaler(factor: f64) -> impl Fn(f64) -> f64 { 3 move |x| x * factor 4 } 5 6 // Necessary boxing: heterogeneous callbacks in one collection 7 fn pipeline() -> Vec i32>> { 8 vec![ 9 Box::new(|x| x + 1), 10 Box::new(|x| x * 3), 11 Box::new(|x| x - 2), 12 ] 13 } 14 15 fn main() { 16 let half = scaler(0.5); 17 println!("{}", half(10.0)); // 5 18 19 let result = pipeline().iter().fold(5, |acc, step| step(acc)); 20 println!("{}", result); // ((5+1)*3)-2 = 16 21 } ``` ### Ask for the weakest closure trait that works [Section titled “Ask for the weakest closure trait that works”](#ask-for-the-weakest-closure-trait-that-works) Bound on `Fn` if you only read, `FnMut` if you mutate, `FnOnce` if you consume. A function bounded on `FnOnce` accepts the widest set of closures; a function bounded on `Fn` is the most restrictive but composes most freely (you can call it many times). Pick based on what your function actually does with the closure. ### Use the purpose-built consumers instead of `fold` where they exist [Section titled “Use the purpose-built consumers instead of fold where they exist”](#use-the-purpose-built-consumers-instead-of-fold-where-they-exist) `sum()`, `product()`, `count()`, `max()`, `min()`, `any()`, `all()`, and `find()` express intent more clearly than a hand-rolled `fold` and are just as fast. ```rust 1 fn main() { 2 let prices = vec![19.99, 5.49, 120.0]; 3 let total: f64 = prices.iter().sum(); // clearer than fold 4 let expensive = prices.iter().any(|&p| p > 100.0); // clearer than fold 5 println!("total={total:.2} expensive={expensive}"); 6 } ``` ### Store closures behind a trait object for plugin-style designs [Section titled “Store closures behind a trait object for plugin-style designs”](#store-closures-behind-a-trait-object-for-plugin-style-designs) A struct field typed `Box` lets you hold a callback whose body you do not know at compile time — the Rust equivalent of stashing a TypeScript function on an object. ```rust 1 struct Button { 2 on_click: Box, 3 } 4 5 fn main() { 6 let btn = Button { 7 on_click: Box::new(|| println!("clicked!")), 8 }; 9 (btn.on_click)(); // clicked! 10 } ``` *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small order-processing report, the kind of thing you would write in a backend service. It demonstrates a higher-order function that *returns* a reusable predicate (`min_total`), plus a `filter`/`map`/`fold` pipeline that processes the data in a single pass. ```rust 1 #[derive(Debug)] 2 struct Order { 3 id: u32, 4 customer: String, 5 total: f64, 6 paid: bool, 7 } 8 9 /// Returns a reusable predicate closure. The `impl Fn(&Order) -> bool` 10 /// return type means callers get a zero-cost, directly-callable filter. 11 fn min_total(threshold: f64) -> impl Fn(&Order) -> bool { 12 move |order| order.total >= threshold 13 } 14 15 fn main() { 16 let orders = vec![ 17 Order { id: 1, customer: "Alice".into(), total: 120.0, paid: true }, 18 Order { id: 2, customer: "Bob".into(), total: 35.5, paid: false }, 19 Order { id: 3, customer: "Carol".into(), total: 250.0, paid: true }, 20 Order { id: 4, customer: "Dan".into(), total: 80.0, paid: true }, 21 ]; 22 23 let is_big = min_total(100.0); 24 25 // Keep paid + big orders, format "#id Name", collect into a Vec. 26 let vip: Vec = orders 27 .iter() 28 .filter(|o| o.paid) 29 .filter(|o| is_big(o)) 30 .map(|o| format!("#{} {}", o.id, o.customer)) 31 .collect(); 32 println!("VIP customers: {:?}", vip); 33 34 // Total revenue from paid orders via fold. 35 let revenue: f64 = orders 36 .iter() 37 .filter(|o| o.paid) 38 .fold(0.0, |acc, o| acc + o.total); 39 println!("Paid revenue: {:.2}", revenue); 40 41 // Count unpaid orders. 42 let unpaid = orders.iter().filter(|o| !o.paid).count(); 43 println!("Unpaid orders: {}", unpaid); 44 } ``` **Output:** ```plaintext 1 VIP customers: ["#1 Alice", "#3 Carol"] 2 Paid revenue: 450.00 3 Unpaid orders: 1 ``` The equivalent TypeScript would chain `.filter(...).filter(...).map(...)` (allocating an array per step) and a `.reduce(...)` for revenue. The Rust version reads almost identically but runs each pipeline in one fused, allocation-free pass, and `min_total` hands back a typed, reusable predicate. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Closures and the `Fn` traits](https://doc.rust-lang.org/book/ch13-01-closures.html) * [The Rust Book — Processing a Series of Items with Iterators](https://doc.rust-lang.org/book/ch13-02-iterators.html) * [`std::iter::Iterator` — all adapters and consumers](https://doc.rust-lang.org/std/iter/trait.Iterator.html) * [Rust by Example — Higher Order Functions](https://doc.rust-lang.org/rust-by-example/fn/hof.html) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/): the `|args|` syntax, `Fn`/`FnMut`/`FnOnce`, and capture-by-ref vs `move` * [Function Pointers](/03-functions/05-function-pointers/): the `fn` type, passing named functions, and how function items differ from closures * [Basic Functions](/03-functions/00-basic-functions/): `fn` signatures and the expression-vs-statement model the closures here rely on * [Return Values](/03-functions/02-return-values/): tail expressions and returning multiple values * [Section 05: Ownership](/05-ownership/): why `iter` vs `into_iter` and `move` matter * [Section 04: Control Flow](/04-control-flow/): loops as an alternative to iterator chains * [Section 02: Variables and Mutability](/02-basics/00-variables/): the `mut` rule behind `FnMut` *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Translate a `map`/`filter`/`reduce` chain [Section titled “Exercise 1: Translate a map/filter/reduce chain”](#exercise-1-translate-a-mapfilterreduce-chain) **Difficulty:** Easy **Objective:** Build muscle memory for the lazy iterator pipeline and `fold`. **Instructions:** Given the TypeScript below, write the equivalent Rust. It should take a slice of word lengths, keep the ones longer than 3, double each, and sum them. ```typescript 1 const lengths = [2, 5, 3, 8, 1, 4]; 2 const result = lengths 3 .filter((n) => n > 3) 4 .map((n) => n * 2) 5 .reduce((acc, n) => acc + n, 0); 6 console.log(result); // (5+8+4)*2 = 34 ``` Solution ```rust 1 fn main() { 2 let lengths = [2, 5, 3, 8, 1, 4]; 3 let result: i32 = lengths 4 .iter() 5 .filter(|&&n| n > 3) 6 .map(|&n| n * 2) 7 .sum(); // sum() is the idiomatic consumer for "+ with 0 seed" 8 println!("{}", result); // 34 9 } ``` You could also write the last step as `.fold(0, |acc, n| acc + n)`, which mirrors the TypeScript `reduce` exactly; `sum()` is the idiomatic shorthand. ### Exercise 2: Write a closure factory [Section titled “Exercise 2: Write a closure factory”](#exercise-2-write-a-closure-factory) **Difficulty:** Medium **Objective:** Practice returning a closure with `impl Fn` and capturing with `move`. **Instructions:** Write a function `make_multiplier(factor: i32)` that returns a closure multiplying its argument by `factor`. Then write `compose(f, g)` that returns a closure applying `f` first, then `g`. Use them so that multiplying by 3 then adding 1 turns `10` into `31`. Solution ```rust 1 fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 { 2 move |x| x * factor 3 } 4 5 // Generic over the input/intermediate/output types. 6 fn compose(f: impl Fn(A) -> B, g: impl Fn(B) -> C) -> impl Fn(A) -> C { 7 move |x| g(f(x)) 8 } 9 10 fn main() { 11 let times3 = make_multiplier(3); 12 let add1 = |x: i32| x + 1; 13 14 let times3_then_add1 = compose(times3, add1); 15 println!("{}", times3_then_add1(10)); // (10*3)+1 = 31 16 } ``` `move` is required in both factories so the returned closures own the values they captured (`factor`, and the inner `f`/`g`). ### Exercise 3: A boxed-callback pipeline [Section titled “Exercise 3: A boxed-callback pipeline”](#exercise-3-a-boxed-callback-pipeline) **Difficulty:** Hard **Objective:** Use `Box` to store and run a heterogeneous list of transformations, and combine it with `filter_map`. **Instructions:** Write `total_valid(inputs: &[&str]) -> i32` that parses each string to an `i32`, discards the ones that fail to parse (use `filter_map` with `.parse().ok()`), keeps only positive numbers, and sums the rest. Then build a `Vec i32>>` of three steps (`+1`, `*3`, `-2`) and fold the seed `5` through them. Verify the parse step yields `35` for `["10", "-3", "abc", "5", "0", "20"]` and the pipeline yields `16`. Solution ```rust 1 fn total_valid(inputs: &[&str]) -> i32 { 2 inputs 3 .iter() 4 .filter_map(|s| s.parse::().ok()) // keep only the Ok values 5 .filter(|&n| n > 0) 6 .sum() 7 } 8 9 fn main() { 10 let inputs = ["10", "-3", "abc", "5", "0", "20"]; 11 println!("{}", total_valid(&inputs)); // 10 + 5 + 20 = 35 12 13 // A heterogeneous pipeline of boxed closures. 14 let steps: Vec i32>> = vec![ 15 Box::new(|x| x + 1), 16 Box::new(|x| x * 3), 17 Box::new(|x| x - 2), 18 ]; 19 let result = steps.iter().fold(5, |acc, step| step(acc)); 20 println!("{}", result); // ((5+1)*3)-2 = 16 21 } ``` `filter_map` fuses a `map` and a `filter`: the closure returns an `Option`, and `None` values are dropped. `.parse::()` returns a `Result`, and `.ok()` converts it to an `Option`, throwing away the error. Boxing the steps is required because each closure has a distinct anonymous type, yet they must live together in one `Vec`. # Function Pointers > Rust's fn pointer type is TypeScript's (x) => x, but Rust splits named functions, fn pointers, and closures into distinct types, and only non-capturing ones coerce. In JavaScript and TypeScript, functions are just values: you assign them to variables, stash them in objects, and pass them around without a second thought. Rust lets you do the same, but it draws a sharp line between a **function pointer** (the `fn` type) and a **closure**, and it even gives every named function its own unique zero-sized type. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) A **function pointer** in Rust is a value whose type is written `fn(Args) -> Ret`: a plain pointer to compiled code, exactly like passing a named function in TypeScript. The twist is that each named function actually has its own unique **function item** type (with size 0), which Rust silently coerces to a `fn` pointer when needed. Function pointers are the lightweight cousin of closures: they cannot capture surrounding state, so reach for them when you only need to pass an existing named function around. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // A couple of named functions with the same shape. 2 function double(x: number): number { 3 return x * 2; 4 } 5 6 function increment(x: number): number { 7 return x + 1; 8 } 9 10 // A higher-order function: its parameter `f` is itself a function. 11 // In TypeScript a function type is written `(arg: T) => R`. 12 function applyTwice(f: (x: number) => number, value: number): number { 13 return f(f(value)); 14 } 15 16 console.log(applyTwice(double, 5)); // double(double(5)) = 20 17 console.log(applyTwice(increment, 5)); // increment(increment(5)) = 7 18 19 // Functions are values: store them in arrays, objects, anywhere. 20 const ops: Array<(x: number) => number> = [double, increment]; 21 for (const op of ops) { 22 console.log(op(10)); 23 } ``` In TypeScript every function is a first-class object. There is exactly one notion of “a function value”, and whether it captures variables (a closure) or not makes no difference to its type: `(x: number) => number` describes both. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 fn double(x: i32) -> i32 { 2 x * 2 3 } 4 5 fn increment(x: i32) -> i32 { 6 x + 1 7 } 8 9 // `f: fn(i32) -> i32` is a function POINTER parameter. 10 fn apply_twice(f: fn(i32) -> i32, value: i32) -> i32 { 11 f(f(value)) 12 } 13 14 fn main() { 15 // Bind a named function to a variable of fn-pointer type. 16 let f: fn(i32) -> i32 = double; 17 println!("{}", f(21)); // 42 18 19 println!("{}", apply_twice(double, 5)); // 20 20 println!("{}", apply_twice(increment, 5)); // 7 21 22 // Store function pointers in an array, just like in TypeScript. 23 let ops: [fn(i32) -> i32; 2] = [double, increment]; 24 for op in ops { 25 println!("{}", op(10)); 26 } 27 } ``` Output (real, from `cargo run`): ```text 1 42 2 20 3 7 4 20 5 11 ``` The `fn(i32) -> i32` type is the Rust spelling of TypeScript’s `(x: number) => number`, *for named functions and non-capturing closures only*. The moment a function needs to capture state, you need a closure type instead, which is what the [Arrow Functions and Closures](/03-functions/03-arrow-vs-closures/) topic covers. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Two types hide behind one function [Section titled “Two types hide behind one function”](#two-types-hide-behind-one-function) When you write `fn double(x: i32) -> i32 { .. }`, Rust creates a value `double` whose type is **not** `fn(i32) -> i32`. It is a unique, anonymous **function item type**: the compiler refers to it as `fn(i32) -> i32 {double}`. Every named function gets its own distinct item type, and that type is **zero-sized**: it carries no data at runtime because the identity of the function is known at compile time. A **function pointer** (`fn(i32) -> i32`, with no name in braces) is the runtime-flavored version: an actual pointer to code, the size of one machine word. You rarely write the function-item type yourself. Rust coerces a function item to a function pointer automatically wherever a `fn` type is expected (assigning to an annotated variable, passing to a `fn` parameter, putting items of different functions into the same collection, and so on). ```rust 1 fn double(x: i32) -> i32 { x * 2 } 2 3 fn main() { 4 // The function ITEM `double` is a zero-sized type. 5 println!("size of function item: {}", std::mem::size_of_val(&double)); 6 7 // A function POINTER is a real pointer (8 bytes on a 64-bit target). 8 let p: fn(i32) -> i32 = double; 9 println!("size of fn pointer: {}", std::mem::size_of_val(&p)); 10 } ``` Real output: ```text 1 size of function item: 0 2 size of fn pointer: 8 ``` > **Note:** The zero-sized function item is a performance feature. When you call `double` directly, or pass it to a generic `fn foo(f: F)`, the compiler knows the exact function and can inline the call with no indirection. Coercing to a `fn` pointer erases that identity, so calls go through a real (cheap, but non-inlinable) indirect jump. ### Passing a named function by name [Section titled “Passing a named function by name”](#passing-a-named-function-by-name) To “pass a function” you just use its name as a value, no `&`, no parentheses, no special syntax: ```rust 1 fn square(x: i32) -> i32 { x * x } 2 3 fn main() { 4 let nums = [1, 2, 3]; 5 // `square` (a function item) is accepted because `map` takes anything 6 // implementing `Fn`, and every function item/pointer implements `Fn`. 7 let squared: Vec = nums.iter().copied().map(square).collect(); 8 println!("{squared:?}"); // [1, 4, 9] 9 } ``` This is the direct analogue of `numbers.map(square)` in JavaScript. The difference is purely in the type machinery: `map` is generic over `Fn`, and `square`’s function-item type implements `Fn`, so it fits. ### `fn` pointers versus closures [Section titled “fn pointers versus closures”](#fn-pointers-versus-closures) A closure is a function bundled with the environment it captured. A `fn` pointer has *no* environment. It is just an address. That leads to the single most important rule: > A closure can be coerced to a `fn` pointer **only if it captures nothing**. ```rust 1 fn run(f: fn(i32) -> i32, x: i32) -> i32 { 2 f(x) 3 } 4 5 // Generic over `Fn` — accepts BOTH fn pointers and capturing closures. 6 fn run_any i32>(f: F, x: i32) -> i32 { 7 f(x) 8 } 9 10 fn square(x: i32) -> i32 { x * x } 11 12 fn main() { 13 // A non-capturing closure coerces to a fn pointer. 14 let f: fn(i32) -> i32 = |x| x + 100; 15 println!("{}", run(f, 1)); // 101 16 17 // A capturing closure is NOT a fn pointer, but satisfies `Fn`. 18 let factor = 3; 19 let scale = |x: i32| x * factor; 20 println!("{}", run_any(scale, 10)); // 30 21 22 // Function items satisfy `Fn` too, so they also work with generics/adapters. 23 println!("{:?}", [1, 2, 3].map(square)); // [1, 4, 9] 24 } ``` Real output: ```text 1 101 2 30 3 [1, 4, 9] ``` The takeaway for API design: **accept `impl Fn(...)` (a generic), not `fn(...)`**, unless you specifically need a plain pointer (an FFI boundary, a `static` table, or storing many functions of identical type compactly). A `fn` parameter is the most restrictive choice; `impl Fn` accepts function pointers *and* closures. ### Constructors are functions too [Section titled “Constructors are functions too”](#constructors-are-functions-too) A subtle, delightful Rust feature: tuple-struct constructors and enum-variant constructors are themselves callable function values. You can pass their name anywhere a function is expected. ```rust 1 #[derive(Debug)] 2 struct Meters(f64); 3 4 #[derive(Debug)] 5 enum Token { 6 Word(String), 7 } 8 9 fn main() { 10 // `Meters` is a function value of type `fn(f64) -> Meters`. 11 let readings: Vec = [1.0, 2.5, 3.0].into_iter().map(Meters).collect(); 12 println!("{readings:?}"); 13 14 // `String::from` and `Token::Word` used as function values. 15 let tokens: Vec = ["hi", "there"] 16 .into_iter() 17 .map(String::from) // associated fn as a value 18 .map(Token::Word) // enum variant constructor as a value 19 .collect(); 20 println!("{tokens:?}"); 21 } ``` Real output: ```text 1 [Meters(1.0), Meters(2.5), Meters(3.0)] 2 [Word("hi"), Word("there")] ``` > **Note:** Compiling this exact snippet also emits two benign `dead_code` warnings (`field `0` is never read`), because the tuple fields are only observed through the derived `Debug` impl, which the dead-code analysis intentionally ignores. The program still runs and prints the output above; add `#[allow(dead_code)]` to silence the warnings if you copy this into a probe. There is no TypeScript equivalent: a `class`/tuple type does not give you a free `(x) => new T(x)` function to pass around. In Rust, `Meters` *is* that function. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Aspect | TypeScript/JavaScript | Rust | | --------------------------- | ---------------------------------------------- | -------------------------------------------------------------- | | Function type syntax | `(x: number) => number` | `fn(i32) -> i32` | | One type for all functions? | Yes: closures and plain functions share a type | No: function *items*, `fn` pointers, and closures are distinct | | Capturing state | Any function may close over variables | Only closures capture; `fn` pointers never do | | Size of a function value | Heap-allocated object reference | Function item: `0` bytes; `fn` pointer: 1 word | | Passing a named function | `arr.map(double)` | `arr.map(double)` (identical syntax) | | Constructors as values | Not available | Tuple-struct / enum-variant constructors *are* functions | | Storing mixed functions | Trivial (`Function[]`) | Need a common `fn` type, or `Box` for closures | ### The trait hierarchy in one breath [Section titled “The trait hierarchy in one breath”](#the-trait-hierarchy-in-one-breath) Every function pointer and function item implements all three closure traits — `Fn`, `FnMut`, and `FnOnce` — because it has no state to mutate or consume. That is why a `fn` pointer is accepted by any generic bounded on the least restrictive trait it needs: ```rust 1 fn shout(s: &str) -> String { s.to_uppercase() } 2 3 fn call_fn String>(f: F, s: &str) -> String { f(s) } 4 fn call_fnmut String>(mut f: F, s: &str) -> String { f(s) } 5 fn call_fnonce String>(f: F, s: &str) -> String { f(s) } 6 7 fn main() { 8 let p: fn(&str) -> String = shout; 9 println!("{}", call_fn(p, "hi")); // HI 10 println!("{}", call_fnmut(p, "hi")); // HI 11 println!("{}", call_fnonce(p, "hi")); // HI 12 } ``` Real output: ```text 1 HI 2 HI 3 HI ``` > **Tip:** The relationship is one-directional. A `fn` pointer is usable wherever `Fn`/`FnMut`/`FnOnce` is required, but a capturing closure is *not* usable where a `fn` pointer is required. The `Fn` traits are the more general abstraction; `fn` is the concrete, capture-free special case. The full story of `Fn`/`FnMut`/`FnOnce` lives in [Arrow Functions and Closures](/03-functions/03-arrow-vs-closures/). *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Expecting a capturing closure to be a `fn` pointer [Section titled “Pitfall 1: Expecting a capturing closure to be a fn pointer”](#pitfall-1-expecting-a-capturing-closure-to-be-a-fn-pointer) Coming from TypeScript, where `(x) => x * factor` is “just a function”, it is natural to write a `fn`-typed parameter and hand it a closure that captures a variable. ```rust 1 fn run(f: fn(i32) -> i32, x: i32) -> i32 { 2 f(x) 3 } 4 5 fn main() { 6 let factor = 3; 7 let scale = |x: i32| x * factor; // captures `factor` 8 println!("{}", run(scale, 10)); // does not compile 9 } ``` The real compiler error: ```text 1 error[E0308]: mismatched types 2 --> src/main.rs:8:24 3 | 4 7 | let scale = |x: i32| x * factor; // captures `factor` 5 | -------- the found closure 6 8 | println!("{}", run(scale, 10)); // does not compile 7 | --- ^^^^^ expected fn pointer, found closure 8 | | 9 | arguments to this function are incorrect 10 | 11 = note: expected fn pointer `fn(i32) -> i32` 12 found closure `{closure@src/main.rs:7:17: 7:25}` 13 note: closures can only be coerced to `fn` types if they do not capture any variables 14 --> src/main.rs:7:30 15 | 16 7 | let scale = |x: i32| x * factor; // captures `factor` 17 | ^^^^^^ `factor` captured here ``` **Fix:** make the parameter generic so it accepts closures too: `fn run i32>(f: F, x: i32) -> i32`. Unlike TypeScript, the *capture* changes the type, and the compiler points exactly at the captured variable. ### Pitfall 2: Storing two different functions and getting a “different fn item” error [Section titled “Pitfall 2: Storing two different functions and getting a “different fn item” error”](#pitfall-2-storing-two-different-functions-and-getting-a-different-fn-item-error) Each named function has its own type, so inferring a binding from one function fixes it to *that* item type. Assigning a second function then fails. ```rust 1 fn double(x: i32) -> i32 { x * 2 } 2 fn triple(x: i32) -> i32 { x * 3 } 3 4 fn main() { 5 let mut op = double; // type inferred as the item type of `double` 6 op = triple; // a different item type 7 println!("{}", op(4)); 8 } ``` The real compiler error: ```text 1 error[E0308]: mismatched types 2 --> src/main.rs:6:10 3 | 4 5 | let mut op = double; // type inferred as the item type of `double` 5 | ------ expected due to this value 6 6 | op = triple; // a different item type 7 | ^^^^^^ expected fn item, found a different fn item 8 | 9 = note: expected fn item `fn(_) -> _ {double}` 10 found fn item `fn(_) -> _ {triple}` 11 = note: different fn items have unique types, even if their signatures are the same 12 = help: consider casting both fn items to fn pointers using `as fn(i32) -> i32` ``` **Fix:** force the function-pointer type, either with an annotation (`let mut op: fn(i32) -> i32 = double;`) or with an `as` cast, exactly as the compiler suggests: ```rust 1 fn double(x: i32) -> i32 { x * 2 } 2 fn triple(x: i32) -> i32 { x * 3 } 3 4 fn main() { 5 // Cast to a fn pointer up front so both branches share one type. 6 let mut op = double as fn(i32) -> i32; 7 println!("{}", op(4)); // 8 8 op = triple; // OK: same fn-pointer type 9 println!("{}", op(4)); // 12 10 } ``` Real output: ```text 1 8 2 12 ``` > **Note:** Array and `vec!` literals are smart enough to coerce a mix of function items to a common `fn` pointer on their own, so `let ops = [double, triple];` compiles fine. The reassignment case above does not get that help because the binding’s type is locked in by the *first* value. ### Pitfall 3: Reaching for `fn` when you mean “any callable” [Section titled “Pitfall 3: Reaching for fn when you mean “any callable””](#pitfall-3-reaching-for-fn-when-you-mean-any-callable) A `fn(T) -> R` parameter quietly rejects every closure that captures anything, which, in practice, is most closures. If your function takes a callback, default to a generic `impl Fn` bound. Use a bare `fn` pointer only when you have a concrete reason (FFI, a `static`/`const` table, or you genuinely want the smaller, non-generic type to avoid monomorphization bloat). ### Pitfall 4: Thinking `&function` is how you pass a function [Section titled “Pitfall 4: Thinking \&function is how you pass a function”](#pitfall-4-thinking-function-is-how-you-pass-a-function) In TypeScript you never take the address of a function — and you do not in Rust either. Writing `apply(&double, 5)` produces `&fn`-of-item confusion. Pass the bare name: `apply(double, 5)`. *** ## Best Practices [Section titled “Best Practices”](#best-practices) * **Prefer `impl Fn(...)` for callback parameters.** It accepts function pointers *and* closures and stays zero-cost via monomorphization. Reserve `fn(...)` for the special cases below. See [Higher-Order Functions](/03-functions/04-higher-order/) for the full pattern of accepting and returning closures. * **Use `fn` pointers for homogeneous, capture-free tables.** A registry of commands, a parser’s keyword-to-handler map, or a state machine’s transition table are all naturally `HashMap ..>`: small, copyable, and storable in `static`/`const`. * **Add a `type` alias for repeated signatures.** `type Handler = fn(&Request) -> Response;` reads far better than repeating `fn(&Request) -> Response` across a codebase. * **Pass constructors directly.** `iter.map(Meters)` or `iter.map(Some)` is idiomatic and clearer than `iter.map(|x| Meters(x))`. * **Annotate the binding when collecting heterogeneous functions.** `let ops: Vec i32> = vec![double, triple];` sidesteps the “different fn item” trap before it happens. * **Mark FFI callbacks `extern "C" fn`.** When a function pointer crosses into C, its type must carry the C ABI: `extern "C" fn(c_int) -> c_int`. This is covered in [Unsafe and FFI](/20-unsafe-ffi/). *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A command dispatch table, the kind of thing you would build for a calculator REPL, a chat-bot command router, or a tiny scripting layer. Each command is a plain function with an identical signature, and the registry maps names to function pointers. This is where `fn` pointers genuinely shine: every value is a single word, copyable, and storable in a `HashMap`. ```rust 1 use std::collections::HashMap; 2 3 // Each command is a plain function with the same signature. 4 fn cmd_add(args: &[f64]) -> f64 { 5 args.iter().sum() 6 } 7 8 fn cmd_max(args: &[f64]) -> f64 { 9 args.iter().copied().fold(f64::MIN, f64::max) 10 } 11 12 fn cmd_mean(args: &[f64]) -> f64 { 13 if args.is_empty() { 14 0.0 15 } else { 16 args.iter().sum::() / args.len() as f64 17 } 18 } 19 20 // A readable alias for the shared signature. 21 type Command = fn(&[f64]) -> f64; 22 23 fn build_registry() -> HashMap<&'static str, Command> { 24 let mut registry: HashMap<&'static str, Command> = HashMap::new(); 25 registry.insert("add", cmd_add); 26 registry.insert("max", cmd_max); 27 registry.insert("mean", cmd_mean); 28 registry 29 } 30 31 fn main() { 32 let registry = build_registry(); 33 let data = [10.0, 4.0, 6.0]; 34 35 for name in ["add", "max", "mean", "nope"] { 36 match registry.get(name) { 37 Some(command) => println!("{name} -> {}", command(&data)), 38 None => println!("{name} -> unknown command"), 39 } 40 } 41 } ``` Real output: ```text 1 add -> 20 2 max -> 10 3 mean -> 6.666666666666667 4 nope -> unknown command ``` Compare the TypeScript shape you would write today: ```typescript 1 type Command = (args: number[]) => number; 2 3 const registry: Record = { 4 add: (args) => args.reduce((a, b) => a + b, 0), 5 max: (args) => Math.max(...args), 6 mean: (args) => (args.length ? args.reduce((a, b) => a + b, 0) / args.length : 0), 7 }; 8 9 const data = [10, 4, 6]; 10 for (const name of ["add", "max", "mean", "nope"]) { 11 const cmd = registry[name]; 12 console.log(name, "->", cmd ? cmd(data) : "unknown command"); 13 } ``` The structure is nearly identical. The differences are Rust-flavored: the registry value type is the concrete `fn(&[f64]) -> f64` (not a closure type), the lookup returns an `Option` you must handle, and `cmd_max` passes `f64::max` itself as the folding function, a method path used as a function value, just like passing `Meters` earlier. > **Tip:** If any command needed to capture state — say, a counter or a shared config — you could no longer use `fn`. The registry value would become `Box f64>`. That trade-off (cheap copyable `fn` vs. heap-allocated, capturing `dyn Fn`) is the practical reason to know the difference between the two. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Reference - Function pointer types](https://doc.rust-lang.org/reference/types/function-pointer.html) * [The Rust Reference - Function item types](https://doc.rust-lang.org/reference/types/function-item.html) * [`std::primitive::fn`](https://doc.rust-lang.org/std/primitive.fn.html) * [The Rust Book - Function Pointers](https://doc.rust-lang.org/book/ch13-01-closures.html) (closures chapter; function pointers appear near the end) * [Rust by Example - Higher Order Functions](https://doc.rust-lang.org/rust-by-example/fn/hof.html) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Basic Functions](/03-functions/00-basic-functions/) — how `fn` definitions and signatures are built in the first place * [Function Parameters](/03-functions/01-parameters/) — passing data into functions; idiomatic alternatives to default/rest params * [Arrow Functions and Closures](/03-functions/03-arrow-vs-closures/) — closures, capture modes, and the `Fn`/`FnMut`/`FnOnce` traits in depth * [Higher-Order Functions](/03-functions/04-higher-order/) — taking `impl Fn` and returning closures * [Recursion](/03-functions/06-recursion/) — recursive functions and their stack considerations * [Section 02: Basic Types](/02-basics/01-types/) — the `i32`, `f64`, and tuple types used throughout these signatures * [Section 04: Control Flow](/04-control-flow/) — `match` and pattern matching, used to select a function above * [Unsafe and FFI](/20-unsafe-ffi/) — `extern "C" fn` pointers across the C boundary *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Operator lookup [Section titled “Exercise 1: Operator lookup”](#exercise-1-operator-lookup) **Difficulty:** Easy **Objective:** Return a function pointer chosen at runtime. **Instructions:** Write `fn operation(symbol: char) -> Option i32>` that returns `Some(..)` containing the matching binary function for `'+'`, `'-'`, and `'*'`, and `None` for anything else. Call it from `main` and apply the returned function to two numbers. Solution ```rust 1 fn add(a: i32, b: i32) -> i32 { a + b } 2 fn sub(a: i32, b: i32) -> i32 { a - b } 3 fn mul(a: i32, b: i32) -> i32 { a * b } 4 5 fn operation(symbol: char) -> Option i32> { 6 match symbol { 7 '+' => Some(add), 8 '-' => Some(sub), 9 '*' => Some(mul), 10 _ => None, 11 } 12 } 13 14 fn main() { 15 if let Some(op) = operation('*') { 16 println!("{}", op(6, 7)); // 42 17 } 18 } ``` Each `match` arm returns a different function *item*, but the declared return type `fn(i32, i32) -> i32` makes Rust coerce all of them to the same `fn` pointer. Output: `42`. ### Exercise 2: Transform with a function pointer [Section titled “Exercise 2: Transform with a function pointer”](#exercise-2-transform-with-a-function-pointer) **Difficulty:** Medium **Objective:** Write a higher-order function whose callback parameter is a `fn` pointer. **Instructions:** Implement `fn transform_all(values: &[i32], f: fn(i32) -> i32) -> Vec` that applies `f` to every element and collects the results. Test it by passing a named `negate` function. Then, in a comment, explain why you could *not* pass `|x| x * some_local` to it. Solution ```rust 1 fn transform_all(values: &[i32], f: fn(i32) -> i32) -> Vec { 2 values.iter().map(|&v| f(v)).collect() 3 } 4 5 fn negate(x: i32) -> i32 { 6 -x 7 } 8 9 fn main() { 10 println!("{:?}", transform_all(&[1, 2, 3], negate)); // [-1, -2, -3] 11 12 // A closure that captures a local, e.g. `|x| x * some_local`, has a closure 13 // type, not `fn(i32) -> i32`, so it would NOT compile here. To accept such 14 // closures, change the bound to a generic: `f: impl Fn(i32) -> i32`. 15 } ``` Output: `[-1, -2, -3]`. ### Exercise 3: A retry runner [Section titled “Exercise 3: A retry runner”](#exercise-3-a-retry-runner) **Difficulty:** Medium/Hard **Objective:** Pass a fallible function as a `fn` pointer and call it repeatedly. **Instructions:** Write `fn retry(f: fn(u32) -> Result, max: u32) -> Result` that calls `f` with attempt numbers `1..=max`, returning the first `Ok`, or the last `Err` if every attempt fails. Test it with a `flaky` function that only succeeds once `attempt >= 3`. Solution ```rust 1 fn flaky(attempt: u32) -> Result { 2 if attempt >= 3 { 3 Ok(attempt) 4 } else { 5 Err(format!("failed on attempt {attempt}")) 6 } 7 } 8 9 fn retry(f: fn(u32) -> Result, max: u32) -> Result { 10 let mut last = Err(String::from("never ran")); 11 for attempt in 1..=max { 12 last = f(attempt); 13 if last.is_ok() { 14 return last; 15 } 16 } 17 last 18 } 19 20 fn main() { 21 println!("{:?}", retry(flaky, 5)); // Ok(3) 22 println!("{:?}", retry(flaky, 2)); // Err("failed on attempt 2") 23 } ``` Output: ```text 1 Ok(3) 2 Err("failed on attempt 2") ``` Because `flaky` neither captures state nor needs to, a `fn` pointer is the perfect parameter type. If `retry` had to accept a closure that captured, say, a shared HTTP client, you would switch the bound to `f: impl FnMut(u32) -> Result`. # Recursion > Recursion looks identical to TypeScript, but Rust guarantees no tail-call optimization, aborts on stack overflow, and needs Box for recursive enums like a cons list. A function that calls itself is **recursion**, and it works the same way in Rust as it does in TypeScript: you split a problem into a smaller version of itself plus a base case. The surprises for a TypeScript/JavaScript developer are not about syntax — they are about *limits*. Rust does **not** guarantee tail-call optimization, so deep recursion overflows the stack just like it does in V8, and Rust’s strict type system forces you to reach for `Box` the moment you write a **recursive data type**. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) **Recursion** is a function defined in terms of itself: a **base case** that stops the recursion and a **recursive case** that calls the function with a smaller input. Rust supports recursion fully, but, like JavaScript engines, it makes **no guarantee** of tail-call optimization, so each call consumes a real stack frame and very deep recursion will abort with a stack overflow. This file shows recursive functions, why deep recursion is risky, how to convert recursion to iteration, and a first look at recursive data types that need `Box`. > **Note:** This file focuses on *recursive functions*, *stack depth*, and *iterative alternatives*, with a teaser of *recursive enums*. The function-definition basics (typed parameters, return types, tail expressions) live in [Basic Functions and Signatures](/03-functions/00-basic-functions/); recursive data structures get full treatment in [Section 10: Smart Pointers](/10-smart-pointers/). *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // TypeScript - classic recursion: factorial and a recursive list sum. 2 function factorial(n: number): number { 3 if (n <= 1) { 4 return 1; // base case 5 } 6 return n * factorial(n - 1); // recursive case 7 } 8 9 function sumArray(numbers: number[]): number { 10 if (numbers.length === 0) { 11 return 0; // base case 12 } 13 const [first, ...rest] = numbers; // head + tail 14 return first + sumArray(rest); 15 } 16 17 console.log(factorial(5)); // 120 18 console.log(sumArray([3, 1, 4, 1, 5, 9, 2, 6])); // 31 19 20 // Deep recursion blows the JS call stack, too: 21 function sumTo(n: number): number { 22 return n === 0 ? 0 : n + sumTo(n - 1); 23 } 24 // sumTo(1_000_000); // RangeError: Maximum call stack size exceeded ``` **Key points:** * A recursive function needs a **base case** or it loops forever (or until the stack runs out). * JavaScript engines impose a call-stack limit; exceeding it throws `RangeError: Maximum call stack size exceeded`. * The ES2015 spec defined tail-call optimization, but in practice **only Safari/JavaScriptCore ships it** — V8 (Node, Chrome) does not. So you cannot rely on it either. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 // Recursive factorial with a base case. 2 fn factorial(n: u64) -> u64 { 3 if n <= 1 { 4 1 // base case 5 } else { 6 n * factorial(n - 1) // recursive case 7 } 8 } 9 10 // Recursive sum over a slice using "head + recurse on the tail". 11 fn sum_slice(numbers: &[i64]) -> i64 { 12 match numbers { 13 [] => 0, // base case: empty slice 14 [first, rest @ ..] => first + sum_slice(rest), // head + tail 15 } 16 } 17 18 fn main() { 19 println!("5! = {}", factorial(5)); 20 println!("10! = {}", factorial(10)); 21 22 let data = [3, 1, 4, 1, 5, 9, 2, 6]; 23 println!("sum = {}", sum_slice(&data)); 24 } ``` **Output (verified):** ```text 1 5! = 120 2 10! = 3628800 3 sum = 31 ``` **Key points:** * A recursive `fn` in Rust looks just like any other function — it simply calls itself. * The slice pattern `[first, rest @ ..]` binds the first element to `first` and the **remaining slice** to `rest`, the idiomatic Rust analog of JavaScript’s `const [first, ...rest] = numbers`. It does **not** allocate a new array; `rest` is a borrowed view into the same data. * Just like V8, Rust has a finite stack and **no guaranteed tail-call optimization**. The next sections show exactly what that means. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Anatomy of a recursive function [Section titled “Anatomy of a recursive function”](#anatomy-of-a-recursive-function) Every correct recursion has two parts: 1. A **base case** that returns without recursing. In `factorial`, that is `n <= 1 => 1`. 2. A **recursive case** that calls the function on a *smaller* input so the recursion makes progress toward the base case. In `factorial`, that is `n * factorial(n - 1)`. If you forget the base case, or the recursive case never shrinks the input, the function recurses forever, which in Rust means it runs until the stack is exhausted (covered below), not an infinite hang. ### Slice patterns replace `[first, ...rest]` [Section titled “Slice patterns replace \[first, ...rest\]”](#slice-patterns-replace-first-rest) In TypeScript, `const [first, ...rest] = numbers` copies the tail into a brand-new array on every call, so a recursive `sumArray` is quietly O(n²) in allocations. Rust’s `[first, rest @ ..]` pattern binds `rest` to a **sub-slice** (a pointer + length) that borrows the original data with **zero copying**. The recursion is still linear in stack depth, but it allocates nothing. > **Note:** Slice patterns and `match` are part of Rust’s pattern-matching system, covered in depth in [Section 04: Control Flow](/04-control-flow/). Here we use them only to express “head and tail.” ### The call stack is finite — and Rust does not hide it [Section titled “The call stack is finite — and Rust does not hide it”](#the-call-stack-is-finite--and-rust-does-not-hide-it) Each function call pushes a **stack frame** holding its parameters and locals. When the function returns, the frame is popped. Recursion stacks up one frame per pending call, and the call stack has a fixed size — the main thread defaults to about **8 MiB** on most platforms. Pure tail-recursive functions *could* in principle reuse a single frame, but **Rust makes no guarantee that it will do so**, so you must assume every recursive call costs a frame. ```rust 1 // Deeply recursive sum 1..=n. Each call adds a frame to the call stack. 2 // There is no guaranteed tail-call optimization, so this overflows the 3 // stack for large n. 4 fn sum_to(n: u64) -> u64 { 5 if n == 0 { 6 0 7 } else { 8 n + sum_to(n - 1) 9 } 10 } 11 12 fn main() { 13 // A few hundred thousand frames is enough to blow the default 8 MiB stack. 14 println!("{}", sum_to(1_000_000)); 15 } ``` This compiles cleanly, but running it aborts the process. **Real output (verified):** ```text 1 thread 'main' has overflowed its stack 2 fatal runtime error: stack overflow, aborting ``` The process exits with a failure (code 134 / `SIGABRT` on this machine). This is the direct counterpart to V8’s `RangeError: Maximum call stack size exceeded` — the difference is that JavaScript throws a *catchable error*, whereas a Rust stack overflow **aborts the whole process** and cannot be caught. That makes uncontrolled recursion depth more dangerous in Rust than in Node, and it is why production Rust code prefers iteration for anything that scales with input size. ### ”Tail position” does not save you (no guaranteed TCO) [Section titled “”Tail position” does not save you (no guaranteed TCO)”](#tail-position-does-not-save-you-no-guaranteed-tco) A call is in **tail position** if it is the very last thing the function does — its result is returned directly without any further work. The factorial above is *not* tail-recursive (it still has to multiply by `n` after the call returns), but an accumulator-passing version is: ```rust 1 // Accumulator style: still recursive, but the recursive call is in 2 // "tail position". Rust does NOT guarantee this is optimized away, 3 // so it can still overflow - shown here only to contrast the shape. 4 fn sum_to_acc(n: u64, acc: u64) -> u64 { 5 if n == 0 { 6 acc 7 } else { 8 sum_to_acc(n - 1, acc + n) // tail call: nothing happens after it returns 9 } 10 } 11 12 fn main() { 13 println!("sum_to_acc(100, 0) = {}", sum_to_acc(100, 0)); 14 } ``` **Output (verified):** ```text 1 sum_to_acc(100, 0) = 5050 ``` In a language with **guaranteed** tail-call optimization (such as Scheme, or Safari’s JavaScript engine for proper tail calls), the compiler would rewrite this into a loop and it would handle any depth. Rust gives **no such guarantee**. The optimizer (LLVM) *may* turn a tail call into a jump at higher optimization levels, but this is an implementation detail you must never depend on. There is no `become` keyword in stable Rust to *require* it. Treat tail-position recursion exactly like any other recursion: bounded by stack size. ### Mutual recursion works too [Section titled “Mutual recursion works too”](#mutual-recursion-works-too) Two functions can call each other. Because Rust resolves module items regardless of order (no hoisting needed — see [Basic Functions and Signatures](/03-functions/00-basic-functions/)), neither has to be declared “first.” ```rust 1 // Mutual recursion: two functions that call each other. 2 fn is_even(n: u64) -> bool { 3 if n == 0 { 4 true 5 } else { 6 is_odd(n - 1) 7 } 8 } 9 10 fn is_odd(n: u64) -> bool { 11 if n == 0 { 12 false 13 } else { 14 is_even(n - 1) 15 } 16 } 17 18 fn main() { 19 println!("is_even(10) = {}", is_even(10)); 20 println!("is_odd(7) = {}", is_odd(7)); 21 } ``` **Output (verified):** ```text 1 is_even(10) = true 2 is_odd(7) = true ``` The same stack-depth caveat applies: mutual recursion consumes one frame per call, alternating between the two functions. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript/JavaScript (Node/V8) | Rust | | ------------------------- | ------------------------------------------------------- | -------------------------------------------------------------- | | Recursion syntax | Function calls itself | Identical: function calls itself | | Head/tail destructuring | `const [first, ...rest]` (copies the tail) | `[first, rest @ ..]` slice pattern (borrows, no copy) | | Stack-overflow behavior | Throws `RangeError` (catchable with `try/catch`) | **Aborts the process**; not catchable on the main thread | | Tail-call optimization | Spec’d in ES2015, but V8/Node does **not** implement it | **No guarantee**; no `become` keyword in stable Rust | | Recursive data types | Just nest objects (`{ value, next }`) | Need indirection (`Box`, `Rc`) or the type has “infinite size” | | Default main-thread stack | \~984 KiB in Node (V8), tunable via `--stack-size` | \~8 MiB on most platforms; spawned threads are configurable | | Idiomatic preference | Recursion or iteration, both common | Iteration strongly preferred for input-scaled depth | ### Why iteration is the default in Rust [Section titled “Why iteration is the default in Rust”](#why-iteration-is-the-default-in-rust) In JavaScript you often reach for recursion because it reads nicely and the engine’s limit feels far away. In Rust, iterators (`for`, `while`, and the `Iterator` methods like `.fold()`, `.sum()`) are **zero-cost abstractions** that compile down to tight loops with no per-element stack growth. So the idiomatic Rust instinct is: *use an iterator unless the data itself is recursive (a tree) and shallow.* This is the same code, rewritten iteratively: ```rust 1 // Iterative factorial: a loop instead of recursion - constant stack usage. 2 fn factorial_iter(n: u64) -> u64 { 3 let mut result = 1u64; 4 for i in 2..=n { 5 result *= i; 6 } 7 result 8 } 9 10 // Iterative sum 1..=n - no stack growth, handles huge n with one line. 11 fn sum_to_iter(n: u64) -> u64 { 12 (1..=n).sum() 13 } 14 15 fn main() { 16 println!("10! = {}", factorial_iter(10)); 17 println!("sum 1..=1_000_000 = {}", sum_to_iter(1_000_000)); 18 } ``` **Output (verified):** ```text 1 10! = 3628800 2 sum 1..=1_000_000 = 500000500000 ``` Note that `sum_to_iter(1_000_000)` runs fine, while the recursive `sum_to(1_000_000)` aborted with a stack overflow. Same result, radically different runtime safety. (Iterators and their combinators are covered in [Higher-Order Functions](/03-functions/04-higher-order/) and [Section 07: Collections](/07-collections/).) *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: A recursive `enum` without indirection won’t compile [Section titled “Pitfall 1: A recursive enum without indirection won’t compile”](#pitfall-1-a-recursive-enum-without-indirection-wont-compile) This is unique to Rust and surprises every TypeScript developer. In TypeScript, a linked-list node is just `{ value: number; next: Node | null }`; objects are heap references, so the size is always “one pointer.” In Rust, a value’s size must be known at compile time, and a type that *contains itself by value* would be infinitely large. ```rust 1 // A recursive enum WITHOUT a Box - this does NOT compile. 2 enum List { 3 Cons(i32, List), 4 Nil, 5 } 6 7 fn main() { 8 let _ = List::Nil; 9 } ``` **Real compiler error:** ```text 1 error[E0072]: recursive type `List` has infinite size 2 --> src/main.rs:2:1 3 | 4 2 | enum List { 5 | ^^^^^^^^^ 6 3 | Cons(i32, List), 7 | ---- recursive without indirection 8 | 9 help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle 10 | 11 3 | Cons(i32, Box), 12 | ++++ + ``` The compiler tells you exactly how to fix it: wrap the recursive field in a `Box` (a heap pointer of known size). See the [recursive enum teaser](#a-recursive-enum-teaser-box) below. ### Pitfall 2: A missing or unreachable base case → stack overflow at runtime [Section titled “Pitfall 2: A missing or unreachable base case → stack overflow at runtime”](#pitfall-2-a-missing-or-unreachable-base-case--stack-overflow-at-runtime) This compiles fine — the bug only shows up when you run it. ```rust 1 // BUG: the recursive call never shrinks toward a base case. 2 fn count_down_forever(n: u32) { 3 println!("{n}"); 4 count_down_forever(n - 1); // u32 wraps below 0 in release, panics in debug 5 } ``` In **debug builds** the `n - 1` panics with `attempt to subtract with overflow` once `n` hits `0`. In **release builds** the subtraction wraps `0` to `u32::MAX` and the recursion runs effectively forever until it aborts with `thread 'main' has overflowed its stack`. Either way, the fix is a real base case: `if n == 0 { return; }`. > **Warning:** A Rust stack overflow **aborts the process** and cannot be recovered with error handling. Unlike a Node `RangeError` you can `catch`, there is no safety net — design recursion so its depth is bounded by something small (like tree height), not by raw input size. ### Pitfall 3: Assuming tail recursion is free [Section titled “Pitfall 3: Assuming tail recursion is free”](#pitfall-3-assuming-tail-recursion-is-free) Coming from a functional background (or Safari’s engine), you might write a tail-recursive loop expecting it to run in constant stack space. Rust does not promise this: ```rust 1 // Looks tail-recursive, but Rust gives NO guarantee it becomes a loop. 2 fn sum_to_acc(n: u64, acc: u64) -> u64 { 3 if n == 0 { acc } else { sum_to_acc(n - 1, acc + n) } 4 } 5 // sum_to_acc(10_000_000, 0) may still overflow the stack. ``` There is no stable `become` keyword to force a tail call. If you need guaranteed constant stack usage, **write a loop** — do not rely on the optimizer. ### Pitfall 4: Exponential recursion (the naive Fibonacci trap) [Section titled “Pitfall 4: Exponential recursion (the naive Fibonacci trap)”](#pitfall-4-exponential-recursion-the-naive-fibonacci-trap) This is not Rust-specific, but it bites hard because Rust is fast enough that you might not notice the algorithmic blowup until the input grows: ```rust 1 // O(2^n): recomputes the same values exponentially many times. 2 fn fib_rec(n: u32) -> u64 { 3 match n { 4 0 => 0, 5 1 => 1, 6 _ => fib_rec(n - 1) + fib_rec(n - 2), 7 } 8 } ``` `fib_rec(50)` recomputes lower Fibonacci numbers billions of times. The recursion *depth* is fine (only \~50 frames), but the *number of calls* explodes. The fix is the same as in TypeScript: memoize, or rewrite iteratively (see [Best Practices](#best-practices) and Exercise 3). *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Prefer iteration when depth scales with input [Section titled “1. Prefer iteration when depth scales with input”](#1-prefer-iteration-when-depth-scales-with-input) If your recursion depth grows with the size of a list, a number, or any unbounded input, write a loop or use an iterator. Reserve recursion for cases where the **data structure itself** is recursive and **shallow** (a parse tree, a small directory tree, JSON). ```rust 1 // Iterative: O(1) stack, handles any n. 2 fn fib_iter(n: u32) -> u64 { 3 let (mut a, mut b) = (0u64, 1u64); 4 for _ in 0..n { 5 let next = a + b; 6 a = b; 7 b = next; 8 } 9 a 10 } ``` ### 2. Recurse on bounded structures, iterate over sequences [Section titled “2. Recurse on bounded structures, iterate over sequences”](#2-recurse-on-bounded-structures-iterate-over-sequences) Walking a binary tree recursively is idiomatic and safe — the depth is the tree’s height (often `log n`). Summing a million-element list recursively is not — its depth is the list length. Match the technique to the shape of the data. ### 3. Reach for `Box` (or `Rc`) for recursive types [Section titled “3. Reach for Box (or Rc) for recursive types”](#3-reach-for-box-or-rc-for-recursive-types) A self-referential `enum` or `struct` needs heap indirection so its size is finite. `Box` is the simplest choice; `Rc` when you need shared ownership. This is introduced below and detailed in [Section 10: Smart Pointers](/10-smart-pointers/). ### 4. Bound recursion explicitly when accepting untrusted input [Section titled “4. Bound recursion explicitly when accepting untrusted input”](#4-bound-recursion-explicitly-when-accepting-untrusted-input) If recursion depth can be influenced by external input (parsing user-supplied JSON, deserializing nested data), add an explicit `depth` parameter and return an error past a limit, rather than risking an unrecoverable stack overflow. This is a security-relevant pattern; see [Section 27: Security](/27-security/). ### 5. Memoize or tabulate exponential recursions [Section titled “5. Memoize or tabulate exponential recursions”](#5-memoize-or-tabulate-exponential-recursions) For overlapping-subproblem recursions like Fibonacci, cache results in a `HashMap` (memoization) or build a table bottom-up (tabulation / iteration). The iterative `fib_iter` above is tabulation taken to its limit. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) Recursion shines on genuinely recursive data. Here is a directory-tree size calculator, the kind of traversal a build tool, bundler, or `du`-style utility performs. The tree is a recursive `enum` (a directory owns its children), and both the traversal functions are naturally recursive. Importantly, the recursion depth equals the **tree’s depth**, which is small and bounded, so this is exactly where recursion belongs. ```rust 1 // Real-world example: compute the total size of a directory tree. 2 // The tree is a recursive data structure (a node owns its children), 3 // and the traversal is naturally recursive. 4 5 /// A node in a filesystem tree. 6 enum Node { 7 /// A file with a size in bytes. 8 File { name: String, size: u64 }, 9 /// A directory containing child nodes. 10 Dir { name: String, children: Vec }, 11 } 12 13 /// Recursively sums the byte size of a node and everything under it. 14 fn total_size(node: &Node) -> u64 { 15 match node { 16 Node::File { size, .. } => *size, 17 Node::Dir { children, .. } => children.iter().map(total_size).sum(), 18 } 19 } 20 21 /// Recursively prints the tree with indentation showing depth. 22 fn print_tree(node: &Node, depth: usize) { 23 let indent = " ".repeat(depth); 24 match node { 25 Node::File { name, size } => println!("{indent}{name} ({size} bytes)"), 26 Node::Dir { name, children } => { 27 println!("{indent}{name}/"); 28 for child in children { 29 print_tree(child, depth + 1); 30 } 31 } 32 } 33 } 34 35 fn main() { 36 let tree = Node::Dir { 37 name: "src".to_string(), 38 children: vec![ 39 Node::File { name: "main.rs".to_string(), size: 1200 }, 40 Node::Dir { 41 name: "utils".to_string(), 42 children: vec![ 43 Node::File { name: "parse.rs".to_string(), size: 3400 }, 44 Node::File { name: "format.rs".to_string(), size: 900 }, 45 ], 46 }, 47 ], 48 }; 49 50 print_tree(&tree, 0); 51 println!("total size: {} bytes", total_size(&tree)); 52 } ``` **Output (verified):** ```text 1 src/ 2 main.rs (1200 bytes) 3 utils/ 4 parse.rs (3400 bytes) 5 format.rs (900 bytes) 6 total size: 5500 bytes ``` **What to notice:** * `Node` is a recursive type, but `children: Vec` already provides the heap indirection (`Vec` stores its elements behind a pointer), so no explicit `Box` is needed here. (You only need `Box` when a variant contains the type *directly*, as in the `Cons` list below.) * `total_size` recurses on each child; `children.iter().map(total_size).sum()` is the idiomatic combination of iteration *over siblings* and recursion *into depth*. * `print_tree` passes a `depth` parameter, a common pattern for tracking how deep you are, useful both for formatting and for enforcing a depth limit on untrusted input. * Because the depth is the directory nesting level (typically a handful), there is no stack-overflow risk. This is the right shape for recursion. ### A recursive `enum` teaser (`Box`) [Section titled “A recursive enum teaser (Box)”](#a-recursive-enum-teaser-box) When a type contains *itself directly* (a classic cons list) you need explicit indirection. `Box` is a heap pointer with a fixed size (one machine word), which breaks the “infinite size” cycle from [Pitfall 1](#pitfall-1-a-recursive-enum-without-indirection-wont-compile). ```rust 1 // A recursive enum: a cons-style linked list. 2 // `Box` gives the variant a known, finite size (a pointer). 3 #[derive(Debug)] 4 enum List { 5 Cons(i32, Box), 6 Nil, 7 } 8 9 use List::{Cons, Nil}; 10 11 fn sum_list(list: &List) -> i32 { 12 match list { 13 Cons(value, rest) => value + sum_list(rest), 14 Nil => 0, 15 } 16 } 17 18 fn main() { 19 // 1 -> 2 -> 3 -> Nil 20 let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); 21 println!("list = {list:?}"); 22 println!("sum = {}", sum_list(&list)); 23 } ``` **Output (verified):** ```text 1 list = Cons(1, Cons(2, Cons(3, Nil))) 2 sum = 6 ``` This is only a teaser. `Box`, `Rc`, and recursive data structures get full coverage in [Section 10: Smart Pointers](/10-smart-pointers/). The takeaway for now: **recursive *functions* need a base case; recursive *types* need indirection.** *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book – Recursive Types with Box](https://doc.rust-lang.org/book/ch15-01-box.html#enabling-recursive-types-with-boxes) * [The Rust Book – Defining an Enum](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html) * [Rust by Example – Functions](https://doc.rust-lang.org/rust-by-example/fn.html) * [Rust Reference – Recursive type errors (E0072)](https://doc.rust-lang.org/error_codes/E0072.html) * [Rust Reference – Slice patterns](https://doc.rust-lang.org/reference/patterns.html#slice-patterns) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Basic Functions](/03-functions/00-basic-functions/) — function signatures, tail expressions, and why items need no hoisting. * [Parameters](/03-functions/01-parameters/) — accumulator-style parameters and slices `&[T]` as inputs. * [Return Values](/03-functions/02-return-values/): early `return` for base cases, and returning the unit type. * [Higher-Order Functions](/03-functions/04-higher-order/) — iterator combinators (`map`, `fold`, `sum`) that often replace recursion. * [Function Pointers](/03-functions/05-function-pointers/) — passing a function (such as a recursive one) by name. * [Section 04: Control Flow](/04-control-flow/) — `match`, slice patterns, and `loop`/`while` for the iterative alternatives. * [Section 07: Collections](/07-collections/): `Vec`, `HashMap` (for memoization), and iterators over sequences. * [Section 10: Smart Pointers](/10-smart-pointers/) — `Box`, `Rc`, and recursive data structures in full. * [Section 27: Security](/27-security/): bounding recursion depth on untrusted input. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Recursive countdown [Section titled “Exercise 1: Recursive countdown”](#exercise-1-recursive-countdown) **Difficulty:** Easy **Objective:** Write a recursive function with a clear base case that performs a side effect. **Instructions:** 1. Write `fn countdown(n: u32)` that prints each number from `n` down to `1`, then prints `"liftoff!"`. 2. The base case is `n == 0` (print `"liftoff!"` and stop). 3. The recursive case prints `n` and calls `countdown(n - 1)`. 4. Call `countdown(3)` from `main`. Solution ```rust 1 fn countdown(n: u32) { 2 if n == 0 { 3 println!("liftoff!"); // base case 4 } else { 5 println!("{n}"); 6 countdown(n - 1); // recursive case 7 } 8 } 9 10 fn main() { 11 countdown(3); 12 } ``` **Verified output:** ```text 1 3 2 2 3 1 4 liftoff! ``` This is safe because the depth (`n`) is tiny. If `n` could be in the millions, you would write a `for` loop instead to avoid a stack overflow. ### Exercise 2: Greatest common divisor (Euclid’s algorithm) [Section titled “Exercise 2: Greatest common divisor (Euclid’s algorithm)”](#exercise-2-greatest-common-divisor-euclids-algorithm) **Difficulty:** Medium **Objective:** Implement a naturally recursive numeric algorithm and return a value via a tail expression. **Instructions:** 1. Write `fn gcd(a: u64, b: u64) -> u64`. 2. The base case: when `b == 0`, return `a`. 3. The recursive case: return `gcd(b, a % b)`. 4. Test it with `gcd(48, 18)` (should be `6`). > **Tip:** This recursion is shallow (its depth grows only logarithmically) so recursion is perfectly safe here. Note that the recursive call is in tail position, but remember Rust does not *guarantee* that helps with stack usage; it simply does not matter at this depth. Solution ```rust 1 fn gcd(a: u64, b: u64) -> u64 { 2 if b == 0 { 3 a // base case 4 } else { 5 gcd(b, a % b) // recursive case 6 } 7 } 8 9 fn main() { 10 println!("gcd(48, 18) = {}", gcd(48, 18)); 11 } ``` **Verified output:** ```text 1 gcd(48, 18) = 6 ``` ### Exercise 3: Fibonacci, recursive vs. iterative [Section titled “Exercise 3: Fibonacci, recursive vs. iterative”](#exercise-3-fibonacci-recursive-vs-iterative) **Difficulty:** Medium **Objective:** Feel the difference between an exponential recursion and an efficient loop, and understand why Rust developers reach for iteration. **Instructions:** 1. Write `fn fib_rec(n: u32) -> u64` using the naive recursive definition (`fib(0) = 0`, `fib(1) = 1`, otherwise `fib(n-1) + fib(n-2)`). 2. Write `fn fib_iter(n: u32) -> u64` that computes the same value with a single loop and two accumulators — O(n) time, O(1) stack. 3. Print `fib_rec(10)`, `fib_iter(10)` (both `55`), and `fib_iter(90)`. 4. Reflect: why would calling `fib_rec(90)` be a terrible idea even though `fib_iter(90)` is instant? (The recursive version makes an exponential number of calls.) Solution ```rust 1 // O(2^n): recomputes the same subproblems exponentially many times. 2 fn fib_rec(n: u32) -> u64 { 3 match n { 4 0 => 0, 5 1 => 1, 6 _ => fib_rec(n - 1) + fib_rec(n - 2), 7 } 8 } 9 10 // O(n) time, O(1) stack: tabulate with two rolling accumulators. 11 fn fib_iter(n: u32) -> u64 { 12 let (mut a, mut b) = (0u64, 1u64); 13 for _ in 0..n { 14 let next = a + b; 15 a = b; 16 b = next; 17 } 18 a 19 } 20 21 fn main() { 22 println!("fib_rec(10) = {}", fib_rec(10)); 23 println!("fib_iter(10) = {}", fib_iter(10)); 24 println!("fib_iter(90) = {}", fib_iter(90)); 25 } ``` **Verified output:** ```text 1 fib_rec(10) = 55 2 fib_iter(10) = 55 3 fib_iter(90) = 2880067194370816120 ``` `fib_rec`’s recursion *depth* is only about `n`, so it will not overflow the stack at `n = 90`, but its *number of calls* roughly doubles with each step, so `fib_rec(90)` would take longer than the age of the universe. The iterative version computes it instantly. This is the classic illustration of why, in Rust, iteration (or memoization) is the default for anything with overlapping subproblems. *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * A recursive function needs a **base case** and a **recursive case** that shrinks toward it — identical in concept to TypeScript. * Rust has a finite call stack and makes **no guarantee of tail-call optimization**; deep recursion **aborts the process** with a stack overflow that you cannot catch. * Slice patterns (`[first, rest @ ..]`) express “head and tail” by **borrowing**, not copying like JavaScript’s `[first, ...rest]`. * The idiomatic Rust default is **iteration** (loops and iterators) whenever recursion depth scales with input; reserve recursion for shallow, naturally recursive data like trees. * Recursive *data types* (a cons list) need heap indirection (`Box`) or the compiler rejects them with `error[E0072]: recursive type has infinite size`. # Rust Control Flow > Rust control flow is expression-based: if, match, and loop each return a value, replacing the ternary and switch. Plus no truthiness and exhaustive matching. You already write conditionals, loops, and `switch` statements every day in TypeScript and JavaScript. Rust gives you the same building blocks — `if`/`else`, `for`/`while`/`loop`, and the `match` expression — but with one defining shift. Rust control flow is **expression-based**, not statement-based. An `if`, a `match`, and even a `loop` each *evaluate to a value*, so they replace the ternary, the discriminated-union `switch`, and the “declare a `let` outside the loop and mutate it” pattern all at once. Two more rules ripple through everything: conditions must be a real `bool` (there is no truthiness), and `match` is exhaustive (the compiler refuses to let you forget a case). *** ## What You’ll Learn [Section titled “What You’ll Learn”](#what-youll-learn) * Treat `if`/`else` and `match` as **expressions** that produce values, replacing the ternary `? :` and `switch` * Write conditions as explicit `bool` tests. Rust has **no truthiness**, so `if count`, `if name`, and `if 0` are all compile errors * Iterate over **ranges** (`0..n`, `1..=n`) and collections instead of a C-style `for` counter, which Rust does not have * Use the dedicated infinite `loop` and return a value from it with `break value` * Replace `switch` with `match`: exhaustiveness, the `_` catch-all, guards, `|` alternatives, ranges, `@` bindings, and destructuring, with no fall-through * Reach for the lightweight `if let`, `while let`, and `let ... else` when only one case matters * Control nested loops with **labeled loops** (`'outer:`) and understand how they differ from JavaScript labels *** ## Topics [Section titled “Topics”](#topics) | # | Topic | What it covers | | - | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | 1 | [Conditionals](/04-control-flow/00-conditionals/) | `if`/`else`; the ternary becomes `if` as an **expression**; no truthiness (conditions are `bool` only); an `if let` teaser | | 2 | [Loops](/04-control-flow/01-loops/) | `for` over ranges/iterators, `while`, the infinite `loop`; `loop` returning a value via `break`; why there is **no C-style `for`** | | 3 | [`match`](/04-control-flow/02-match/) | `switch` becomes `match`: exhaustiveness, the `_` arm, guards, `\|` patterns, ranges, the `@` binding, and destructuring | | 4 | [`if let` / `while let`](/04-control-flow/03-if-let-while-let/) | Concise pattern matching: `if let` / `else`, `while let`, and `let ... else` for early-return guard clauses | | 5 | [`break` and `continue`](/04-control-flow/04-break-continue/) | `break`/`continue`; `break` carrying a value out of a `loop`; `continue` in `for` and `while` | | 6 | [Labeled Loops](/04-control-flow/05-labeled-loops/) | Labeled loops (`'outer:`); `break`/`continue` to a label; nested-loop control versus JavaScript labels | *** ## Learning Objectives [Section titled “Learning Objectives”](#learning-objectives) After completing this section, a TypeScript/JavaScript developer should be able to: 1. Bind the result of an `if`/`else` or `match` directly to a `let`, and explain why both arms must unify to a single type. 2. Rewrite any truthiness-based condition (`if (x)`, `while (queue.length)`) as an explicit `bool` test (`if x != 0`, `while !queue.is_empty()`). 3. Translate a C-style `for (let i = 0; i < n; i++)` into a range loop, and use `.enumerate()` when an index is genuinely needed. 4. Produce a value from a loop with `loop { ... break value; }` instead of a pre-declared mutable variable. 5. Replace a `switch` with an exhaustive `match`, using `|`, ranges, guards, `@`, and destructuring, and know why `match` never falls through. 6. Choose the lightest pattern-matching tool: `if let`, `while let`, `let ... else`, or a full `match`. 7. Use `break 'label` / `continue 'label` to control nested loops, and recognize when an iterator method is clearer. *** ## Prerequisites [Section titled “Prerequisites”](#prerequisites) This section assumes you have completed: * **[Section 03: Functions](/03-functions/)**: the `fn` signature, typed parameters, and especially the **tail expression** (a block’s last line, with no semicolon, becomes its value). That expression-as-value rule is exactly what makes `if`, `match`, and `loop` usable as values here. If the expression-oriented model (`let x = { ...; a + b };`) or the statement-vs-expression distinction feels unfamiliar, revisit [Section 02 — Variables and Mutability](/02-basics/00-variables/) before starting. > **Note:** A few topics here *preview* concepts covered fully later. `Option` and the `?` operator are introduced in [Section 08 — Error Handling](/08-error-handling/); the borrow-checker rules behind “you can’t mutate a collection while iterating it” belong to [Section 05 — Ownership](/05-ownership/); and the full iterator toolbox (`map`/`filter`/`find`/`take_while`) lives in [Section 07 — Collections](/07-collections/). You do not need those sections first; the links are there for when you want to go deeper. > **Tip:** The apostrophe in a loop label (`'outer:`) is the same sigil Rust uses for **lifetimes** in [Section 05 — Ownership](/05-ownership/04-lifetimes/). They are unrelated concepts that share punctuation; the compiler always knows which one you mean from context. *** ## Estimated Time [Section titled “Estimated Time”](#estimated-time) * **Reading:** 2.5-3.5 hours * **Hands-on Practice & Exercises:** 2.5-3.5 hours * **Total:** 5-7 hours A reasonable order is the list order above: conditionals → loops → `match` → `if let`/`while let` → `break`/`continue` → labeled loops. `match` (topic 3) is the conceptual heart of the section — it underpins `if let`, `while let`, and `let ... else` — so do not skip it. *** ## Frequently asked questions [Section titled “Frequently asked questions”](#frequently-asked-questions) ### Does Rust have a ternary operator? [Section titled “Does Rust have a ternary operator?”](#does-rust-have-a-ternary-operator) No, because `if` is itself an expression: `let max = if a > b { a } else { b };`. Both branches must produce the same type. This replaces `cond ? a : b` and removes the need for a separate ternary. See [Conditionals](/04-control-flow/00-conditionals/). ### How is `match` different from `switch`? [Section titled “How is match different from switch?”](#how-is-match-different-from-switch) `match` is exhaustive (the compiler rejects unhandled cases), has no fall-through, returns a value, and can destructure and bind variables. A forgotten case is a compile error, not a silent bug. See [Match](/04-control-flow/02-match/). ### How do I write a C-style `for (i = 0; …)` loop? [Section titled “How do I write a C-style for (i = 0; …) loop?”](#how-do-i-write-a-c-style-for-i--0--loop) You iterate a range instead: `for i in 0..n`. To count down use `(0..n).rev()`, and to get the index while iterating use `.enumerate()`. Rust has no three-clause `for`. See [Loops](/04-control-flow/01-loops/). # Conditionals: `if` / `else` > Rust has no ternary because if is an expression: let x = if cond { a } else { b }. Conditions must be a real bool, since Rust, unlike JavaScript, has no truthiness. In TypeScript, `if` is a **statement** and the ternary `? :` is the only conditional that produces a value. In Rust, there is no ternary at all, because `if` itself is an **expression** that produces a value. This single shift, plus the fact that Rust conditions must be a real `bool` (no “truthiness”), is most of what you need to know. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) Rust’s `if`/`else` looks almost identical to TypeScript’s, but two things are different in ways that matter every day: * **`if` is an expression.** It evaluates to a value, so `let x = if cond { a } else { b };` replaces the ternary `cond ? a : b`. No separate `?:` operator exists. * **Conditions must be `bool`.** There is no truthiness. `if 0`, `if ""`, `if someObject` — all of those are *compile errors* in Rust. You write the comparison explicitly. If you internalize “the condition is always a `bool`, and the whole `if` can be a value,” you already understand 90% of Rust conditionals. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // A small shipping-cost calculator, the way you'd write it in TS. 2 function shippingCost(weightKg: number, isMember: boolean): number { 3 let cost: number; 4 5 // if/else statement: assigns into a pre-declared `let`. 6 if (weightKg > 20) { 7 cost = 25; 8 } else if (weightKg > 5) { 9 cost = 12; 10 } else { 11 cost = 5; 12 } 13 14 // Ternary expression: the ONLY conditional in JS that yields a value. 15 const discount = isMember ? cost * 0.1 : 0; 16 17 return cost - discount; 18 } 19 20 // Truthiness: JS coerces non-booleans in a condition. 21 function describe(name?: string) { 22 if (name) { 23 // "" , undefined, null, 0, NaN are all falsy 24 console.log(`Hello, ${name}`); 25 } else { 26 console.log("Hello, stranger"); 27 } 28 } 29 30 console.log(shippingCost(3, true)); // 4.5 31 console.log(shippingCost(8, false)); // 12 32 describe("Ada"); // Hello, Ada 33 describe(""); // Hello, stranger (empty string is falsy) ``` Two TypeScript habits to notice: the `let cost` declared-then-assigned pattern, and `if (name)` relying on the empty string being **falsy**. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 // The same calculator, written idiomatically in Rust. 2 fn shipping_cost(weight_kg: f64, is_member: bool) -> f64 { 3 // `if` is an EXPRESSION: it evaluates to a value we bind with `let`. 4 // This replaces the declared-then-assigned `let cost` pattern. 5 let cost = if weight_kg > 20.0 { 6 25.0 7 } else if weight_kg > 5.0 { 8 12.0 9 } else { 10 5.0 11 }; 12 13 // This `if`-expression is Rust's ternary. There is no `? :` operator. 14 let discount = if is_member { cost * 0.1 } else { 0.0 }; 15 16 cost - discount 17 } 18 19 // No truthiness: the condition must be an explicit `bool`. 20 fn describe(name: &str) { 21 if !name.is_empty() { 22 println!("Hello, {name}"); 23 } else { 24 println!("Hello, stranger"); 25 } 26 } 27 28 fn main() { 29 println!("{}", shipping_cost(3.0, true)); // 4.5 30 println!("{}", shipping_cost(8.0, false)); // 12 31 describe("Ada"); // Hello, Ada 32 describe(""); // Hello, stranger 33 } ``` Real output from `cargo run`: ```text 1 4.5 2 12 3 Hello, Ada 4 Hello, stranger ``` > **Note:** The `cost` binding is computed once by the whole `if`-expression. There is no mutable, uninitialized variable waiting to be filled in. This is more than a style preference: the compiler *knows* `cost` is always initialized to exactly one value, which is why it never needs `let mut`. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### `if` is an expression, not a statement [Section titled “if is an expression, not a statement”](#if-is-an-expression-not-a-statement) In TypeScript, an `if` block does not evaluate to anything; you reach a value either by mutating an outer variable or by using the ternary. In Rust, the `if`/`else` construct *is* a value: ```rust 1 fn main() { 2 let score = 72; 3 4 // The whole `if`/`else` evaluates to one of the two branch values. 5 let grade = if score >= 60 { "pass" } else { "fail" }; 6 7 println!("grade = {grade}"); // grade = pass 8 } ``` The key mechanic: a block `{ ... }` in Rust evaluates to its **last expression** when that expression has **no trailing semicolon**. So `{ "pass" }` is a block whose value is `"pass"`. Each arm of the `if` is such a block, and the `if` takes the value of whichever arm runs. This is the same block-as-value rule you saw with function bodies in [Section 03 — Functions](/03-functions/02-return-values/), now applied to control flow. (Expressions vs. statements were introduced back in [Section 02 — Variables](/02-basics/00-variables/).) A block arm can contain multiple lines; only the final unterminated expression is the value: ```rust 1 fn main() { 2 let n = 7; 3 4 let label = if n % 2 == 0 { 5 let kind = "even"; // intermediate statement (note the semicolon) 6 format!("{n} is {kind}") // final expression -> the block's value 7 } else { 8 format!("{n} is odd") 9 }; 10 11 println!("{label}"); // 7 is odd 12 } ``` ### Why there is no ternary operator [Section titled “Why there is no ternary operator”](#why-there-is-no-ternary-operator) Because `if` is already an expression, a separate `? :` would be redundant. The Rust form is slightly longer than `cond ? a : b`, but it reads the same and, importantly, it is the *exact same construct* whether you use one branch or ten. You never switch syntaxes between “I want a value” and “I want side effects.” ```rust 1 fn main() { 2 let is_member = true; 3 let cost = 100.0; 4 5 // TS: const discount = isMember ? cost * 0.1 : 0; 6 let discount = if is_member { cost * 0.1 } else { 0.0 }; 7 8 println!("{discount}"); // 10 9 } ``` ### Both arms must have the same type [Section titled “Both arms must have the same type”](#both-arms-must-have-the-same-type) Since an `if`-expression produces a single value, every branch that can be reached must produce the **same type**. The compiler unifies the arm types; if they disagree, it is a compile error (see Common Pitfalls). TypeScript’s ternary, by contrast, happily produces a *union* type like `string | number`. ### No truthiness — the condition is always `bool` [Section titled “No truthiness — the condition is always bool”](#no-truthiness--the-condition-is-always-bool) In JavaScript, `if (x)` runs `Boolean(x)` for you: `0`, `""`, `null`, `undefined`, `NaN` are falsy; everything else is truthy. Rust does **none** of this. The condition between `if` and `{` must already be of type `bool`. You make the comparison explicit: ```rust 1 fn main() { 2 let count = 3; 3 let name = "Bob"; 4 5 // JS: if (count) -> Rust: compare to get a bool 6 if count != 0 { 7 println!("count is non-zero"); 8 } 9 10 // JS: if (name) -> Rust: ask the value a yes/no question 11 if !name.is_empty() { 12 println!("name has {} chars", name.len()); 13 } 14 } ``` Output: ```text 1 count is non-zero 2 name has 3 chars ``` This is verbose at first, but it removes an entire class of bugs (the infamous `if (count)` that silently skips when `count` is legitimately `0`). ### `if let` — a teaser for pattern matching [Section titled “if let — a teaser for pattern matching”](#if-let--a-teaser-for-pattern-matching) Rust has a second flavor of `if` that pattern-matches and binds in one step. You will reach for it constantly with `Option` (Rust’s `null`-free alternative; see [Section 02 — Types](/02-basics/01-types/)): ```rust 1 fn main() { 2 let maybe_user: Option<&str> = Some("ada"); 3 4 // "If this Option is a `Some`, bind its inner value to `user`." 5 if let Some(user) = maybe_user { 6 println!("logged in as {user}"); 7 } else { 8 println!("anonymous"); 9 } 10 } ``` Output: ```text 1 logged in as ada ``` Think of `if let` as the conditional cousin of TypeScript’s `if (user !== undefined)` narrowing, except it also **unwraps** the value for you in the same line. This is just a preview; the full treatment (including `while let` and `let ... else`) lives in [if let / while let](/04-control-flow/03-if-let-while-let/), and the general pattern-matching tool is [match](/04-control-flow/02-match/). *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Aspect | TypeScript / JavaScript | Rust | | ------------------------------- | -------------------------------------------------- | --------------------------------------------- | | Is `if` a value? | No. `if` is a statement; only `? :` yields a value | Yes. `if` is an expression | | Ternary operator | `cond ? a : b` | None; use `if cond { a } else { b }` | | Condition type | Any value (coerced via truthiness) | Must be exactly `bool` | | `if (0)` / `if ("")` | Runs the else branch (falsy) | Compile error: `expected bool` | | Branch result types | May differ — produces a union (`string \| number`) | Must unify to one type | | `if` without `else` as a value | Ternary requires both sides | `()` only — can’t bind to a typed `let` | | Parentheses around condition | Required: `if (x > 0)` | Omitted: `if x > 0` (braces required) | | Chained comparison `0 < x < 10` | Allowed (and usually a bug) | Compile error — must write `0 < x && x < 10` | ### Why Rust makes these choices [Section titled “Why Rust makes these choices”](#why-rust-makes-these-choices) * **Expression-orientation** means fewer mutable, half-initialized variables. The value and the decision that produces it live in one place. * **No truthiness** means the compiler can guarantee a condition is a genuine boolean question, never an accidental coercion. The cost, a slightly longer `x != 0`, is paid once at write time and saves you at debug time. * **Type unification across arms** is what makes `if` usable as an expression at all: a value has to have *one* type. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Using a number or string as a condition (expecting truthiness) [Section titled “Pitfall 1: Using a number or string as a condition (expecting truthiness)”](#pitfall-1-using-a-number-or-string-as-a-condition-expecting-truthiness) ```rust 1 fn main() { 2 let count = 3; 3 if count { 4 // does not compile (error[E0308]: mismatched types) 5 println!("non-zero"); 6 } 7 } ``` Real compiler output: ```text 1 error[E0308]: mismatched types 2 --> src/main.rs:3:8 3 | 4 3 | if count { 5 | ^^^^^ expected `bool`, found integer ``` The same happens with strings: `if name { ... }` reports `expected bool, found &str`. **Fix:** write the comparison you actually mean: `if count != 0` or `if !name.is_empty()`. ### Pitfall 2: `if` and `else` arms with different types [Section titled “Pitfall 2: if and else arms with different types”](#pitfall-2-if-and-else-arms-with-different-types) ```rust 1 fn main() { 2 let score = 72; 3 // does not compile (error[E0308]: `if` and `else` have incompatible types) 4 let result = if score >= 60 { "pass" } else { 0 }; 5 println!("{result:?}"); 6 } ``` Real compiler output: ```text 1 error[E0308]: `if` and `else` have incompatible types 2 --> src/main.rs:3:51 3 | 4 3 | let result = if score >= 60 { "pass" } else { 0 }; 5 | ------ ^ expected `&str`, found integer 6 | | 7 | expected because of this ``` Unlike a TypeScript ternary (which would infer `string | number`), Rust needs a single type. **Fix:** make both arms the same type, or model the two cases with an `enum` and `match` (see [match](/04-control-flow/02-match/)). ### Pitfall 3: Using a bare `if` (no `else`) as an expression [Section titled “Pitfall 3: Using a bare if (no else) as an expression”](#pitfall-3-using-a-bare-if-no-else-as-an-expression) ```rust 1 fn main() { 2 let score = 72; 3 // does not compile (error[E0317]: `if` may be missing an `else` clause) 4 let grade = if score >= 60 { "pass" }; 5 println!("{grade:?}"); 6 } ``` Real compiler output: ```text 1 error[E0317]: `if` may be missing an `else` clause 2 --> src/main.rs:3:17 3 | 4 3 | let grade = if score >= 60 { "pass" }; 5 | ^^^^^^^^^^^^^^^^^------^^ 6 | | | 7 | | found here 8 | expected `&str`, found `()` 9 | 10 = note: `if` expressions without `else` evaluate to `()` 11 = help: consider adding an `else` block that evaluates to the expected type ``` An `if` with no `else` evaluates to the unit type `()` (Rust’s “nothing” — comparable to `void`). You can only bind such an `if` to nothing, not to a `&str`. **Fix:** add an `else` arm that returns the same type, or restructure (often a `match` or a default value). ### Pitfall 4: Trying to chain comparisons like in math [Section titled “Pitfall 4: Trying to chain comparisons like in math”](#pitfall-4-trying-to-chain-comparisons-like-in-math) ```rust 1 fn main() { 2 let x = 5; 3 // does not compile (error: comparison operators cannot be chained) 4 let ok = 0 < x < 10; 5 println!("{ok}"); 6 } ``` Real compiler output: ```text 1 error: comparison operators cannot be chained 2 --> src/main.rs:3:16 3 | 4 3 | let ok = 0 < x < 10; 5 | ^ ^ 6 | 7 help: split the comparison into two 8 | 9 3 | let ok = 0 < x && x < 10; 10 | ++++ ``` JavaScript *allows* `0 < x < 10`, but it means `(0 < x) < 10` → `true < 10` → `1 < 10` → `true`, which is almost never what you want. Rust rejects it outright. **Fix:** `0 < x && x < 10`, or the more readable `(0..10).contains(&x)` for ranges. (See [Operators](/02-basics/02-operators/) for the logical operators.) ### Pitfall 5: Putting a semicolon after a branch value [Section titled “Pitfall 5: Putting a semicolon after a branch value”](#pitfall-5-putting-a-semicolon-after-a-branch-value) ```rust 1 fn main() { 2 let n = 4; 3 // Adding `;` turns the branch into a statement that yields `()`, 4 // so both arms become `()` and `kind` is `()` — not what you wanted. 5 let _kind = if n % 2 == 0 { 6 "even"; // ← stray semicolon discards the value 7 } else { 8 "odd"; 9 }; 10 // _kind is now (), not "even" 11 } ``` This *compiles* (both arms are `()`), so it is a silent logic bug rather than a hard error. **Fix:** drop the semicolons inside the arms so each block’s final expression becomes its value. *** ## Best Practices [Section titled “Best Practices”](#best-practices) * **Prefer `let x = if ... { } else { };` over a mutable, assigned-later variable.** It keeps the binding immutable and proves to the reader (and compiler) that exactly one value is chosen. * **Make conditions read as questions.** `if !name.is_empty()`, `if count > 0`, `if user.is_some()`. Method calls like `.is_empty()`, `.is_some()`, `.contains(&x)` are idiomatic and clearer than re-deriving truthiness. * **Reach for `match` once you have three-plus mutually exclusive branches** on the same value — it gives you exhaustiveness checking that `else if` chains don’t. See [match](/04-control-flow/02-match/). * **Use `if let` instead of an `if` + manual unwrap** when you only care about one variant of an `Option`/`Result`. It’s shorter and avoids panics. See [if let / while let](/04-control-flow/03-if-let-while-let/). * **Keep arm types identical and small.** If arms want to return different shapes, that’s a signal to introduce an `enum`. * **Don’t wrap conditions in parentheses.** `if (x > 0)` compiles but `clippy` will nudge you toward `if x > 0`; the braces already delimit the body. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A retry policy for an HTTP client: classify a response by status code and decide whether to succeed, retry with backoff, or give up. Every decision is driven by `if`/`else` expressions, and every condition is an explicit `bool`. ```rust 1 #[derive(Debug)] 2 enum RetryDecision { 3 Succeed, 4 RetryAfter(u64), // milliseconds to wait before retrying 5 Fail, 6 } 7 8 fn classify(status: u16, attempt: u32) -> RetryDecision { 9 // No truthiness: each condition is a concrete boolean test. 10 // `(200..300).contains(&status)` reads better than `status >= 200 && status < 300`. 11 if (200..300).contains(&status) { 12 RetryDecision::Succeed 13 } else if status == 429 || (500..600).contains(&status) { 14 // `if` as an expression computes exponential backoff inline, 15 // capping it after a few attempts. 16 let backoff = if attempt < 5 { 100 * 2u64.pow(attempt) } else { 3200 }; 17 RetryDecision::RetryAfter(backoff) 18 } else { 19 RetryDecision::Fail 20 } 21 } 22 23 fn main() { 24 let responses = [(200, 0), (404, 0), (503, 2), (429, 6)]; 25 26 for (status, attempt) in responses { 27 match classify(status, attempt) { 28 RetryDecision::Succeed => println!("status {status}: ok"), 29 RetryDecision::RetryAfter(ms) => println!("status {status}: retry in {ms} ms"), 30 RetryDecision::Fail => println!("status {status}: giving up"), 31 } 32 } 33 } ``` Real output from `cargo run`: ```text 1 status 200: ok 2 status 404: giving up 3 status 503: retry in 400 ms 4 status 429: retry in 3200 ms ``` Notice how the inner `let backoff = if ... { ... } else { ... };` and the outer `if`/`else if`/`else` are the same construct used at two scales, and how `(200..300).contains(&status)` replaces a truthiness-laden range check. The `match` at the end is a preview of the next tool in your control-flow toolbox. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book – Control Flow (`if` expressions)](https://doc.rust-lang.org/book/ch03-05-control-flow.html#if-expressions) * [The Rust Book – `if let` Concise Control Flow](https://doc.rust-lang.org/book/ch06-03-if-let.html) * [Rust Reference – `if` and `if let` expressions](https://doc.rust-lang.org/reference/expressions/if-expr.html) * [Rust by Example – `if`/`else`](https://doc.rust-lang.org/rust-by-example/flow_control/if_else.html) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Loops](/04-control-flow/01-loops/) — `for`, `while`, and the value-returning `loop`; why there is no C-style `for`. * [`match`](/04-control-flow/02-match/) — the exhaustive, pattern-matching successor to `switch` and long `else if` chains. * [`if let` / `while let`](/04-control-flow/03-if-let-while-let/) — the full story behind the `if let` teaser above, plus `let ... else`. * [Variables and Mutability](/02-basics/00-variables/) — expressions vs. statements, and why immutable bindings pair so well with `if`-expressions. * [Operators](/02-basics/02-operators/) — the comparison and logical operators that build your conditions. * [Section 05 — Ownership](/05-ownership/) — how values flow out of `if` branches once types get more complex than `&str`. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Ternary to `if`-expression [Section titled “Exercise 1: Ternary to if-expression”](#exercise-1-ternary-to-if-expression) **Difficulty:** Easy **Objective:** Translate a TypeScript ternary into an idiomatic Rust `if`-expression. **Instructions:** The TypeScript below returns `"adult"` or `"minor"`. Implement the Rust `category` function so it returns the same `&str`, binding the result of a single `if`-expression. Do not use a mutable variable. ```typescript 1 // TypeScript original 2 function category(age: number): string { 3 return age >= 18 ? "adult" : "minor"; 4 } ``` ```rust 1 fn category(age: u32) -> &'static str { 2 // TODO: return "adult" if age >= 18, otherwise "minor" 3 /* ??? */ 4 } 5 6 fn main() { 7 println!("{}", category(20)); // adult 8 println!("{}", category(12)); // minor 9 } ``` Solution ```rust 1 fn category(age: u32) -> &'static str { 2 if age >= 18 { "adult" } else { "minor" } 3 } 4 5 fn main() { 6 println!("{}", category(20)); // adult 7 println!("{}", category(12)); // minor 8 } ``` The whole `if`/`else` is the function’s tail expression, so it becomes the return value: no `return` keyword and no mutable variable needed. Output: ```text 1 adult 2 minor ``` ### Exercise 2: Kill the truthiness [Section titled “Exercise 2: Kill the truthiness”](#exercise-2-kill-the-truthiness) **Difficulty:** Medium **Objective:** Convert truthiness-based JavaScript conditions into explicit Rust `bool` tests, using nested `if`-expressions for a tiered result. **Instructions:** Port this JS pricing function to Rust. A `"premium"` role pays no base fee; everyone else pays `5`. On top of that, add a surcharge of `20` for more than 100 monthly orders, `10` for more than 10, and `0` otherwise. Return the total as a `u32`. ```javascript 1 // JavaScript original (relies on string comparison, not truthiness here, 2 // but watch the branching) 3 function feeFor(role, monthlyOrders) { 4 const base = role === "premium" ? 0 : 5; 5 let surcharge; 6 if (monthlyOrders > 100) surcharge = 20; 7 else if (monthlyOrders > 10) surcharge = 10; 8 else surcharge = 0; 9 return base + surcharge; 10 } ``` ```rust 1 fn fee_for(role: &str, monthly_orders: u32) -> u32 { 2 // TODO: compute base and surcharge with if-expressions, then sum them 3 /* ??? */ 4 } 5 6 fn main() { 7 println!("{}", fee_for("premium", 150)); // 20 8 println!("{}", fee_for("free", 50)); // 15 9 println!("{}", fee_for("free", 3)); // 5 10 } ``` Solution ```rust 1 fn fee_for(role: &str, monthly_orders: u32) -> u32 { 2 let base = if role == "premium" { 0 } else { 5 }; 3 let surcharge = if monthly_orders > 100 { 4 20 5 } else if monthly_orders > 10 { 6 10 7 } else { 8 0 9 }; 10 base + surcharge 11 } 12 13 fn main() { 14 println!("{}", fee_for("premium", 150)); // 20 15 println!("{}", fee_for("free", 50)); // 15 16 println!("{}", fee_for("free", 3)); // 5 17 } ``` Both `base` and `surcharge` are immutable bindings produced by `if`-expressions; no `let mut surcharge;` declared-then-assigned dance. Output: ```text 1 20 2 15 3 5 ``` ### Exercise 3: `if let` instead of a default value [Section titled “Exercise 3: if let instead of a default value”](#exercise-3-if-let-instead-of-a-default-value) **Difficulty:** Medium **Objective:** Use the `if let` teaser to greet a user by name when one is present, falling back to a guest greeting otherwise. **Instructions:** Implement `greet` so that, given `Some(name)`, it returns `"Welcome back, {name}!"`, and given `None`, it returns `"Welcome, guest!"`. Use `if let ... else`, and make the whole thing an expression that produces the returned `String`. ```rust 1 fn greet(config: Option<&str>) -> String { 2 // TODO: if there's a name, welcome them back by name; otherwise greet a guest 3 /* ??? */ 4 } 5 6 fn main() { 7 println!("{}", greet(Some("Ada"))); 8 println!("{}", greet(None)); 9 } ``` Solution ```rust 1 fn greet(config: Option<&str>) -> String { 2 if let Some(name) = config { 3 format!("Welcome back, {name}!") 4 } else { 5 "Welcome, guest!".to_string() 6 } 7 } 8 9 fn main() { 10 println!("{}", greet(Some("Ada"))); 11 println!("{}", greet(None)); 12 } ``` `if let Some(name) = config` both *tests* that `config` is a `Some` and *binds* its inner value to `name` in one step: the conditional cousin of TypeScript’s `if (config !== undefined)` narrowing, but it unwraps for you. Both arms produce a `String`, so the `if let`/`else` is itself the returned expression. Output: ```text 1 Welcome back, Ada! 2 Welcome, guest! ``` For the deeper dive on `if let`, `while let`, and `let ... else`, head to [if let / while let](/04-control-flow/03-if-let-while-let/). # Loops > Rust drops the C-style for entirely: you iterate ranges and collections, not counters. for, while, and loop cover it, and loop can return a value via break value. Rust gives you three loop keywords (`for`, `while`, and `loop`), but the way you actually iterate is different from JavaScript. You loop over **ranges** and **iterators** instead of incrementing a counter by hand, and `loop` is a real expression that can hand a value back. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) In TypeScript/JavaScript you reach for `for (let i = 0; i < n; i++)` constantly. Rust has **no C-style `for`** at all. Instead you iterate over a **range** (`0..n`) or directly over the elements of a collection, which eliminates an entire class of off-by-one and out-of-bounds bugs. Rust also has `while` (same as you know it) and a dedicated infinite `loop` keyword that — uniquely — can produce a value with `break value`. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // The four loop shapes a TS/JS dev reaches for every day. 2 const scores = [88, 92, 75]; 3 4 // 1. Classic C-style index loop 5 for (let i = 0; i < scores.length; i++) { 6 console.log(`index ${i}: ${scores[i]}`); 7 } 8 9 // 2. for...of over the values 10 for (const score of scores) { 11 console.log(`score ${score}`); 12 } 13 14 // 3. while with a manual counter 15 let countdown = 3; 16 while (countdown > 0) { 17 console.log(`T-minus ${countdown}`); 18 countdown--; 19 } 20 21 // 4. "Infinite" loop with break (often hides a return value in a mutable var) 22 let n = 1; 23 let firstBig: number; 24 while (true) { 25 n *= 2; 26 if (n > 100) { 27 firstBig = n; 28 break; 29 } 30 } 31 console.log(`first power of two over 100: ${firstBig}`); ``` **Key characteristic:** the index loop is the default, and “loop until I find something” usually means mutating an outer variable and `break`-ing. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 fn main() { 2 let scores = [88, 92, 75]; 3 4 // 1. There is NO C-style for. To iterate by index, loop over a range: 5 for i in 0..scores.len() { 6 println!("index {i}: {}", scores[i]); 7 } 8 9 // 2. for over the values (the idiomatic default) 10 for score in &scores { 11 println!("score {score}"); 12 } 13 14 // 3. while with a manual counter — identical idea to JavaScript 15 let mut countdown = 3; 16 while countdown > 0 { 17 println!("T-minus {countdown}"); 18 countdown -= 1; 19 } 20 21 // 4. `loop` is an expression: `break value` hands a value back out. 22 let mut n = 1; 23 let first_big = loop { 24 n *= 2; 25 if n > 100 { 26 break n; // <-- the loop *evaluates* to this value 27 } 28 }; 29 println!("first power of two over 100: {first_big}"); 30 } ``` ```text 1 index 0: 88 2 index 1: 92 3 index 2: 75 4 score 88 5 score 92 6 score 75 7 T-minus 3 8 T-minus 2 9 T-minus 1 10 first power of two over 100: 128 ``` **Key characteristic:** iterate over ranges/collections, not hand-rolled counters; and `loop` can be the right-hand side of a `let`. > **Note:** `0..scores.len()` is shown here only to make the comparison concrete. In real Rust you almost never index like this; prefer `for score in &scores`. See [Common Pitfalls](#common-pitfalls). *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### `for` iterates over an iterator, never a counter [Section titled “for iterates over an iterator, never a counter”](#for-iterates-over-an-iterator-never-a-counter) In Rust, `for pattern in expression { ... }` requires `expression` to be something that can turn into an **iterator** (anything implementing `IntoIterator`). Each pass binds the next item to `pattern`. There is no init/condition/increment triple, because there is no counter; the iterator decides when it is exhausted. The most common iterables: ```rust 1 fn main() { 2 // A range: 1, 2, 3 (the end is EXCLUSIVE) 3 for i in 1..4 { 4 println!("range {i}"); 5 } 6 7 // An inclusive range with ..= : 1, 2, 3 8 for i in 1..=3 { 9 println!("inclusive {i}"); 10 } 11 12 // An array/slice/Vec, borrowed with & 13 let names = ["Alice", "Bob", "Carol"]; 14 for name in &names { 15 println!("name {name}"); 16 } 17 } ``` ```text 1 range 1 2 range 2 3 range 3 4 inclusive 1 5 inclusive 2 6 inclusive 3 7 name Alice 8 name Bob 9 name Carol ``` A **range** like `1..4` is the closest thing to `for (let i = 1; i < 4; i++)`. Note the boundary: `start..end` **excludes** `end` (like `Array.prototype.slice`), while `start..=end` **includes** it. JavaScript has no range literal at all; the nearest equivalents are `Array.from({length: n}, (_, i) => i)` or a manual counter. ### `.enumerate()` when you genuinely need the index [Section titled “.enumerate() when you genuinely need the index”](#enumerate-when-you-genuinely-need-the-index) If you want both the index and the value — the legitimate reason a TS/JS dev uses the C-style loop — call `.enumerate()` on the iterator. It yields `(index, value)` tuples, which you destructure right in the `for` pattern: ```rust 1 fn main() { 2 let names = ["Alice", "Bob", "Carol"]; 3 for (index, name) in names.iter().enumerate() { 4 println!("{index}: {name}"); 5 } 6 } ``` ```text 1 0: Alice 2 1: Bob 3 2: Carol ``` This is the Rust equivalent of JavaScript’s `array.forEach((value, index) => ...)` or `for (const [index, value] of array.entries())`. ### Iterator adapters give you ranges JavaScript can’t express in a header [Section titled “Iterator adapters give you ranges JavaScript can’t express in a header”](#iterator-adapters-give-you-ranges-javascript-cant-express-in-a-header) Because the thing after `in` is just an iterator, you compose adapters instead of editing a loop header: ```rust 1 fn main() { 2 // Count down: rev() reverses any iterator that supports it 3 for i in (0..3).rev() { 4 println!("rev {i}"); 5 } 6 7 // Step by 3: 0, 3, 6, 9 — like `for (i = 0; i < 10; i += 3)` 8 for i in (0..10).step_by(3) { 9 println!("step {i}"); 10 } 11 } ``` ```text 1 rev 2 2 rev 1 3 rev 0 4 step 0 5 step 3 6 step 6 7 step 9 ``` > **Tip:** `rev()` and `step_by()` replace the `i--` and `i += 3` you would write in a C-style loop header. The whole iterator toolbox (`filter`, `map`, `take`, `zip`, …) is covered in [Section 07 — Collections](/07-collections/). ### `while` is exactly what you expect [Section titled “while is exactly what you expect”](#while-is-exactly-what-you-expect) `while condition { ... }` runs the body while the condition is `true`. The only catch (covered in [conditionals](/04-control-flow/00-conditionals/)) is that the condition must be a real `bool`. There is no truthiness, so `while queue.length` does not compile; you write `while !queue.is_empty()`. ```rust 1 fn main() { 2 let mut count = 3; 3 while count > 0 { 4 println!("while {count}"); 5 count -= 1; 6 } 7 } ``` ```text 1 while 3 2 while 2 3 while 1 ``` ### `loop` is an infinite loop — and an expression [Section titled “loop is an infinite loop — and an expression”](#loop-is-an-infinite-loop--and-an-expression) `loop { ... }` repeats forever until you `break`. That alone is just `while (true)`. What is genuinely new for a JavaScript developer is that `loop` is an **expression**: `break value` makes the entire `loop` evaluate to `value`, so you can assign it to a binding. ```rust 1 fn main() { 2 let mut n = 1; 3 let result = loop { 4 n *= 2; 5 if n > 100 { 6 break n; // the loop produces 128 7 } 8 }; 9 println!("result {result}"); // result 128 10 } ``` ```text 1 result 128 ``` In JavaScript you simulate this by declaring a `let result;` outside the loop and assigning to it before `break`. In Rust the value flows *out of* the loop, so there is no uninitialized outer variable to forget about. (`while` and `for` cannot do this; they always evaluate to `()`, because they may run zero times, leaving no value to produce.) > **Note:** This is the same expression-orientation you saw with [`if`](/04-control-flow/00-conditionals/): in Rust, control-flow constructs are values in their own right, going beyond plain statements. Returning a value from `loop` via `break` and from named blocks is detailed in [break & continue](/04-control-flow/04-break-continue/). *** ## Key Differences from TypeScript [Section titled “Key Differences from TypeScript”](#key-differences-from-typescript) | Aspect | TypeScript/JavaScript | Rust | | --------------------------------- | -------------------------------- | --------------------------------------------------- | | C-style `for (init; cond; step)` | Yes, the default | **Does not exist** | | Iterate values | `for...of` | `for x in &collection` | | Iterate with index | `for (let i...)` or `.entries()` | `for (i, x) in coll.iter().enumerate()` | | Numeric range | `Array.from(...)` / manual | `start..end` (exclusive), `start..=end` (inclusive) | | Condition type | any value (truthy/falsy) | must be `bool` | | Infinite loop | `while (true)` | `loop { ... }` | | Loop produces a value | No (mutate an outer `let`) | `loop` can: `let x = loop { break v; };` | | Mutate collection while iterating | Allowed (often buggy) | Rejected by the borrow checker at compile time | | `for...in` over object keys | Yes | No equivalent; iterate a `HashMap` instead | ### Why no C-style `for`? [Section titled “Why no C-style for?”](#why-no-c-style-for) The C-style header is three independent pieces — initialization, a re-checked condition, and a mutation — that the programmer must keep in sync. Almost every classic loop bug (off-by-one, `<` vs `<=`, forgetting to increment, indexing past the end) lives in that header. Rust deletes the whole category by making you say *what you are iterating over* (a range or a collection) rather than *how to advance a counter*. The compiler then guarantees the index can never go out of bounds. ### Why does `loop` exist when `while true` would do? [Section titled “Why does loop exist when while true would do?”](#why-does-loop-exist-when-while-true-would-do) `loop` signals intent (“this runs until an explicit `break`”) and, importantly, the compiler treats it specially for **type inference and reachability**. Because `loop` has no condition, code after it is reachable only via `break`, so a `loop` with no `break` has type `!` (“never”), and a `loop { break v; }` can produce a value. `while true` does not get this treatment; it always has type `()`. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Writing a C-style `for` [Section titled “Pitfall 1: Writing a C-style for”](#pitfall-1-writing-a-c-style-for) A TS/JS dev’s fingers will type this automatically: ```rust 1 fn main() { 2 for (let mut i = 0; i < 5; i += 1) { // does not compile 3 println!("{i}"); 4 } 5 } ``` The real compiler error is a parse failure. Rust tries to read `(let mut i = 0; ...)` as a **pattern** to bind, not a loop header: ```text 1 error: expected pattern, found `let` 2 --> src/main.rs:2:10 3 | 4 2 | for (let mut i = 0; i < 5; i += 1) { 5 | ^^^ 6 | 7 help: remove the unnecessary `let` keyword ``` **Fix:** loop over a range. ```rust 1 fn main() { 2 for i in 0..5 { 3 println!("{i}"); 4 } 5 } ``` ### Pitfall 2: Reaching for `0..len` and indexing [Section titled “Pitfall 2: Reaching for 0..len and indexing”](#pitfall-2-reaching-for-0len-and-indexing) This compiles, but it is not idiomatic and reintroduces bounds checks and off-by-one risk: ```rust 1 fn main() { 2 let scores = [88, 92, 75]; 3 // Works, but un-idiomatic: 4 for i in 0..scores.len() { 5 println!("idx {}: {}", i, scores[i]); 6 } 7 } ``` **Fix:** iterate the elements directly; use `.enumerate()` only if you actually need the index. ```rust 1 fn main() { 2 let scores = [88, 92, 75]; 3 for score in &scores { 4 println!("score {score}"); 5 } 6 } ``` ### Pitfall 3: Off-by-one with `..` vs `..=` [Section titled “Pitfall 3: Off-by-one with .. vs ..=”](#pitfall-3-off-by-one-with--vs-) `1..4` yields `1, 2, 3` — **not** `4`. Coming from `for (i = 1; i <= 4; i++)`, you will reach for the wrong one. ```rust 1 fn main() { 2 let exclusive: Vec = (1..4).collect(); // [1, 2, 3] 3 let inclusive: Vec = (1..=4).collect(); // [1, 2, 3, 4] 4 println!("{exclusive:?} vs {inclusive:?}"); 5 } ``` ```text 1 [1, 2, 3] vs [1, 2, 3, 4] ``` > **Tip:** Read `..` as “up to” and `..=` as “up to and including”. The exclusive form matches `array.length` indexing perfectly: `0..arr.len()` covers exactly the valid indices. ### Pitfall 4: Mutating a collection while iterating over it [Section titled “Pitfall 4: Mutating a collection while iterating over it”](#pitfall-4-mutating-a-collection-while-iterating-over-it) In JavaScript, pushing to an array while looping over it is legal (and a common source of subtle bugs). Rust rejects it at compile time, because the `for` loop holds an **immutable borrow** of the collection while a `push` needs a **mutable** one: ```rust 1 fn main() { 2 let mut numbers = vec![1, 2, 3]; 3 for n in &numbers { 4 if *n == 2 { 5 numbers.push(99); // does not compile (error[E0502]) 6 } 7 } 8 } ``` ```text 1 error[E0502]: cannot borrow `numbers` as mutable because it is also borrowed as immutable 2 --> src/main.rs:5:13 3 | 4 3 | for n in &numbers { 5 | -------- 6 | | 7 | immutable borrow occurs here 8 | immutable borrow later used here 9 4 | if *n == 2 { 10 5 | numbers.push(99); // try to mutate while borrowed 11 | ^^^^^^^^^^^^^^^^ mutable borrow occurs here ``` **Fix:** collect the changes first, or build a new collection, then apply them after the loop ends. The rules behind this error are the subject of [Section 05 — Ownership](/05-ownership/). ```rust 1 fn main() { 2 let mut numbers = vec![1, 2, 3]; 3 let mut to_add = Vec::new(); 4 for n in &numbers { 5 if *n == 2 { 6 to_add.push(99); 7 } 8 } 9 numbers.extend(to_add); // mutate after the borrow ends 10 println!("{numbers:?}"); // [1, 2, 3, 99] 11 } ``` ### Pitfall 5: Using a value moved by `for` [Section titled “Pitfall 5: Using a value moved by for”](#pitfall-5-using-a-value-moved-by-for) `for x in collection` (without `&`) **consumes** the collection: each element is moved into `x`. Afterward the original is gone: ```rust 1 fn main() { 2 let owned = vec![String::from("a"), String::from("b")]; 3 for s in owned { // moves each String out of `owned` 4 println!("{s}"); 5 } 6 // println!("{}", owned.len()); // would not compile: `owned` was moved 7 } ``` **Fix:** borrow with `&` if you still need the collection after the loop: ```rust 1 fn main() { 2 let owned = vec![String::from("a"), String::from("b")]; 3 for s in &owned { // borrows; `owned` survives 4 println!("{s}"); 5 } 6 println!("still have {} items", owned.len()); // 7 } ``` *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Default to iterating values, not indices [Section titled “1. Default to iterating values, not indices”](#1-default-to-iterating-values-not-indices) ```rust 1 // index-heavy, easy to get wrong 2 for i in 0..items.len() { 3 process(&items[i]); 4 } 5 6 // clear and bounds-safe 7 for item in &items { 8 process(item); 9 } ``` ### 2. Use `.enumerate()` instead of a side counter [Section titled “2. Use .enumerate() instead of a side counter”](#2-use-enumerate-instead-of-a-side-counter) ```rust 1 // manual counter alongside the loop 2 let mut i = 0; 3 for line in &lines { 4 println!("{i}: {line}"); 5 i += 1; 6 } 7 8 // index comes from the iterator 9 for (i, line) in lines.iter().enumerate() { 10 println!("{i}: {line}"); 11 } ``` ### 3. Use `loop { break value }` instead of a sentinel variable [Section titled “3. Use loop { break value } instead of a sentinel variable”](#3-use-loop--break-value--instead-of-a-sentinel-variable) ```rust 1 // JavaScript-style: outer mutable var + while true 2 let mut found = -1; 3 let mut i = 0; 4 while i < 1000 { 5 if is_target(i) { found = i; break; } 6 i += 1; 7 } 8 9 // the loop produces the value directly 10 let found = loop { 11 let candidate = next_candidate(); 12 if is_target(candidate) { 13 break candidate; 14 } 15 }; ``` ### 4. Prefer iterator methods when you are computing a single result [Section titled “4. Prefer iterator methods when you are computing a single result”](#4-prefer-iterator-methods-when-you-are-computing-a-single-result) A `for` loop that just accumulates is often clearer as an iterator chain, and the compiler optimizes it just as well: ```rust 1 fn main() { 2 let scores = [88, 92, 75]; 3 4 // manual accumulation 5 let mut total = 0; 6 for s in &scores { 7 total += s; 8 } 9 10 // expresses intent directly 11 let total: i32 = scores.iter().sum(); 12 13 println!("{total}"); // 255 14 } ``` > **Note:** Reach for a `for` loop when the body has side effects or early exits; reach for iterator adapters (`map`/`filter`/`sum`/`collect`) when you are transforming data into a value. Both are idiomatic. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small slice of a job-runner: poll a backend until a job finishes (using `loop` + `break value`), render a text progress bar with a `for` range, then drain a work queue with `while`. ```rust 1 #[derive(Debug)] 2 struct Job { 3 id: u32, 4 status: &'static str, 5 } 6 7 // Pretend this calls an API. The job "completes" on the 3rd poll. 8 fn poll_job(attempt: u32) -> Job { 9 let status = if attempt >= 3 { "done" } else { "running" }; 10 Job { id: 42, status } 11 } 12 13 fn main() { 14 // `loop` + `break value`: poll until done, hand the finished job back out. 15 let mut attempt = 0; 16 let finished = loop { 17 attempt += 1; 18 let job = poll_job(attempt); 19 println!("attempt {attempt}: job {} is {}", job.id, job.status); 20 21 if job.status == "done" { 22 break job; // the loop evaluates to this Job 23 } 24 if attempt >= 5 { 25 break job; // give up after 5 tries 26 } 27 }; 28 println!("final: {finished:?}"); 29 30 // `for` over a range to render a 10-segment progress bar. 31 let percent = 60; 32 let filled = percent / 10; 33 let mut bar = String::new(); 34 for i in 0..10 { 35 bar.push(if i < filled { '#' } else { '-' }); 36 } 37 println!("[{bar}] {percent}%"); 38 39 // `while` to drain a stack of remaining tasks. 40 let mut queue = vec!["build", "test", "deploy"]; 41 while !queue.is_empty() { 42 let task = queue.pop().unwrap(); 43 println!("running {task}"); 44 } 45 } ``` ```text 1 attempt 1: job 42 is running 2 attempt 2: job 42 is running 3 attempt 3: job 42 is done 4 final: Job { id: 42, status: "done" } 5 [######----] 60% 6 running deploy 7 running test 8 running build ``` > **Note:** Draining the queue with `while let Some(task) = queue.pop()` is even more idiomatic than `while !queue.is_empty()` + `unwrap()`. That `while let` pattern is covered in [if let / while let](/04-control-flow/03-if-let-while-let/). *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Control Flow: Repetition with Loops](https://doc.rust-lang.org/book/ch03-05-control-flow.html#repetition-with-loops) * [The Rust Book — Processing a Series of Items with Iterators](https://doc.rust-lang.org/book/ch13-02-iterators.html) * [Rust by Example — Loops](https://doc.rust-lang.org/rust-by-example/flow_control.html) * [`std::ops::Range` documentation](https://doc.rust-lang.org/std/ops/struct.Range.html) * [`Iterator` trait (adapters like `enumerate`, `rev`, `step_by`)](https://doc.rust-lang.org/std/iter/trait.Iterator.html) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Conditionals](/04-control-flow/00-conditionals/): `if` as an expression; why loop/`while` conditions must be `bool` * [match](/04-control-flow/02-match/): the other major control-flow expression * [if let / while let](/04-control-flow/03-if-let-while-let/): concise pattern-driven looping (e.g. `while let Some(x) = ...`) * [break & continue](/04-control-flow/04-break-continue/): early exit, skipping iterations, and returning values from a `loop` * [Labeled loops](/04-control-flow/05-labeled-loops/): `break`/`continue` targeting an outer loop by label * [Section 02 — Basics](/02-basics/): variables, types, and operators used above * [Section 03 — Functions](/03-functions/): functions are also expression-based * [Section 05 — Ownership](/05-ownership/): the borrowing rules behind the “mutate while iterating” error * [Section 07 — Collections](/07-collections/): the full iterator toolbox *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: FizzBuzz with a range [Section titled “Exercise 1: FizzBuzz with a range”](#exercise-1-fizzbuzz-with-a-range) **Difficulty:** Easy **Objective:** Practice `for` over an inclusive range and combine it with `if`/`else if`. **Instructions:** Print the numbers `1` through `15`, but print `Fizz` for multiples of 3, `Buzz` for multiples of 5, and `FizzBuzz` for multiples of both. ```rust 1 fn main() { 2 for n in 1..=15 { 3 // TODO: print Fizz / Buzz / FizzBuzz / the number 4 } 5 } ``` Solution ```rust 1 fn main() { 2 for n in 1..=15 { 3 let line = if n % 15 == 0 { 4 "FizzBuzz".to_string() 5 } else if n % 3 == 0 { 6 "Fizz".to_string() 7 } else if n % 5 == 0 { 8 "Buzz".to_string() 9 } else { 10 n.to_string() 11 }; 12 println!("{line}"); 13 } 14 } ``` ```text 1 1 2 2 3 Fizz 4 4 5 Buzz 6 Fizz 7 7 8 8 9 Fizz 10 Buzz 11 11 12 Fizz 13 13 14 14 15 FizzBuzz ``` ### Exercise 2: Return a value from `loop` [Section titled “Exercise 2: Return a value from loop”](#exercise-2-return-a-value-from-loop) **Difficulty:** Medium **Objective:** Use `loop` + `break value` to produce a result, instead of mutating an outer variable. **Instructions:** Write `next_power_of_two_above(target: u32) -> u32` that returns the smallest power of two strictly greater than `target`. Use a `loop` that `break`s with the answer. (Start at `1` and keep doubling.) ```rust 1 fn next_power_of_two_above(target: u32) -> u32 { 2 // TODO: use `loop { ... break p; ... }` 3 } 4 5 fn main() { 6 println!("{}", next_power_of_two_above(100)); // 128 7 println!("{}", next_power_of_two_above(5)); // 8 8 } ``` Solution ```rust 1 fn next_power_of_two_above(target: u32) -> u32 { 2 let mut p = 1u32; 3 loop { 4 if p > target { 5 break p; // the loop evaluates to p, which the fn returns 6 } 7 p *= 2; 8 } 9 } 10 11 fn main() { 12 println!("{}", next_power_of_two_above(100)); // 128 13 println!("{}", next_power_of_two_above(5)); // 8 14 } ``` ```text 1 128 2 8 ``` > The `loop` expression is the function’s tail expression, so its `break` value becomes the return value, no `return` keyword needed. ### Exercise 3: Collatz step counter with `while` [Section titled “Exercise 3: Collatz step counter with while”](#exercise-3-collatz-step-counter-with-while) **Difficulty:** Medium **Objective:** Drive a `while` loop with a changing condition and mutate a counter. **Instructions:** Write `collatz_steps(n: u64) -> u32` that counts how many steps it takes to reach `1` under the Collatz rule: if `n` is even, halve it; if odd, compute `3 * n + 1`. Count each transformation. ```rust 1 fn collatz_steps(mut n: u64) -> u32 { 2 // TODO: loop with `while n != 1 { ... }` 3 } 4 5 fn main() { 6 println!("{}", collatz_steps(27)); // 111 7 println!("{}", collatz_steps(6)); // 8 8 } ``` Solution ```rust 1 fn collatz_steps(mut n: u64) -> u32 { 2 let mut steps = 0; 3 while n != 1 { 4 n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 }; 5 steps += 1; 6 } 7 steps 8 } 9 10 fn main() { 11 println!("{}", collatz_steps(27)); // 111 12 println!("{}", collatz_steps(6)); // 8 13 } ``` ```text 1 111 2 8 ``` > Note `mut n` in the parameter list: parameters are immutable by default in Rust, so you opt into mutating the local copy. The `if`/`else` here is used as an **expression** producing the next value of `n`; see [conditionals](/04-control-flow/00-conditionals/). *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Rust has **no C-style `for`**: you iterate over ranges and collections * `for x in start..end` (exclusive) and `start..=end` (inclusive) * `.enumerate()` gives you `(index, value)` when you need the index * `.rev()` and `.step_by(n)` replace counting down / stepping * `while condition` works like JavaScript, but the condition must be `bool` * `loop { ... }` is the dedicated infinite loop, and `break value` makes it an expression * Borrowing (`&`) vs moving in `for`, and why you cannot mutate a collection mid-loop **The big mental shift:** stop thinking “advance a counter” and start thinking “iterate over a sequence.” It eliminates off-by-one and out-of-bounds bugs by construction. # Pattern Matching with `match` > Rust's match is JS's switch with no fall-through and no forgotten case: an exhaustive expression that destructures, binds, and matches ranges. Rust’s `match` is the spiritual successor to JavaScript’s `switch`, but it does far more: it is an **expression** that produces a value, and the compiler forces you to handle *every* possible case. If you have ever shipped a bug because a `switch` fell through or missed a case, `match` is about to become your favorite control-flow construct. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) A **`match` expression** compares a value against a series of **patterns** and runs the code for the first one that fits. Unlike `switch`, it is **exhaustive** (you must cover all cases or the code will not compile), it **never falls through** (no `break` needed), and it can **destructure** data and **bind** parts of it to variables in the same step. For a TypeScript/JavaScript developer, think “`switch` + destructuring + a type-checker that refuses to let you forget a case.” *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) Here is a realistic `switch` you might write to turn a typed status object into a message, plus the kind of fall-through bug that `switch` invites: ```typescript 1 type Status = 2 | { kind: "ok" } 3 | { kind: "notFound" } 4 | { kind: "serverError"; code: number }; 5 6 function describeStatus(status: Status): string { 7 switch (status.kind) { 8 case "ok": 9 return "All good"; 10 case "notFound": 11 return "Resource missing"; 12 case "serverError": 13 return `Server error ${status.code}`; 14 default: 15 return "Unknown"; 16 } 17 } 18 19 // Grouping cases relies on intentional fall-through: 20 function classify(n: number): string { 21 switch (n) { 22 case 1: 23 case 2: 24 case 3: 25 return "small"; 26 case 0: 27 return "zero"; 28 default: 29 return "other"; 30 } 31 } 32 33 // The classic accidental fall-through bug: 34 function risky(x: number): string[] { 35 const out: string[] = []; 36 switch (x) { 37 case 1: 38 out.push("one"); // forgot `break`! 39 case 2: 40 out.push("two"); 41 break; 42 default: 43 out.push("other"); 44 } 45 return out; 46 } 47 48 console.log(describeStatus({ kind: "serverError", code: 503 })); // "Server error 503" 49 console.log(risky(1)); // [ 'one', 'two' ] ← the bug: both ran ``` Running this with Node v22 prints `[ 'one', 'two' ]` for `risky(1)`: case `1` “fell through” into case `2` because the `break` was missing. The `default` branch is also opt-in: drop it and TypeScript will *not* complain that you missed a `kind`. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 #[derive(Debug)] 2 enum HttpStatus { 3 Ok, 4 NotFound, 5 ServerError(u16), 6 } 7 8 fn describe_status(status: &HttpStatus) -> String { 9 // `match` is an expression: this whole thing is the function's return value. 10 match status { 11 HttpStatus::Ok => "All good".to_string(), 12 HttpStatus::NotFound => "Resource missing".to_string(), 13 HttpStatus::ServerError(code) => format!("Server error {code}"), 14 } 15 } 16 17 fn classify(n: i32) -> &'static str { 18 match n { 19 0 => "zero", 20 1 | 2 | 3 => "small", // `|` groups patterns — no fall-through trickery 21 _ => "other", // `_` is the catch-all, like `default` 22 } 23 } 24 25 fn main() { 26 println!("{}", describe_status(&HttpStatus::ServerError(503))); // Server error 503 27 println!("{}", classify(2)); // small 28 println!("{}", classify(0)); // zero 29 } ``` Output (real): ```text 1 Server error 503 2 small 3 zero ``` There is no `break`, there is no fall-through, and there is no `default` you can forget: if you delete one of the three `HttpStatus` arms, the program **will not compile**. > **Note:** Each arm is `pattern => expression,`. The `=>` is a “fat arrow,” not a closure (Rust closures use `|args| body`). All arms must produce the **same type**, because the `match` itself evaluates to that type. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### `match` is an expression, not a statement [Section titled “match is an expression, not a statement”](#match-is-an-expression-not-a-statement) In JavaScript a `switch` is a statement — it *does* things, it does not *evaluate to* a value. That is why `describeStatus` had to `return` inside each `case`. In Rust, `match` is an expression, so you can assign its result directly: ```rust 1 fn main() { 2 let code = 503; 3 // The match evaluates to a String, which we bind to `message`. 4 let message = match code { 5 200 => "OK".to_string(), 6 404 => "Not Found".to_string(), 7 500..=599 => format!("Server error {code}"), 8 _ => "Unhandled".to_string(), 9 }; 10 println!("{message}"); // Server error 503 11 } ``` This is the same expression-oriented model you saw with `if` in [Conditionals](/04-control-flow/00-conditionals/) and blocks in [Section 02 — Variables](/02-basics/00-variables/). The last expression of each arm (with **no trailing semicolon**) is that arm’s value. ### Exhaustiveness: the compiler has your back [Section titled “Exhaustiveness: the compiler has your back”](#exhaustiveness-the-compiler-has-your-back) The single biggest upgrade over `switch` is **exhaustiveness checking**. The compiler knows every possible value an `enum` can take, so it refuses to compile a `match` that leaves a case unhandled. This turns “I forgot a case” from a runtime bug into a compile error, and it is *especially* valuable when you later add a new variant to an enum: every `match` that does not handle it lights up red. ### Patterns, not just values [Section titled “Patterns, not just values”](#patterns-not-just-values) A `switch` case can only test for equality against a constant. A `match` arm is a **pattern**, which can: * match a literal (`0`, `'a'`, `"hi"`), * match a range (`1..=9`), * match several alternatives with `|`, * **destructure** a tuple, struct, or enum and pull its fields into variables, * **bind** the whole matched value to a name with `@`, * and add an extra runtime condition with a **guard** (`if ...`). The arms below combine destructuring (`ServerError(code)`) with formatting in one step; no separate `status.code` access needed: ```rust 1 #[derive(Debug)] 2 enum HttpStatus { 3 Ok, 4 NotFound, 5 ServerError(u16), 6 } 7 8 fn main() { 9 let status = HttpStatus::ServerError(503); 10 match &status { 11 HttpStatus::ServerError(code) => println!("got code {code}"), // code is bound to 503 12 other => println!("other: {other:?}"), 13 } 14 } ``` ### Order matters: top to bottom, first match wins [Section titled “Order matters: top to bottom, first match wins”](#order-matters-top-to-bottom-first-match-wins) Like `switch`, arms are tried in order and the **first** matching pattern wins. Unlike `switch`, evaluation stops there; control never falls into the next arm. This means the **catch-all `_` must come last**; put it first and every arm below it becomes dead code (a real warning, shown in Common Pitfalls). *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Aspect | JavaScript `switch` | Rust `match` | | ------------------ | ------------------------------------------ | -------------------------------------- | | Produces a value? | No (statement) | Yes (expression) | | Fall-through | Default behavior; needs `break` | Never; no `break` exists | | Missing a case | Silently allowed | **Compile error** (must be exhaustive) | | Grouping cases | Stacked empty `case`s | `pattern1 \| pattern2` | | Ranges | Not supported (`case 1..5` is not a thing) | `1..=9`, `'a'..='z'` | | Destructuring | Done separately, before/after the switch | Built into the pattern | | Extra conditions | An `if` inside the `case` body | Guard: `pattern if cond =>` | | Equality semantics | Uses `===` (strict) | Structural pattern match (no coercion) | | Catch-all | `default:` (optional, position-free) | `_` (must be last to be reachable) | The deepest difference is the mindset: a `switch` *executes statements*; a `match` *describes the shape of data and computes a value from it*. Rust leans hard on this everywhere: `Option`, `Result`, and most enums are designed to be consumed by `match`. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Forgetting a case (non-exhaustive match) [Section titled “Pitfall 1: Forgetting a case (non-exhaustive match)”](#pitfall-1-forgetting-a-case-non-exhaustive-match) Coming from `switch`, it is natural to handle “the cases you care about” and stop. Rust will not let you: ```rust 1 enum Direction { 2 North, 3 South, 4 East, 5 West, 6 } 7 8 fn turn(d: Direction) -> &'static str { 9 match d { 10 Direction::North => "up", 11 Direction::South => "down", 12 // does not compile (error[E0004]): forgot East and West 13 } 14 } ``` The real compiler error: ```text 1 error[E0004]: non-exhaustive patterns: `Direction::East` and `Direction::West` not covered 2 --> src/main.rs:9:11 3 | 4 9 | match d { 5 | ^ patterns `Direction::East` and `Direction::West` not covered 6 | 7 note: `Direction` defined here 8 ... 9 4 | East, 10 | ---- not covered 11 5 | West, 12 | ---- not covered 13 = note: the matched value is of type `Direction` 14 help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms ``` Add the missing arms, or a `_` catch-all if they really should be treated the same. > **Tip:** Resist the urge to reach for `_` just to silence this error. An explicit `_` means “any new variant added later is also handled here,” which can hide bugs. When the variants are a closed set you control, listing them all means the compiler will remind you to update this `match` the day you add a fifth direction. ### Pitfall 2: Guards do not count toward exhaustiveness [Section titled “Pitfall 2: Guards do not count toward exhaustiveness”](#pitfall-2-guards-do-not-count-toward-exhaustiveness) A guard (`if ...`) makes an arm *conditional*, so the compiler cannot assume it covers the pattern. This is exhaustive-looking but does not compile: ```rust 1 fn main() { 2 let opt = Some(5); 3 let _ = match opt { 4 Some(x) if x > 0 => "positive", 5 None => "none", 6 // does not compile (error[E0004]): `Some(_)` not covered 7 }; 8 } ``` Real error: ```text 1 error[E0004]: non-exhaustive patterns: `Some(_)` not covered 2 --> src/main.rs:3:19 3 | 4 3 | let _ = match opt { 5 | ^^^ pattern `Some(_)` not covered ``` `Some(x)` where `x <= 0` falls through every arm. Add an unguarded arm (e.g. `Some(_) => "non-positive"`) to cover it. ### Pitfall 3: Putting `_` (or a catch-all) too early [Section titled “Pitfall 3: Putting \_ (or a catch-all) too early”](#pitfall-3-putting-_-or-a-catch-all-too-early) In a `switch`, `default` can sit anywhere. In a `match`, arms are tried top-to-bottom, so a catch-all above a specific pattern makes the specific one **unreachable**: ```rust 1 fn main() { 2 let n = 3; 3 let label = match n { 4 _ => "anything", 5 1 => "one", // warning: unreachable 6 }; 7 println!("{label}"); 8 } ``` This compiles but warns: ```text 1 warning: unreachable pattern 2 --> src/main.rs:5:9 3 | 4 4 | _ => "anything", 5 | - matches any value 6 5 | 1 => "one", 7 | ^ no value can reach this 8 | 9 = note: `#[warn(unreachable_patterns)]` on by default ``` Move specific patterns above the catch-all. ### Pitfall 4: Arms must all return the same type [Section titled “Pitfall 4: Arms must all return the same type”](#pitfall-4-arms-must-all-return-the-same-type) Every arm feeds the same `match` expression, so they must agree on a type: ```rust 1 fn main() { 2 let n = 1; 3 // let x = match n { 4 // 1 => "one", // &str 5 // _ => 0, // i32 6 // }; // does not compile (error[E0308]): `match` arms have incompatible types 7 let _ = n; 8 } ``` The fix is to make the arms produce one consistent type (e.g. both `String`, or both `i32`). ### Pitfall 5: Expecting C-style range *case* syntax [Section titled “Pitfall 5: Expecting C-style range case syntax”](#pitfall-5-expecting-c-style-range-case-syntax) There is no `case 1..5:` in `switch`, and the Rust *expression* range `1..5` you use in a `for` loop ([Loops](/04-control-flow/01-loops/)) is **not** the same as a range *pattern*. In a pattern you write `1..=9` (inclusive) or `1..10` (exclusive); you cannot use a runtime range variable as a pattern. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### Match on enums and let exhaustiveness guide you [Section titled “Match on enums and let exhaustiveness guide you”](#match-on-enums-and-let-exhaustiveness-guide-you) Model your states as an `enum` and `match` on it. The compiler then guarantees every state is handled, and reminds you to revisit each `match` whenever you add a variant. This is the Rust replacement for discriminated-union `switch`es in TypeScript. ### Prefer explicit arms over `_` for closed enums [Section titled “Prefer explicit arms over \_ for closed enums”](#prefer-explicit-arms-over-_-for-closed-enums) For your own enums, list the variants. Reserve `_` for genuinely open-ended values like integers and chars, where enumerating everything is impossible. ### Reach for `if let` when you only care about one case [Section titled “Reach for if let when you only care about one case”](#reach-for-if-let-when-you-only-care-about-one-case) If a `match` has one meaningful arm and a do-nothing `_ => {}`, an `if let` is cleaner. That is its own topic; see [Concise Pattern Matching](/04-control-flow/03-if-let-while-let/): ```rust 1 fn main() { 2 let config_value: Option = Some(8080); 3 4 // Instead of: match config_value { Some(port) => {...}, None => {} } 5 if let Some(port) = config_value { 6 println!("listening on {port}"); 7 } 8 } ``` ### Use `|`, ranges, and guards to keep arms readable [Section titled “Use |, ranges, and guards to keep arms readable”](#use--ranges-and-guards-to-keep-arms-readable) Group alternatives with `|`, collapse contiguous values into a range, and use a guard for the one extra condition that does not fit the pattern. Keep the heavy logic in a `{ }` block or a helper function so the arm stays scannable. ### Bind with `@` when you need both the test *and* the value [Section titled “Bind with @ when you need both the test and the value”](#bind-with--when-you-need-both-the-test-and-the-value) When you want to check that a value is in a range *and* keep the value, `name @ pattern` does both in one step instead of re-reading the variable. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A tiny command interpreter — the kind you might build for a REPL, a chat bot, or a debug console. It shows `|` patterns, ranges, the `@` binding, guards, destructuring, and nested `match`es all working together to parse and execute typed commands. ```rust 1 /// A tiny command interpreter, the kind you'd build for a REPL or a chat bot. 2 #[derive(Debug)] 3 enum Command { 4 Help, 5 Echo(String), 6 Add(i64, i64), 7 SetVolume(u8), 8 Quit, 9 Unknown(String), 10 } 11 12 /// Parse a raw line into a typed Command. 13 fn parse(line: &str) -> Command { 14 let mut parts = line.trim().splitn(2, ' '); 15 let verb = parts.next().unwrap_or(""); 16 let rest = parts.next().unwrap_or("").trim(); 17 18 match verb { 19 "help" | "?" => Command::Help, // `|` = multiple aliases 20 "echo" => Command::Echo(rest.to_string()), 21 "add" => { 22 let mut nums = rest.split_whitespace(); 23 // Nested match on a tuple of two parse results. 24 match (nums.next(), nums.next()) { 25 (Some(a), Some(b)) => match (a.parse(), b.parse()) { 26 (Ok(x), Ok(y)) => Command::Add(x, y), 27 _ => Command::Unknown(line.to_string()), 28 }, 29 _ => Command::Unknown(line.to_string()), 30 } 31 } 32 "volume" => match rest.parse::() { 33 // `v @ 0..=100`: bind the value AND require it to be in range. 34 Ok(v @ 0..=100) => Command::SetVolume(v), 35 _ => Command::Unknown(line.to_string()), 36 }, 37 "quit" | "exit" => Command::Quit, 38 _ => Command::Unknown(line.to_string()), 39 } 40 } 41 42 /// Execute a command and return the user-facing response. 43 fn run(cmd: Command) -> String { 44 match cmd { 45 Command::Help => "commands: help, echo, add, volume, quit".to_string(), 46 // Guard: an empty echo is a special case. 47 Command::Echo(text) if text.is_empty() => "(nothing to echo)".to_string(), 48 Command::Echo(text) => text, 49 Command::Add(a, b) => format!("{a} + {b} = {}", a + b), 50 Command::SetVolume(v) => format!("volume set to {v}"), 51 Command::Quit => "bye!".to_string(), 52 Command::Unknown(raw) => format!("unknown command: {raw:?}"), 53 } 54 } 55 56 fn main() { 57 let session = [ 58 "help", 59 "echo hello there", 60 "add 19 23", 61 "volume 80", 62 "volume 250", 63 "dance now", 64 "quit", 65 ]; 66 67 for line in session { 68 let cmd = parse(line); 69 println!("> {line}"); 70 println!(" {}", run(cmd)); 71 } 72 } ``` Real output: ```text 1 > help 2 commands: help, echo, add, volume, quit 3 > echo hello there 4 hello there 5 > add 19 23 6 19 + 23 = 42 7 > volume 80 8 volume set to 80 9 > volume 250 10 unknown command: "volume 250" 11 > dance now 12 unknown command: "dance now" 13 > quit 14 bye! ``` Notice `volume 250` falls through to `Unknown`: the `v @ 0..=100` pattern rejects `250` because it is out of range, so `parse` returns `Command::Unknown`. The range check is part of the *pattern*, not a separate `if`. ### Bonus: the patterns toolbox [Section titled “Bonus: the patterns toolbox”](#bonus-the-patterns-toolbox) A compact tour of every pattern feature in this file’s scope, all in one program: ```rust 1 #[derive(Debug)] 2 struct Point { 3 x: i32, 4 y: i32, 5 z: i32, 6 } 7 8 fn char_kind(c: char) -> &'static str { 9 match c { 10 'a'..='z' => "lowercase", // char ranges work too 11 'A'..='Z' => "uppercase", 12 '0'..='9' => "digit", 13 _ => "other", 14 } 15 } 16 17 fn classify(n: i32) -> &'static str { 18 match n { 19 0 => "zero", 20 1 | 2 | 3 => "small", // `|` alternatives 21 4..=9 => "medium", // inclusive range 22 _ if n < 0 => "negative", // guard on the catch-all 23 _ => "large", 24 } 25 } 26 27 fn main() { 28 for c in ['k', 'Q', '7', '#'] { 29 println!("{c}: {}", char_kind(c)); 30 } 31 32 for n in [-5, 0, 2, 7, 42] { 33 println!("{n} is {}", classify(n)); 34 } 35 36 // Destructure a struct; `..` ignores the rest of the fields. 37 let p = Point { x: 1, y: 2, z: 3 }; 38 match p { 39 Point { x: 0, y: 0, z: 0 } => println!("at origin"), 40 Point { x, .. } => println!("x is {x}, ignoring y and z"), 41 } 42 43 // Tuple destructuring with literal + binding patterns. 44 let point = (0, 7); 45 let desc = match point { 46 (0, 0) => "origin".to_string(), 47 (x, 0) => format!("on x-axis at {x}"), 48 (0, y) => format!("on y-axis at {y}"), 49 (x, y) => format!("at ({x}, {y})"), 50 }; 51 println!("{desc}"); 52 53 // `@` binding: capture the value while also range-checking it. 54 let id = 5; 55 let label = match id { 56 n @ 1..=9 => format!("single digit: {n}"), 57 n @ 10..=99 => format!("double digit: {n}"), 58 n => format!("big: {n}"), 59 }; 60 println!("{label}"); 61 62 // Slice patterns: first and last with `..` in the middle. 63 let numbers = [1, 2, 3]; 64 match numbers { 65 [first, .., last] => println!("first {first}, last {last}"), 66 } 67 } ``` Real output: ```text 1 k: lowercase 2 Q: uppercase 3 7: digit 4 #: other 5 -5 is negative 6 0 is zero 7 2 is small 8 7 is medium 9 42 is large 10 x is 1, ignoring y and z 11 on y-axis at 7 12 single digit: 5 13 first 1, last 3 ``` *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official documentation [Section titled “Official documentation”](#official-documentation) * [The Rust Book — The `match` Control Flow Construct](https://doc.rust-lang.org/book/ch06-02-match.html) * [The Rust Book — Patterns and Matching (Chapter 19)](https://doc.rust-lang.org/book/ch19-00-patterns.html) * [The Rust Book — All the Places Patterns Can Be Used](https://doc.rust-lang.org/book/ch19-01-all-the-places-for-patterns.html) * [Rust Reference — Patterns](https://doc.rust-lang.org/reference/patterns.html) * [Rust by Example — `match`](https://doc.rust-lang.org/rust-by-example/flow_control/match.html) ### Related sections in this guide [Section titled “Related sections in this guide”](#related-sections-in-this-guide) * [Conditionals](/04-control-flow/00-conditionals/) — `if`/`else` as expressions; the `if let` teaser. * [Loops](/04-control-flow/01-loops/) — where expression ranges like `1..5` come from (different from range *patterns*). * [`if let` and `while let`](/04-control-flow/03-if-let-while-let/) — concise pattern matching for one or two cases. * [Break, continue, and labeled loops](/04-control-flow/04-break-continue/) — controlling iteration that wraps a `match`. * [Section 02 — Types](/02-basics/01-types/) — tuples, chars, and the integer types you match on. * [Section 02 — Operators](/02-basics/02-operators/) — why `match` patterns are not `==` comparisons. * [Section 05 — Ownership](/05-ownership/) — how matching on a reference (`match &value`) avoids moving the value out. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: FizzBuzz, the pattern-matching way [Section titled “Exercise 1: FizzBuzz, the pattern-matching way”](#exercise-1-fizzbuzz-the-pattern-matching-way) **Difficulty:** Easy **Objective:** Practice matching on a tuple and using the `_` wildcard inside a pattern. **Instructions:** Implement `fizzbuzz(n)` so that it returns `"Fizz"` when `n` is divisible by 3, `"Buzz"` when divisible by 5, `"FizzBuzz"` when divisible by both, and the number itself otherwise. Do it with a single `match` on the tuple `(n % 3, n % 5)`, no `if`/`else`. ```rust 1 fn fizzbuzz(n: u32) -> String { 2 match (n % 3, n % 5) { 3 /* ??? */ 4 } 5 } 6 7 fn main() { 8 for n in [1, 3, 5, 15, 7] { 9 println!("{n} -> {}", fizzbuzz(n)); 10 } 11 } ``` Solution ```rust 1 fn fizzbuzz(n: u32) -> String { 2 match (n % 3, n % 5) { 3 (0, 0) => "FizzBuzz".to_string(), 4 (0, _) => "Fizz".to_string(), // divisible by 3 only 5 (_, 0) => "Buzz".to_string(), // divisible by 5 only 6 (_, _) => n.to_string(), // neither 7 } 8 } 9 10 fn main() { 11 for n in [1, 3, 5, 15, 7] { 12 println!("{n} -> {}", fizzbuzz(n)); 13 } 14 } ``` Output: ```text 1 1 -> 1 2 3 -> Fizz 3 5 -> Buzz 4 15 -> FizzBuzz 5 7 -> 7 ``` The `(0, 0)` arm must come first: order matters, and a number divisible by both 3 and 5 would otherwise be caught by `(0, _)`. ### Exercise 2: HTTP status categories with ranges [Section titled “Exercise 2: HTTP status categories with ranges”](#exercise-2-http-status-categories-with-ranges) **Difficulty:** Medium **Objective:** Use inclusive range patterns and a catch-all to classify values. **Instructions:** Implement `status_category(code: u16)` that returns `"informational"` for `100..=199`, `"success"` for `200..=299`, `"redirect"` for `300..=399`, `"client error"` for `400..=499`, `"server error"` for `500..=599`, and `"invalid"` for anything else. ```rust 1 fn status_category(code: u16) -> &'static str { 2 match code { 3 // TODO: fill in the range arms 4 } 5 } 6 7 fn main() { 8 for code in [101, 204, 301, 404, 503, 999] { 9 println!("{code} -> {}", status_category(code)); 10 } 11 } ``` Solution ```rust 1 fn status_category(code: u16) -> &'static str { 2 match code { 3 100..=199 => "informational", 4 200..=299 => "success", 5 300..=399 => "redirect", 6 400..=499 => "client error", 7 500..=599 => "server error", 8 _ => "invalid", 9 } 10 } 11 12 fn main() { 13 for code in [101, 204, 301, 404, 503, 999] { 14 println!("{code} -> {}", status_category(code)); 15 } 16 } ``` Output: ```text 1 101 -> informational 2 204 -> success 3 301 -> redirect 4 404 -> client error 5 503 -> server error 6 999 -> invalid ``` The `_ => "invalid"` arm is required: `u16` can hold values like `999` and `0` that no range covers, so without it the match would not be exhaustive. ### Exercise 3: Evaluate an expression tree [Section titled “Exercise 3: Evaluate an expression tree”](#exercise-3-evaluate-an-expression-tree) **Difficulty:** Hard **Objective:** Destructure a recursive enum, bind its fields, and combine `match` with a guard and a nested `match` for error handling. **Instructions:** Given the `Expr` enum below, implement `eval(expr: &Expr) -> Result` that evaluates the tree. Numbers evaluate to themselves; `Add`/`Sub`/`Mul` combine their operands; `Div` returns `Err("division by zero")` when the divisor evaluates to `0.0`. Use the `?` operator to propagate errors from sub-evaluations. (`Box`, `Result`, and `?` are previewed in [Section 03](/03-functions/) and covered fully in [Section 08 — Error Handling](/08-error-handling/).) ```rust 1 #[derive(Debug)] 2 enum Expr { 3 Num(f64), 4 Add(Box, Box), 5 Sub(Box, Box), 6 Mul(Box, Box), 7 Div(Box, Box), 8 } 9 10 fn eval(expr: &Expr) -> Result { 11 // TODO: match on `expr` 12 } 13 14 fn main() { 15 // (2 + 3) * 4 - 1 16 let expr = Expr::Sub( 17 Box::new(Expr::Mul( 18 Box::new(Expr::Add(Box::new(Expr::Num(2.0)), Box::new(Expr::Num(3.0)))), 19 Box::new(Expr::Num(4.0)), 20 )), 21 Box::new(Expr::Num(1.0)), 22 ); 23 println!("{:?}", eval(&expr)); 24 25 let bad = Expr::Div(Box::new(Expr::Num(10.0)), Box::new(Expr::Num(0.0))); 26 println!("{:?}", eval(&bad)); 27 } ``` Solution ```rust 1 #[derive(Debug)] 2 enum Expr { 3 Num(f64), 4 Add(Box, Box), 5 Sub(Box, Box), 6 Mul(Box, Box), 7 Div(Box, Box), 8 } 9 10 fn eval(expr: &Expr) -> Result { 11 match expr { 12 // `Num(n)` binds n: &f64, so dereference with *n. 13 Expr::Num(n) => Ok(*n), 14 Expr::Add(a, b) => Ok(eval(a)? + eval(b)?), 15 Expr::Sub(a, b) => Ok(eval(a)? - eval(b)?), 16 Expr::Mul(a, b) => Ok(eval(a)? * eval(b)?), 17 // Nested match on the divisor catches division by zero. 18 Expr::Div(a, b) => match eval(b)? { 19 0.0 => Err("division by zero".to_string()), 20 divisor => Ok(eval(a)? / divisor), 21 }, 22 } 23 } 24 25 fn main() { 26 // (2 + 3) * 4 - 1 27 let expr = Expr::Sub( 28 Box::new(Expr::Mul( 29 Box::new(Expr::Add(Box::new(Expr::Num(2.0)), Box::new(Expr::Num(3.0)))), 30 Box::new(Expr::Num(4.0)), 31 )), 32 Box::new(Expr::Num(1.0)), 33 ); 34 println!("{:?}", eval(&expr)); // Ok(19.0) 35 36 let bad = Expr::Div(Box::new(Expr::Num(10.0)), Box::new(Expr::Num(0.0))); 37 println!("{:?}", eval(&bad)); // Err("division by zero") 38 } ``` Output: ```text 1 Ok(19.0) 2 Err("division by zero") ``` A few things to notice: `eval(a)?` recursively evaluates the sub-tree and short-circuits the whole function if it returns `Err`. Because we matched on `&Expr`, the bindings `a` and `b` are `&Box`, which auto-dereference cleanly when passed back into `eval`. The nested `match eval(b)?` is how you inspect a computed value mid-pattern. # Concise Pattern Matching: `if let`, `while let`, and `let ... else` > When only one case matters, if let, while let, and let-else fuse the check and binding in one step, replacing TypeScript's if (x !== null) narrowing. A full [`match`](/04-control-flow/02-match/) is wonderful when you genuinely care about *every* variant, but a lot of real code only cares about *one* case: “if this `Option` is `Some`, use it.” Rust gives you three lightweight tools for exactly that — `if let`, `while let`, and `let ... else` — and they will quickly become the workhorses you reach for when you would have written `if (x !== null)` in TypeScript. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) `if let` runs a block only when a value matches a single pattern, binding the inner data in one step. Think of it as `if` fused with destructuring. `while let` is the loop version: keep looping as long as the pattern keeps matching. `let ... else` (often called **let-else**) binds a pattern unconditionally and runs a diverging `else` block (like `return` or `break`) when it *doesn’t* match, which is the cleanest way to do early-return “guard clauses” in Rust. > **Note:** These are all *syntactic sugar* over `match`. Learning them after [`match`](/04-control-flow/02-match/) makes the sugar obvious; learning them first makes `match` feel like a natural generalization. Either order is fine. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) In TypeScript, you constantly narrow a possibly-absent value before using it. There is no destructuring-in-the-condition, so you check first and read the field on the next line. ```typescript 1 // TypeScript: narrow-then-use, the everyday pattern 2 interface Session { 3 userId: number; 4 token: string; 5 } 6 7 function currentSession(): Session | null { 8 return Math.random() > 0.5 ? { userId: 7, token: "abc" } : null; 9 } 10 11 const session = currentSession(); 12 if (session !== null) { 13 // `session` is narrowed to Session inside this block 14 console.log(`User ${session.userId} is logged in`); 15 } else { 16 console.log("No active session"); 17 } 18 19 // "Keep going while there's work" — the while-let shape 20 const stack: number[] = [1, 2, 3, 4, 5]; 21 let top: number | undefined; 22 while ((top = stack.pop()) !== undefined) { 23 console.log(`popped ${top}`); 24 } 25 26 // Guard clause / early return to avoid nesting 27 function parsePort(input: string): string { 28 const port = Number(input); 29 if (!Number.isInteger(port)) { 30 return `'${input}' is not a valid port`; 31 } 32 // `port` is usable for the rest of the function 33 return `listening on port ${port}`; 34 } ``` Notice three friction points a Rust developer will recognize: you re-read `session` after narrowing, the `while` loop needs an extra mutable `top` declared *outside* the loop, and the guard clause re-checks a condition rather than binding the success value directly. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) Rust folds the check and the binding together. Each block below is **compile-verified**. ```rust 1 #[derive(Debug)] 2 struct Session { 3 user_id: u32, 4 token: String, 5 } 6 7 fn current_session() -> Option { 8 Some(Session { user_id: 7, token: "abc".to_string() }) 9 } 10 11 fn parse_port(input: &str) -> String { 12 // let-else: bind on success, diverge on failure — no re-check, no nesting 13 let Ok(port) = input.parse::() else { 14 return format!("'{input}' is not a valid port"); 15 }; 16 format!("listening on port {port}") 17 } 18 19 fn main() { 20 // if let: check + destructure in one step 21 if let Some(session) = current_session() { 22 println!("User {} is logged in", session.user_id); 23 } else { 24 println!("No active session"); 25 } 26 27 // while let: loop as long as `pop()` keeps returning Some 28 let mut stack = vec![1, 2, 3, 4, 5]; 29 while let Some(top) = stack.pop() { 30 println!("popped {top}"); 31 } 32 33 println!("{}", parse_port("8080")); 34 println!("{}", parse_port("not-a-port")); 35 } ``` Running it prints: ```text 1 User 7 is logged in 2 popped 5 3 popped 4 4 popped 3 5 popped 2 6 popped 1 7 listening on port 8080 8 'not-a-port' is not a valid port ``` The mutable `top` variable that TypeScript needed outside the loop is gone. `while let` declares and binds `top` fresh on every iteration, scoped to the loop body. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### `if let`: “match one pattern, ignore the rest” [Section titled “if let: “match one pattern, ignore the rest””](#if-let-match-one-pattern-ignore-the-rest) `if let PATTERN = EXPRESSION { ... }` evaluates `EXPRESSION`, tries to match it against `PATTERN`, and runs the block only on a match. On a match, any variables in the pattern are bound and **scoped to the block**. ```rust 1 fn main() { 2 let maybe_user: Option<&str> = Some("Ada"); 3 4 if let Some(name) = maybe_user { 5 // `name` is the &str inside the Some, only visible here 6 println!("Welcome, {name}!"); 7 } else { 8 // optional else, runs on no-match 9 println!("No user logged in"); 10 } 11 } ``` This is exactly equivalent to the more verbose: ```rust 1 fn main() { 2 let maybe_user: Option<&str> = Some("Ada"); 3 4 match maybe_user { 5 Some(name) => println!("Welcome, {name}!"), 6 None => println!("No user logged in"), 7 } 8 } ``` `if let` shines when one of the `match` arms would be a do-nothing `_ => {}`. It works on *any* pattern, not just `Option`: enums, structs, tuples, and ranges all work: ```rust 1 #[derive(Debug)] 2 enum Event { 3 Click { x: i32, y: i32 }, 4 KeyPress(char), 5 Close, 6 } 7 8 fn main() { 9 let event = Event::Click { x: 10, y: 20 }; 10 // Struct-variant destructuring directly in the condition 11 if let Event::Click { x, y } = event { 12 println!("Clicked at ({x}, {y})"); 13 } 14 15 let key = Event::KeyPress('q'); 16 if let Event::KeyPress(c) = key { 17 println!("Key pressed: {c}"); 18 } 19 20 let _ = Event::Close; // (just to use the variant) 21 } ``` This prints: ```text 1 Clicked at (10, 20) 2 Key pressed: q ``` ### `else if let` chains [Section titled “else if let chains”](#else-if-let-chains) You can chain alternatives with `else if let`, mixing in regular `else if` and a final `else`: ```rust 1 fn main() { 2 let setting: Option = None; 3 let fallback: Option = Some(42); 4 5 if let Some(v) = setting { 6 println!("setting = {v}"); 7 } else if let Some(v) = fallback { 8 println!("fallback = {v}"); 9 } else { 10 println!("nothing set"); 11 } 12 } ``` This prints `fallback = 42`. Each `if let` tries its own pattern; the first match wins. ### `if let` chains with `&&` (Rust 2024) [Section titled “if let chains with && (Rust 2024)”](#if-let-chains-with--rust-2024) Since the latest stable edition (2024, stabilized in Rust 1.88), you can join a pattern match and ordinary boolean conditions with `&&` in a single `if let`. All conditions must hold, and bindings from earlier links are visible to later ones: ```rust 1 fn main() { 2 let opt: Option = Some(7); 3 let flag = true; 4 5 // let-chain: pattern match AND boolean tests together 6 if let Some(n) = opt && n > 5 && flag { 7 println!("matched and n={n} > 5"); 8 } 9 } ``` This prints `matched and n=7 > 5`. Before this feature you had to nest an `if let` inside an `if`, or push the extra condition into a `match` guard. > **Note:** Let-chains require the 2024 edition. A fresh `cargo new` selects it automatically, so you get this for free in new projects. ### `while let`: loop while the pattern keeps matching [Section titled “while let: loop while the pattern keeps matching”](#while-let-loop-while-the-pattern-keeps-matching) `while let PATTERN = EXPRESSION { ... }` re-evaluates `EXPRESSION` before every iteration and stops the first time it does *not* match. It is the idiomatic way to drain anything that yields `Option`: ```rust 1 fn main() { 2 // Drain a stack: pop() returns Some(x) until empty, then None 3 let mut stack = vec![1, 2, 3]; 4 while let Some(top) = stack.pop() { 5 println!("popped {top}"); 6 } 7 8 // Pull values straight from an iterator 9 let mut numbers = vec![10, 20, 30].into_iter(); 10 while let Some(n) = numbers.next() { 11 println!("got {n}"); 12 } 13 } ``` > **Tip:** A `while let ... = iter.next()` loop is almost always better written as a plain `for n in iter { ... }`, which handles the `next()`/`None` dance for you. Reach for `while let` when the source is a mutable container you are *consuming* (like `stack.pop()` or `channel.recv()`), where `for` does not directly apply. See [Loops](/04-control-flow/01-loops/) for the `for`/`while`/`loop` trio. ### `let ... else`: early-return guard clauses [Section titled “let ... else: early-return guard clauses”](#let--else-early-return-guard-clauses) `let PATTERN = EXPRESSION else { ... }` binds `PATTERN` for the **rest of the enclosing scope** when it matches. When it does *not* match, the `else` block runs, and that block **must diverge**, meaning it has to leave the current scope via `return`, `break`, `continue`, or a `panic!`. This is Rust’s answer to the guard-clause / early-return style. ```rust 1 fn first_word(text: &str) -> &str { 2 let Some(word) = text.split_whitespace().next() else { 3 return ""; 4 }; 5 // `word` is in scope here, with no extra indentation 6 word 7 } 8 9 fn main() { 10 println!("{}", first_word("hello world")); // hello 11 println!("{}", first_word(" ")); // 12 } ``` The key difference from `if let`: with `if let`, the binding lives *inside* the block. With `let ... else`, the binding lives *after* the statement, in the surrounding scope — which is precisely what you want for “validate, then proceed with the validated value.” Compare the shapes: ```rust 1 // if let: success path is INSIDE, nesting grows with each check 2 fn describe_if_let(input: &str) -> String { 3 if let Ok(n) = input.parse::() { 4 if n > 0 { 5 return format!("positive: {n}"); 6 } 7 } 8 "invalid".to_string() 9 } 10 11 // let-else: success path is the MAIN body, failures bail early 12 fn describe_let_else(input: &str) -> String { 13 let Ok(n) = input.parse::() else { 14 return "invalid".to_string(); 15 }; 16 if n <= 0 { 17 return "invalid".to_string(); 18 } 19 format!("positive: {n}") 20 } ``` `let ... else` also works mid-loop, where the divergence is `continue` or `break`: ```rust 1 fn sum_even_strings(items: &[&str]) -> i32 { 2 let mut total = 0; 3 for item in items { 4 // Skip anything that isn't a number, without nesting the happy path 5 let Ok(n) = item.parse::() else { 6 continue; 7 }; 8 if n % 2 == 0 { 9 total += n; 10 } 11 } 12 total 13 } 14 15 fn main() { 16 println!("{}", sum_even_strings(&["2", "x", "4", "5", "6"])); // 12 17 } ``` *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript/JavaScript | Rust | | ------------------------ | ----------------------------------------------------- | --------------------------------------------------------- | | Narrow-then-use | `if (x !== null) { use x }`, re-read `x` | `if let Some(v) = x { use v }`, binds in one step | | Binding scope on success | Whole block after the narrowing check | `if let`: inside block; `let ... else`: rest of scope | | Loop while value present | `while ((v = next()) !== undefined)` with outer `let` | `while let Some(v) = next()`, fresh binding each pass | | Early-return guard | `if (!ok) return;` then re-fetch the value | `let Ok(v) = ... else { return; };` binds `v` directly | | Combine match + boolean | nest `if` inside `if` | `if let P = x && cond` (let-chains, edition 2024) | | What you can match | runtime `typeof` / property checks | any pattern: enums, structs, tuples, ranges, `@` bindings | ### The conceptual shift [Section titled “The conceptual shift”](#the-conceptual-shift) In TypeScript you *test* a value and the compiler *narrows* its static type for the rest of the block. In Rust you *match a pattern* and the compiler *binds* the inner data. The mechanisms differ, but the ergonomics line up: `if let Some(v) = opt` feels like `if (opt != null)`, and `let Some(v) = opt else { return; }` feels like `if (opt == null) return;`. Unlike TypeScript, the success binding from `if let` does **not** leak past the closing brace. There is no flow-based narrowing that survives the block. When you want the value afterward, that is the signal to use `let ... else` instead. ### `if let` is refutable; `let` is irrefutable [Section titled “if let is refutable; let is irrefutable”](#if-let-is-refutable-let-is-irrefutable) A plain `let x = ...;` requires an **irrefutable** pattern — one that always matches (like a bare name or a tuple of names). `if let`, `while let`, and the pattern in `let ... else` accept **refutable** patterns — ones that might fail (like `Some(x)` or `Ok(x)`). That refutability is exactly what gives them a “didn’t match” path to run. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Expecting the `if let` binding to survive the block [Section titled “Pitfall 1: Expecting the if let binding to survive the block”](#pitfall-1-expecting-the-if-let-binding-to-survive-the-block) A TypeScript developer expects the narrowed value to be usable after the `if`. In Rust the binding is scoped to the block and disappears. ```rust 1 fn main() { 2 let maybe_name: Option<&str> = Some("Grace"); 3 if let Some(name) = maybe_name { 4 println!("inside: {name}"); 5 } 6 // `name` only existed inside the if-let block: 7 println!("outside: {name}"); // does not compile (error[E0425]) 8 } ``` The real compiler error: ```text 1 error[E0425]: cannot find value `name` in this scope 2 --> src/main.rs:7:25 3 | 4 7 | println!("outside: {name}"); 5 | ^^^^ not found in this scope ``` **Fix:** if you need the value afterward, use `let ... else`, which binds for the rest of the scope: ```rust 1 fn main() { 2 let maybe_name: Option<&str> = Some("Grace"); 3 let Some(name) = maybe_name else { 4 println!("no name"); 5 return; 6 }; 7 println!("inside or outside, {name} is in scope now"); 8 } ``` ### Pitfall 2: A `let ... else` block that doesn’t diverge [Section titled “Pitfall 2: A let ... else block that doesn’t diverge”](#pitfall-2-a-let--else-block-that-doesnt-diverge) The `else` block must leave the scope. If it just prints and falls through, the compiler rejects it, because `name` would otherwise be unbound on that path. ```rust 1 fn get_count(input: &str) -> i32 { 2 let Ok(n) = input.parse::() else { 3 println!("not a number"); 4 // does not compile (error[E0308]): forgot to diverge 5 }; 6 n 7 } 8 9 fn main() { 10 println!("{}", get_count("5")); 11 } ``` The real compiler error: ```text 1 error[E0308]: `else` clause of `let...else` does not diverge 2 --> src/main.rs:2:43 3 | 4 2 | let Ok(n) = input.parse::() else { 5 | ___________________________________________^ 6 3 | | println!("not a number"); 7 4 | | // forgot to diverge (no return/break/panic) 8 5 | | }; 9 | |_____^ expected `!`, found `()` 10 | 11 = note: expected type `!` 12 found unit type `()` 13 = help: try adding a diverging expression, such as `return` or `panic!(..)` 14 = help: ...or use `match` instead of `let...else` ``` **Fix:** end the `else` with `return`, `break`, `continue`, or `panic!` (or another expression of the never type `!`). ### Pitfall 3: Using `if let` with a pattern that always matches [Section titled “Pitfall 3: Using if let with a pattern that always matches”](#pitfall-3-using-if-let-with-a-pattern-that-always-matches) If the pattern can never fail, `if let` is pointless, and Clippy/rustc will tell you so with a built-in lint. ```rust 1 fn main() { 2 let point = (3, 4); 3 // A tuple pattern always matches a tuple -> irrefutable 4 if let (x, y) = point { // irrefutable_let_patterns warning 5 println!("{x}, {y}"); 6 } 7 } ``` The real warning: ```text 1 warning: irrefutable `if let` pattern 2 --> src/main.rs:4:8 3 | 4 4 | if let (x, y) = point { 5 | ^^^^^^^^^^^^^^^^^^ 6 | 7 = note: this pattern will always match, so the `if let` is useless 8 = help: consider replacing the `if let` with a `let` 9 = note: `#[warn(irrefutable_let_patterns)]` on by default ``` **Fix:** use a plain `let (x, y) = point;`; no conditional needed. ### Pitfall 4: `if let` where every variant matters [Section titled “Pitfall 4: if let where every variant matters”](#pitfall-4-if-let-where-every-variant-matters) `if let` silently ignores the non-matching cases. That is a feature when you truly do not care, but a bug when you do. If you find yourself writing `else if let ... else if let ...` over an enum’s variants, you have re-implemented `match` badly and lost its exhaustiveness checking. Prefer [`match`](/04-control-flow/02-match/) when you want the compiler to force you to handle every case. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### Reach for the lightest tool that fits [Section titled “Reach for the lightest tool that fits”](#reach-for-the-lightest-tool-that-fits) * **One case matters, nothing needed afterward → `if let`** (optionally with `else`). * **One case matters, value needed for the rest of the scope → `let ... else`.** * **Repeatedly consume a source that yields `Option` → `while let`** (but prefer `for` when iterating). * **Every variant matters → [`match`](/04-control-flow/02-match/).** ### Use `let ... else` to flatten guard clauses [Section titled “Use let ... else to flatten guard clauses”](#use-let--else-to-flatten-guard-clauses) Early returns keep the happy path un-indented. This scales far better than a pyramid of nested `if let`s: ```rust 1 fn extract_user_id(header: &str) -> Result { 2 let Some(token) = header.strip_prefix("Bearer ") else { 3 return Err("missing Bearer prefix".to_string()); 4 }; 5 let Ok(id) = token.parse::() else { 6 return Err(format!("invalid id: {token}")); 7 }; 8 Ok(id) 9 } 10 11 fn main() { 12 println!("{:?}", extract_user_id("Bearer 42")); // Ok(42) 13 println!("{:?}", extract_user_id("Basic 42")); // Err("missing Bearer prefix") 14 println!("{:?}", extract_user_id("Bearer notanum")); // Err("invalid id: notanum") 15 } ``` > **Tip:** Inside a function returning `Result` or `Option`, the [`?` operator](/03-functions/) is often even shorter than `let ... else` for plain “propagate the error” cases. Use `let ... else` when you need a *custom* failure action (a different error, a log line, a `continue`), not just propagation. ### Combine conditions with let-chains instead of nesting [Section titled “Combine conditions with let-chains instead of nesting”](#combine-conditions-with-let-chains-instead-of-nesting) When you need a pattern match *and* an extra check, a let-chain (`if let P = x && cond`) reads better than nesting and keeps both the binding and the condition in one place. ### Don’t reach for `if let` over an exhaustive enum [Section titled “Don’t reach for if let over an exhaustive enum”](#dont-reach-for-if-let-over-an-exhaustive-enum) If the compiler could be checking exhaustiveness for you, let it. `match` turns “I forgot the `Disconnected` variant” into a compile error; a chain of `if let`s turns it into a silent runtime no-op. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small command interpreter that reads lines, parses each into a typed `Command`, and builds up a profile. It uses `while let` to drain a work queue, `if let` to react only to recognized commands, `let ... else` (via `?`) inside the parser, and an `if let` with a tuple pattern at the end. This compiles and runs as-is. ```rust 1 use std::collections::VecDeque; 2 3 /// A command parsed from a line of user input. 4 #[derive(Debug)] 5 enum Command { 6 SetName(String), 7 SetAge(u32), 8 Quit, 9 } 10 11 /// Parse one line into a `Command`, or `None` if it is not recognized. 12 fn parse_command(line: &str) -> Option { 13 let mut parts = line.split_whitespace(); 14 let verb = parts.next()?; // `?` returns None if the line is empty 15 match verb { 16 "name" => { 17 let rest = parts.next()?; 18 Some(Command::SetName(rest.to_string())) 19 } 20 "age" => { 21 // let-else: bail out of THIS function cleanly on a bad number 22 let Ok(age) = parts.next()?.parse::() else { 23 return None; 24 }; 25 Some(Command::SetAge(age)) 26 } 27 "quit" => Some(Command::Quit), 28 _ => None, 29 } 30 } 31 32 #[derive(Debug, Default)] 33 struct Profile { 34 name: Option, 35 age: Option, 36 } 37 38 fn main() { 39 let input = [ 40 "name Ada", 41 "age 36", 42 "age not-a-number", 43 "unknown verb", 44 "quit", 45 ]; 46 47 // Feed lines into a queue and drain it with `while let`. 48 let mut queue: VecDeque<&str> = input.iter().copied().collect(); 49 let mut profile = Profile::default(); 50 51 while let Some(line) = queue.pop_front() { 52 // `if let` to react only when parsing succeeds. 53 if let Some(command) = parse_command(line) { 54 match command { 55 Command::SetName(name) => profile.name = Some(name), 56 Command::SetAge(age) => profile.age = Some(age), 57 Command::Quit => { 58 println!("Quit received, stopping."); 59 break; 60 } 61 } 62 } else { 63 println!("Ignoring unrecognized line: {line:?}"); 64 } 65 } 66 67 // `if let` with a tuple pattern: only print once BOTH fields are present. 68 if let (Some(name), Some(age)) = (&profile.name, profile.age) { 69 println!("Profile complete: {name}, age {age}"); 70 } else { 71 println!("Profile incomplete: {profile:?}"); 72 } 73 } ``` Output: ```text 1 Ignoring unrecognized line: "age not-a-number" 2 Ignoring unrecognized line: "unknown verb" 3 Quit received, stopping. 4 Profile complete: Ada, age 36 ``` Notice how `"age 36"` parses, `"age not-a-number"` is rejected by the `let ... else` inside `parse_command` (which returns `None`), and the loop stops cleanly at `"quit"` via `break` — three different control-flow idioms cooperating in one tidy loop. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Concise Control Flow with `if let` and `let ... else`](https://doc.rust-lang.org/book/ch06-03-if-let.html) * [The Rust Reference — `if let` expressions](https://doc.rust-lang.org/reference/expressions/if-expr.html#if-let-expressions) * [The Rust Reference — `let` statements (`let ... else`)](https://doc.rust-lang.org/reference/statements.html#let-statements) * [Rust by Example — `if let`](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html) and [`while let`](https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Conditionals](/04-control-flow/00-conditionals/): `if`/`else` as an expression and why Rust has no truthiness * [Loops](/04-control-flow/01-loops/): `for`, `while`, and `loop`, and when to prefer `for` over `while let` * [`match`](/04-control-flow/02-match/): the full pattern-matching expression these tools are sugar for * [`break` and `continue`](/04-control-flow/04-break-continue/): the divergence options for a `let ... else` inside a loop * [Labeled loops](/04-control-flow/05-labeled-loops/) — breaking out of nested `while let` loops * [Functions and the `?` operator](/03-functions/) — the other tool for early returns * [Variables and Mutability](/02-basics/00-variables/): how binding and scope work in Rust * [Ownership](/05-ownership/) — why a binding’s scope matters for borrows and moves *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Greet or fall back [Section titled “Exercise 1: Greet or fall back”](#exercise-1-greet-or-fall-back) **Difficulty:** Easy **Objective:** Practice `if let` with an `else` branch. **Instructions:** Implement `greet` so that `greet(Some("Lin"))` prints `Hello, Lin!` and `greet(None)` prints `Hello, guest!`. ```rust 1 fn greet(user: Option<&str>) { 2 // TODO: use `if let` with an `else` 3 } 4 5 fn main() { 6 greet(Some("Lin")); 7 greet(None); 8 } ``` Solution ```rust 1 fn greet(user: Option<&str>) { 2 if let Some(name) = user { 3 println!("Hello, {name}!"); 4 } else { 5 println!("Hello, guest!"); 6 } 7 } 8 9 fn main() { 10 greet(Some("Lin")); // Hello, Lin! 11 greet(None); // Hello, guest! 12 } ``` ### Exercise 2: Drain a job queue [Section titled “Exercise 2: Drain a job queue”](#exercise-2-drain-a-job-queue) **Difficulty:** Medium **Objective:** Use `while let` to consume a container until it is empty. **Instructions:** Implement `drain_report` so it repeatedly pops the last job off `queue`, prints `processing job N` for each, and finally prints `queue empty`. (`Vec::pop` returns `Option`.) ```rust 1 fn drain_report(mut queue: Vec) { 2 // TODO: use `while let` and `queue.pop()` 3 } 4 5 fn main() { 6 drain_report(vec![1, 2, 3]); 7 } ``` Solution ```rust 1 fn drain_report(mut queue: Vec) { 2 while let Some(job) = queue.pop() { 3 println!("processing job {job}"); 4 } 5 println!("queue empty"); 6 } 7 8 fn main() { 9 drain_report(vec![1, 2, 3]); 10 // processing job 3 11 // processing job 2 12 // processing job 1 13 // queue empty 14 } ``` > The `mut` on the parameter lets the function mutate its own copy of the `Vec`. For more on this, see [Variables and Mutability](/02-basics/00-variables/). ### Exercise 3: Parse an auth header with `let ... else` [Section titled “Exercise 3: Parse an auth header with let ... else”](#exercise-3-parse-an-auth-header-with-let--else) **Difficulty:** Hard **Objective:** Use `let ... else` for two early-return guard clauses, keeping the happy path un-indented. **Instructions:** Implement `extract_user_id` to return `Ok(u64)` when `header` looks like `"Bearer 42"`. If the `"Bearer "` prefix is missing, return `Err("missing Bearer prefix")`. If the remainder is not a valid `u64`, return `Err(format!("invalid id: {token}"))`. Use `str::strip_prefix` (returns `Option`) and `str::parse::()` (returns `Result`). Do **not** nest `if let`s. ```rust 1 fn extract_user_id(header: &str) -> Result { 2 // TODO: two `let ... else` guards, then `Ok(id)` 3 /* ??? */ 4 } 5 6 fn main() { 7 println!("{:?}", extract_user_id("Bearer 42")); 8 println!("{:?}", extract_user_id("Basic 42")); 9 println!("{:?}", extract_user_id("Bearer notanum")); 10 } ``` Solution ```rust 1 fn extract_user_id(header: &str) -> Result { 2 let Some(token) = header.strip_prefix("Bearer ") else { 3 return Err("missing Bearer prefix".to_string()); 4 }; 5 let Ok(id) = token.parse::() else { 6 return Err(format!("invalid id: {token}")); 7 }; 8 Ok(id) 9 } 10 11 fn main() { 12 println!("{:?}", extract_user_id("Bearer 42")); // Ok(42) 13 println!("{:?}", extract_user_id("Basic 42")); // Err("missing Bearer prefix") 14 println!("{:?}", extract_user_id("Bearer notanum")); // Err("invalid id: notanum") 15 } ``` Each guard binds its success value (`token`, then `id`) for the rest of the function, so the final `Ok(id)` reads as the clean, un-nested happy path. This is the same shape as the [`?` operator](/03-functions/), but with custom error messages on each failure. # Break and Continue > break and continue work like JavaScript inside a loop, but Rust's loop is an expression: break value hands a result out, replacing the mutate-a-variable pattern. `break` and `continue` are the two keywords you reach for when you want to *interrupt* the normal flow of a loop. If you’ve written `for`/`while` loops in JavaScript, you already know the basic idea. Rust keeps the familiar parts and adds one genuinely new superpower: a loop can **`break` with a value**, turning the whole loop into an expression that produces a result. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) In both TypeScript/JavaScript and Rust, `break` exits the nearest enclosing loop and `continue` skips to the next iteration. The big difference is that Rust’s `loop` is an **expression**, so `break value;` can hand a value back out of the loop, something JavaScript simply cannot do. This single feature replaces a lot of the “declare a `let` outside the loop, mutate it inside” boilerplate you write in JavaScript. > **Note:** This page focuses narrowly on `break`/`continue` themselves and on `break`-with-a-value. The loop constructs they live in (`for`, `while`, `loop`) are covered in [Loops](/04-control-flow/01-loops/), and breaking/continuing across *nested* loops with labels is covered in [Labeled Loops](/04-control-flow/05-labeled-loops/). *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) ```typescript 1 // Scan log entries: ignore "debug" noise, stop at the first "fatal". 2 interface LogEntry { 3 level: "debug" | "info" | "warn" | "error" | "fatal"; 4 message: string; 5 } 6 7 function collectUntilFatal(entries: LogEntry[]): string[] { 8 const collected: string[] = []; 9 10 for (const entry of entries) { 11 if (entry.level === "debug") continue; // skip noisy debug lines 12 if (entry.level === "fatal") break; // stop scanning entirely 13 collected.push(entry.message); 14 } 15 16 return collected; 17 } 18 19 const entries: LogEntry[] = [ 20 { level: "debug", message: "connecting" }, 21 { level: "warn", message: "slow response" }, 22 { level: "error", message: "timeout" }, 23 { level: "fatal", message: "out of memory" }, 24 { level: "info", message: "never reached" }, 25 ]; 26 27 console.log(collectUntilFatal(entries)); 28 // [ 'slow response', 'timeout' ] ``` **Key points:** * `continue` jumps straight to the next iteration, skipping the rest of the loop body. * `break` leaves the loop entirely; everything after the `fatal` entry is never visited. * `break`/`continue` are **statements** in JavaScript — they don’t produce values. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) ```rust 1 #[derive(Debug)] 2 struct LogEntry { 3 level: String, 4 message: String, 5 } 6 7 /// Scan log entries, skip "debug" lines, and stop at the first "fatal". 8 fn collect_until_fatal(entries: &[LogEntry]) -> Vec { 9 let mut collected = Vec::new(); 10 11 for entry in entries { 12 match entry.level.as_str() { 13 "debug" => continue, // ignore noisy debug lines 14 "fatal" => break, // stop scanning entirely 15 _ => collected.push(entry.message.clone()), 16 } 17 } 18 19 collected 20 } 21 22 fn main() { 23 let entries = vec![ 24 LogEntry { level: "debug".into(), message: "connecting".into() }, 25 LogEntry { level: "warn".into(), message: "slow response".into() }, 26 LogEntry { level: "error".into(), message: "timeout".into() }, 27 LogEntry { level: "fatal".into(), message: "out of memory".into() }, 28 LogEntry { level: "info".into(), message: "never reached".into() }, 29 ]; 30 31 let important = collect_until_fatal(&entries); 32 println!("{:#?}", important); 33 } ``` **Output:** ```text 1 [ 2 "slow response", 3 "timeout", 4 ] ``` **Key points:** * `continue` and `break` work exactly like JavaScript inside a `for` loop. * Here they appear as arms of a `match`, which reads cleanly, but a plain `if`/`else if` works identically. * `break`/`continue` with **no value** is the form allowed in `for` and `while` loops (more on values below). *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### `continue`: skip the rest of this iteration [Section titled “continue: skip the rest of this iteration”](#continue-skip-the-rest-of-this-iteration) `continue` immediately abandons the current iteration and moves to the next one. Anything in the loop body *after* the `continue` is skipped for that pass only. ```rust 1 fn main() { 2 let mut i = 0; 3 let mut kept = String::new(); 4 while i < 10 { 5 i += 1; 6 if i % 3 == 0 { 7 continue; // skip multiples of 3 8 } 9 kept.push_str(&i.to_string()); 10 kept.push(' '); 11 } 12 println!("non-multiples of 3: {}", kept.trim()); 13 } ``` **Output:** ```text 1 non-multiples of 3: 1 2 4 5 7 8 10 ``` > **Warning:** In a `while` loop, `continue` does **not** re-run any “update” step for you; there is no `i++` clause like in a C-style `for`. Notice that `i += 1` is the *first* line of the body above, on purpose. If you put `i += 1` at the *end* and then `continue` before reaching it, the counter never advances and you get an infinite loop. This is a very common JavaScript-to-Rust slip, because JS `for (let i = 0; ...; i++)` runs `i++` even after a `continue`. Rust has no C-style `for` (see [Loops](/04-control-flow/01-loops/)), so you manage the counter yourself. ### `break`: leave the loop entirely [Section titled “break: leave the loop entirely”](#break-leave-the-loop-entirely) `break` exits the nearest enclosing loop. Execution resumes at the first statement *after* the loop. ```rust 1 fn main() { 2 let mut sum = 0; 3 for n in 1..=10 { 4 if n % 2 == 0 { 5 continue; // skip even numbers 6 } 7 if n > 7 { 8 break; // stop once we pass 7 9 } 10 sum += n; 11 } 12 println!("sum of odd numbers <= 7: {}", sum); // 1 + 3 + 5 + 7 = 16 13 } ``` **Output:** ```text 1 sum of odd numbers <= 7: 16 ``` ### `break` with a value — Rust’s superpower [Section titled “break with a value — Rust’s superpower”](#break-with-a-value--rusts-superpower) This is the part with no JavaScript equivalent. Because `loop { ... }` is an **expression**, you can give `break` a value and that value becomes the value of the whole loop: ```rust 1 fn main() { 2 let mut attempts = 0; 3 let result = loop { 4 attempts += 1; 5 if attempts * attempts > 50 { 6 break attempts; // hand this value out of the loop 7 } 8 }; 9 println!("first n where n*n > 50: {}", result); 10 } ``` **Output:** ```text 1 first n where n*n > 50: 8 ``` Read `break attempts;` as “stop looping, and the loop expression evaluates to `attempts`.” In JavaScript you’d declare `let result;` *before* the loop, assign to it inside, and `break;` separately. Rust folds those three steps into one and lets the result be immutable (`let result`, no `mut`). > **Note:** `break value` is only valid in a `loop` (or a labeled block — see [Labeled Loops](/04-control-flow/05-labeled-loops/)). A `for` or `while` loop always evaluates to the unit value `()`, so `break` there must be value-less. The loops page covers *why* `loop` is the construct that returns values. ### Combining both: a retry / first-match pattern [Section titled “Combining both: a retry / first-match pattern”](#combining-both-a-retry--first-match-pattern) A `loop` that `continue`s on failure and `break`s with a value on success is an extremely common idiom. Think “find the first item that parses, otherwise a sentinel”: ```rust 1 fn main() { 2 let inputs = ["abc", "not a number", "42", "99"]; 3 let mut idx = 0; 4 5 let parsed: i32 = loop { 6 if idx >= inputs.len() { 7 break -1; // sentinel: nothing parsed 8 } 9 let candidate = inputs[idx]; 10 idx += 1; 11 match candidate.parse::() { 12 Ok(n) => break n, // success: hand the value back out of the loop 13 Err(_) => continue, // bad input: try the next one 14 } 15 }; 16 17 println!("first parseable value: {}", parsed); 18 } ``` **Output:** ```text 1 first parseable value: 42 ``` > **Tip:** When the sentinel feels hacky, return `Option` instead: `break Some(n)` / `break None`. You’ll see exactly that in the [Real-World Example](#real-world-example) and the exercises below. Often an iterator method like `.find_map(...)` is even cleaner; see [Best Practices](#best-practices). *** ## Key Differences from TypeScript/JavaScript [Section titled “Key Differences from TypeScript/JavaScript”](#key-differences-from-typescriptjavascript) | Concept | TypeScript/JavaScript | Rust | | ----------------------------- | ------------------------------------------- | ----------------------------------------------------- | | `break` exits nearest loop | Yes | Yes | | `continue` skips iteration | Yes | Yes | | `break` returns a value | Not possible | `break value` (in `loop` / labeled block) | | `continue` returns a value | No | No (it’s value-less everywhere) | | Loop as an expression | Statements only | `let x = loop { ... break v; };` | | C-style `for (;;i++)` update | Runs even after `continue` | n/a — no C-style `for`; you advance counters manually | | `break`/`continue` to a label | `break outer;` (label is a statement label) | `break 'outer;` (label is a lifetime-style name) | ### Why does Rust let `break` carry a value? [Section titled “Why does Rust let break carry a value?”](#why-does-rust-let-break-carry-a-value) Because Rust is **expression-oriented**. Almost everything produces a value: `if`/`else` (see [Conditionals](/04-control-flow/00-conditionals/)), `match` (see [Pattern Matching with `match`](/04-control-flow/02-match/)), and blocks all evaluate to something. A `loop` would be the odd one out if it didn’t, so Rust closes the gap: `break value` makes a `loop` produce a result the same way `return value` makes a function produce one. This eliminates the “uninitialized variable mutated inside the loop” pattern that’s so common (and so bug-prone) in JavaScript. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: `break value` in a `for` loop [Section titled “Pitfall 1: break value in a for loop”](#pitfall-1-break-value-in-a-for-loop) A `for` loop can never produce a non-unit value, so attaching a value to its `break` is a compile error. TypeScript developers reach for this because they expect any loop to be assignable. ```rust 1 fn main() { 2 let found = for n in 1..10 { 3 if n == 5 { 4 break n; // does not compile (error E0571: `break` with value from a `for` loop) 5 } 6 }; 7 println!("{:?}", found); 8 } ``` **Real compiler error:** ```text 1 error[E0571]: `break` with value from a `for` loop 2 --> src/main.rs:4:13 3 | 4 2 | let found = for n in 1..10 { 5 | -------------- you can't `break` with a value in a `for` loop 6 3 | if n == 5 { 7 4 | break n; // trying to return a value from a `for` 8 | ^^^^^^^ can only break with a value inside `loop` or breakable block 9 | 10 help: use `break` on its own without a value inside this `for` loop 11 | 12 4 - break n; // trying to return a value from a `for` 13 4 + break; // trying to return a value from a `for` 14 | ``` **Fix:** Use `loop` when you need a value, or restructure with an iterator method like `.find()`: ```rust 1 fn main() { 2 let found = (1..10).find(|&n| n == 5); // Option 3 println!("{:?}", found); // Some(5) 4 } ``` ### Pitfall 2: Mismatched types across multiple `break` values [Section titled “Pitfall 2: Mismatched types across multiple break values”](#pitfall-2-mismatched-types-across-multiple-break-values) Every `break value` in the same `loop` must produce the *same* type. The loop has exactly one result type, just like a function has one return type. ```rust 1 fn main() { 2 let x = loop { 3 break 5; 4 break "done"; // does not compile (error E0308: mismatched types) 5 }; 6 println!("{}", x); 7 } ``` **Real compiler error** (the unreachable second `break` also triggers a warning): ```text 1 warning: unreachable statement 2 --> src/main.rs:4:9 3 | 4 3 | break 5; 5 | ------- any code following this expression is unreachable 6 4 | break "done"; // type mismatch with earlier break 7 | ^^^^^^^^^^^^^ unreachable statement 8 | 9 = note: `#[warn(unreachable_code)]` on by default 10 11 error[E0308]: mismatched types 12 --> src/main.rs:4:15 13 | 14 3 | break 5; 15 | ------- expected because of this `break` 16 4 | break "done"; // type mismatch with earlier break 17 | ^^^^^^ expected integer, found `&str` ``` **Fix:** Make all `break` values share a type (here, both `&str`): ```rust 1 fn main() { 2 let x = loop { 3 if true { 4 break "five"; 5 } 6 break "done"; 7 }; 8 println!("{}", x); // five 9 } ``` ### Pitfall 3: `break`/`continue` outside any loop [Section titled “Pitfall 3: break/continue outside any loop”](#pitfall-3-breakcontinue-outside-any-loop) In JavaScript these are also illegal outside a loop, but the error fires at parse time. Rust gives you a precise, named diagnostic. ```rust 1 fn main() { 2 let n = 5; 3 if n > 3 { 4 break; // does not compile (error E0268: `break` outside of a loop) 5 } 6 } ``` **Real compiler error:** ```text 1 error[E0268]: `break` outside of a loop or labeled block 2 --> src/main.rs:4:9 3 | 4 4 | break; // not inside any loop 5 | ^^^^^ cannot `break` outside of a loop or labeled block ``` > **Note:** The message says “loop **or labeled block**.” Rust does let you `break` out of a labeled *block* (`'name: { ... break 'name value; }`) even though it isn’t a loop — a niche feature covered in [Labeled Loops](/04-control-flow/05-labeled-loops/). A bare `if` block is not breakable. ### Pitfall 4: Expecting `continue` to carry a value [Section titled “Pitfall 4: Expecting continue to carry a value”](#pitfall-4-expecting-continue-to-carry-a-value) `continue` never takes a value — not even in a `loop`. There’s no “continue with X” concept in Rust (or JavaScript). Trying it is a syntax error: ```rust 1 fn main() { 2 let mut total = 0; 3 for n in 1..5 { 4 total += continue 0; // does not compile (syntax error: continue takes no value) 5 } 6 println!("{}", total); 7 } ``` **Real compiler error:** ```text 1 error: expected one of `.`, `;`, `?`, `}`, or an operator, found `0` 2 --> src/main.rs:4:27 3 | 4 4 | total += continue 0; // continue does not take a value 5 | ^ expected one of `.`, `;`, `?`, `}`, or an operator ``` **Fix:** `continue` stands alone. If you wanted to add `0` and move on, just `continue;` (adding `0` is a no-op anyway). *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Reach for `break value` instead of a pre-declared mutable [Section titled “1. Reach for break value instead of a pre-declared mutable”](#1-reach-for-break-value-instead-of-a-pre-declared-mutable) ** JavaScript-style: declare, mutate, break separately** ```rust 1 fn main() { 2 let mut result = 0; // needs `mut`, starts as a meaningless 0 (rustc warns this initial value is never read — which is exactly the point) 3 let mut n = 0; 4 loop { 5 n += 1; 6 if n * n > 50 { 7 result = n; 8 break; 9 } 10 } 11 println!("{}", result); 12 } ``` ** Idiomatic Rust: let the loop be the value** ```rust 1 fn main() { 2 let mut n = 0; 3 let result = loop { 4 n += 1; 5 if n * n > 50 { 6 break n; 7 } 8 }; 9 println!("{}", result); // 8 10 } ``` The second version makes `result` immutable and removes the meaningless initial value. ### 2. Prefer iterator adapters over manual `break`/`continue` when the intent is “filter” or “stop early” [Section titled “2. Prefer iterator adapters over manual break/continue when the intent is “filter” or “stop early””](#2-prefer-iterator-adapters-over-manual-breakcontinue-when-the-intent-is-filter-or-stop-early) `continue` to skip and `break` to stop early often map directly onto `filter`, `take_while`, `find`, and friends. The iterator version states the *intent* and is harder to get wrong. ```rust 1 fn main() { 2 // Imperative: `continue` to skip evens, `break` to stop past 19. 3 let mut sum_imperative = 0; 4 for n in 1..=100 { 5 if n % 2 == 0 { 6 continue; 7 } 8 if n > 19 { 9 break; 10 } 11 sum_imperative += n; 12 } 13 14 // Iterator-idiomatic equivalent. 15 let sum_iter: i32 = (1..=100) 16 .take_while(|&n| n <= 19) 17 .filter(|&n| n % 2 != 0) 18 .sum(); 19 20 println!("imperative = {}, iterator = {}", sum_imperative, sum_iter); 21 assert_eq!(sum_imperative, sum_iter); 22 } ``` **Output:** ```text 1 imperative = 100, iterator = 100 ``` > **Tip:** Use `take_while` for the `break` (stop when a condition stops holding) and `filter` for the `continue` (skip items that don’t match). For “find the first match and return it,” `find` / `find_map` replace the whole `loop { ... break value; }` dance. Reach for explicit `break`/`continue` when the control flow is genuinely irregular (multiple exit conditions, side effects, mutation of outside state). ### 3. Keep the loop body small enough that the exit conditions are obvious [Section titled “3. Keep the loop body small enough that the exit conditions are obvious”](#3-keep-the-loop-body-small-enough-that-the-exit-conditions-are-obvious) A loop with three `break`s and four `continue`s buried in nested `if`s is hard to follow. If you find yourself there, extract the body into a function that returns an enum describing what to do (`Keep`, `Skip`, `Stop`), or switch to an iterator pipeline. ### 4. Don’t forget to advance counters in `while` loops before `continue` [Section titled “4. Don’t forget to advance counters in while loops before continue”](#4-dont-forget-to-advance-counters-in-while-loops-before-continue) As noted above, a `continue` in a `while` loop bypasses any code below it, including a counter increment. Put the increment first, or use a `for` over a range so the iterator advances for you. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small networking utility: given a list of candidate port strings (from a config file, environment variable, CLI args, etc.), find the first one that is both a valid port number and a non-privileged port (>= 1024). This combines `continue` (skip invalid candidates) with `break Some(_)` / `break None` (produce a typed result). ```rust 1 /// Return the first candidate that parses as a valid, non-privileged port. 2 fn first_valid_port(candidates: &[&str]) -> Option { 3 let mut idx = 0; 4 let port = loop { 5 if idx >= candidates.len() { 6 break None; // exhausted all candidates 7 } 8 let raw = candidates[idx]; 9 idx += 1; 10 match raw.parse::() { 11 Ok(p) if p >= 1024 => break Some(p), // valid, non-privileged port 12 _ => continue, // skip invalid / privileged 13 } 14 }; 15 port 16 } 17 18 fn main() { 19 let candidates = ["not-a-port", "80", "8080", "3000"]; 20 println!("{:?}", first_valid_port(&candidates)); // Some(8080) 21 22 let none_valid = ["xyz", "22", "443"]; 23 println!("{:?}", first_valid_port(&none_valid)); // None 24 } ``` **Output:** ```text 1 Some(8080) 2 None ``` Notice how `break Some(8080)` and `break None` give the `loop` a uniform `Option` type, and the whole result flows into an immutable `port`. In TypeScript you’d track a `result` variable and a found-flag, or `return` early from a helper. Rust lets the loop itself be the answer. > **Tip:** This exact “find the first match” shape is what `Iterator::find_map` exists for: `candidates.iter().find_map(|raw| raw.parse::().ok().filter(|&p| p >= 1024))`. Use the explicit `loop` when the iteration also needs side effects or an index you can’t easily express as an adapter. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Repetition with Loops](https://doc.rust-lang.org/book/ch03-05-control-flow.html#repetition-with-loops) — covers `break`, `continue`, and returning values from loops. * [Rust Reference — `break` expressions](https://doc.rust-lang.org/reference/expressions/loop-expr.html#break-expressions) * [Rust Reference — `continue` expressions](https://doc.rust-lang.org/reference/expressions/loop-expr.html#continue-expressions) * [Rust by Example — Loops: `break` and `continue`](https://doc.rust-lang.org/rust-by-example/flow_control/loop.html) ### Related Topics in This Guide [Section titled “Related Topics in This Guide”](#related-topics-in-this-guide) * [Loops](/04-control-flow/01-loops/): `for`, `while`, and `loop`; the constructs `break`/`continue` operate on, and how `loop` returns a value. * [Labeled Loops](/04-control-flow/05-labeled-loops/) — `break`/`continue` to an outer loop with a `'label`, and breaking out of labeled blocks. * [Conditionals](/04-control-flow/00-conditionals/): `if`/`else` as an expression, which pairs with the conditions that trigger `break`/`continue`. * [Match](/04-control-flow/02-match/) — using `match` arms (as in the log-scanning example) to decide between `continue`, `break`, and normal work. * [if let and while let](/04-control-flow/03-if-let-while-let/): concise pattern-matching loop conditions that often remove the need for a manual `break`. * [Functions: Return Values](/03-functions/02-return-values/) — `return` from a function is the function-level analogue of `break` from a loop. * [Ownership](/05-ownership/): why immutable bindings (which `break value` enables) are central to Rust. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Skip and Stop [Section titled “Exercise 1: Skip and Stop”](#exercise-1-skip-and-stop) **Difficulty:** Beginner **Objective:** Practice using `continue` to skip and `break` to terminate within a single `for` loop. **Instructions:** Implement `sum_positive_until_zero`. It should iterate over a slice of `i32`, *ignore* any negative numbers (use `continue`), *stop* completely as soon as it sees a `0` (use `break`), and return the sum of the positive numbers seen before the zero. ```rust 1 fn sum_positive_until_zero(nums: &[i32]) -> i32 { 2 let mut sum = 0; 3 // TODO: loop with continue (skip negatives) and break (stop at 0) 4 sum 5 } 6 7 fn main() { 8 let data = [3, -1, 4, -5, 2, 0, 100]; 9 println!("{}", sum_positive_until_zero(&data)); // expected: 9 10 } ``` Solution ```rust 1 fn sum_positive_until_zero(nums: &[i32]) -> i32 { 2 let mut sum = 0; 3 for &n in nums { 4 if n == 0 { 5 break; // 0 is a terminator 6 } 7 if n < 0 { 8 continue; // ignore negatives 9 } 10 sum += n; 11 } 12 sum 13 } 14 15 fn main() { 16 let data = [3, -1, 4, -5, 2, 0, 100]; 17 println!("{}", sum_positive_until_zero(&data)); // 3 + 4 + 2 = 9 18 } ``` **Output:** ```text 1 9 ``` The `100` after the `0` is never added because `break` ends the loop at the zero. ### Exercise 2: Break with a Value [Section titled “Exercise 2: Break with a Value”](#exercise-2-break-with-a-value) **Difficulty:** Intermediate **Objective:** Use `loop` plus `break value` to compute and return a result without a pre-declared mutable. **Instructions:** Implement `first_square_over(limit)`. Starting from `1`, find the smallest positive integer `n` whose square is strictly greater than `limit`, and return `n`. Use a `loop` and `break n;` — do **not** declare a separate `result` variable. ```rust 1 fn first_square_over(limit: u32) -> u32 { 2 // TODO: use `loop` and `break n;` 3 } 4 5 fn main() { 6 println!("{}", first_square_over(50)); // expected: 8 (8*8 = 64) 7 println!("{}", first_square_over(100)); // expected: 11 (11*11 = 121) 8 } ``` Solution ```rust 1 fn first_square_over(limit: u32) -> u32 { 2 let mut n = 0; 3 loop { 4 n += 1; 5 if n * n > limit { 6 break n; // the loop expression evaluates to n 7 } 8 } 9 } 10 11 fn main() { 12 println!("{}", first_square_over(50)); // 8 13 println!("{}", first_square_over(100)); // 11 14 } ``` **Output:** ```text 1 8 2 11 ``` Because the `loop` is the last expression in the function, its broken-out value is also the function’s return value; no `return` keyword needed. ### Exercise 3: First Valid Result or `None` [Section titled “Exercise 3: First Valid Result or None”](#exercise-3-first-valid-result-or-none) **Difficulty:** Advanced **Objective:** Combine `continue` (skip failures) with `break Some(_)` / `break None` to make a `loop` produce a typed `Option`. **Instructions:** Implement `first_even_parse(candidates: &[&str]) -> Option`. Walk the candidates in order; parse each as `i32`. Skip ones that fail to parse *or* are odd (use `continue`). Return `Some(n)` for the first candidate that parses to an **even** number, or `None` if none qualify. Use a `loop` with an index — not an iterator adapter — so you practice the pattern explicitly. ```rust 1 fn first_even_parse(candidates: &[&str]) -> Option { 2 // TODO: loop over an index; continue on failures/odds; break Some/None 3 } 4 5 fn main() { 6 println!("{:?}", first_even_parse(&["x", "3", "10", "12"])); // Some(10) 7 println!("{:?}", first_even_parse(&["1", "abc", "7"])); // None 8 } ``` Solution ```rust 1 fn first_even_parse(candidates: &[&str]) -> Option { 2 let mut idx = 0; 3 loop { 4 if idx >= candidates.len() { 5 break None; // ran out of candidates 6 } 7 let raw = candidates[idx]; 8 idx += 1; 9 match raw.parse::() { 10 Ok(n) if n % 2 == 0 => break Some(n), // first even wins 11 _ => continue, // unparseable or odd: skip 12 } 13 } 14 } 15 16 fn main() { 17 println!("{:?}", first_even_parse(&["x", "3", "10", "12"])); // Some(10) 18 println!("{:?}", first_even_parse(&["1", "abc", "7"])); // None 19 } ``` **Output:** ```text 1 Some(10) 2 None ``` > **Tip:** The idiomatic one-liner is `candidates.iter().find_map(|s| s.parse::().ok().filter(|n| n % 2 == 0))`. Reach for the explicit `loop` form when you need the index or extra side effects; reach for `find_map` when you don’t. *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * `continue` skips to the next iteration; `break` exits the loop, same as JavaScript. * A `loop` is an **expression**: `break value;` makes the loop evaluate to `value`. * `for`/`while` loops always evaluate to `()`, so their `break` must be value-less. * All `break value` expressions in one loop must share a single type. * `continue` never carries a value. * The “skip with `continue`, return with `break value`” pattern frequently has a cleaner iterator equivalent (`filter`, `take_while`, `find`, `find_map`). **Key syntax** (skeleton, not runnable — `skip`/`done`/`work`/`found`/`value` are placeholders): ```text 1 for x in iter { 2 if skip(x) { continue; } // skip this item 3 if done(x) { break; } // value-less break (for/while) 4 work(x); 5 } 6 7 let answer = loop { 8 if found() { break value; } // break WITH a value (loop only) 9 }; ``` # Labeled Loops > Name an outer loop with 'label to break or continue it from nested loops. Like JavaScript labels, but type-checked in Rust and able to break with a value. When you nest loops, a plain `break` or `continue` only affects the *innermost* loop. **Labeled loops** let you name an outer loop and target it directly, so you can break out of several loops at once or skip to the next iteration of an outer loop. JavaScript has the exact same feature, so the concept will be familiar. But Rust’s version is type-checked, can carry a value, and even works on plain blocks. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) A **loop label** is an identifier prefixed with an apostrophe (`'outer:`) placed before a `loop`, `while`, or `for`. You then write `break 'outer` or `continue 'outer` to control *that* loop from anywhere inside the nested loops. This is Rust’s clean, structured alternative to flag variables or `goto`, and unlike JavaScript labels, a labeled `loop` can also `break` *with a value*. > **Note:** This page focuses on the **labeling** mechanism. Plain `break`/`continue` (including `break` returning a value from a single `loop`) is covered in [Break and Continue](/04-control-flow/04-break-continue/), and the loop forms themselves in [Loops](/04-control-flow/01-loops/). *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) JavaScript has supported labeled statements since the beginning. A label is an identifier followed by a colon, and `break`/`continue` can name it: ```typescript 1 // Find the first cell matching a target in a 2-D grid. 2 const grid: number[][] = [ 3 [1, 2, 3], 4 [4, 5, 99], 5 [7, 8, 9], 6 ]; 7 const target = 99; 8 let found: [number, number] | null = null; 9 10 outer: for (let r = 0; r < grid.length; r++) { 11 for (let c = 0; c < grid[r].length; c++) { 12 if (grid[r][c] === target) { 13 found = [r, c]; 14 break outer; // breaks BOTH loops at once 15 } 16 } 17 } 18 19 console.log( 20 found ? `Found ${target} at row ${found[0]}, col ${found[1]}` : "not found", 21 ); 22 // → Found 99 at row 1, col 2 23 24 // `continue` to a label: abandon the current row, move to the next one. 25 const matrix: number[][] = [ 26 [1, 2, -1, 4], 27 [5, 6, 7, 8], 28 ]; 29 30 rows: for (const row of matrix) { 31 let sum = 0; 32 for (const val of row) { 33 if (val < 0) { 34 console.log("negative -> skip rest of row"); 35 continue rows; // jump to the next iteration of `rows` 36 } 37 sum += val; 38 } 39 console.log("row sum:", sum); 40 } 41 // → negative -> skip rest of row 42 // → row sum: 26 ``` Two things to note about JavaScript labels: there is **no leading sigil** (just `outer:`), and a labeled `break` can *never* produce a value. Labels are purely control-flow markers. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) The same two programs in Rust. The structure is nearly identical: the differences are the leading apostrophe on the label and that we iterate with iterators instead of C-style index loops: ```rust 1 fn main() { 2 // Find the first cell matching a target in a 2-D grid. 3 let grid = [ 4 [1, 2, 3], 5 [4, 5, 99], 6 [7, 8, 9], 7 ]; 8 let target = 99; 9 let mut found = None; 10 11 'rows: for (r, row) in grid.iter().enumerate() { 12 for (c, &val) in row.iter().enumerate() { 13 if val == target { 14 found = Some((r, c)); 15 break 'rows; // breaks BOTH loops at once 16 } 17 } 18 } 19 20 match found { 21 Some((r, c)) => println!("Found {target} at row {r}, col {c}"), 22 None => println!("{target} not found"), 23 } 24 } ``` Real output: ```text 1 Found 99 at row 1, col 2 ``` And `continue` to a label: ```rust 1 fn main() { 2 let matrix = [ 3 [1, 2, -1, 4], 4 [5, 6, 7, 8], 5 [9, -1, 11, 12], 6 ]; 7 8 'rows: for row in &matrix { 9 let mut sum = 0; 10 for &val in row { 11 if val < 0 { 12 println!("Negative found, skipping rest of this row"); 13 continue 'rows; // jump to the next iteration of `'rows` 14 } 15 sum += val; 16 } 17 println!("Row sum: {sum}"); 18 } 19 } ``` Real output: ```text 1 Negative found, skipping rest of this row 2 Row sum: 26 3 Negative found, skipping rest of this row ``` > **Note:** The label is written `'rows` with a leading apostrophe, the same syntax Rust uses for [lifetimes](/05-ownership/04-lifetimes/). They are unrelated concepts that happen to share the apostrophe sigil; a loop label is never a lifetime, and the compiler always knows which one you mean from context. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Defining and targeting a label [Section titled “Defining and targeting a label”](#defining-and-targeting-a-label) A label is an identifier with a leading apostrophe, written immediately before the loop keyword and followed by a colon: ```rust 1 'outer: for i in 0..3 { 2 'inner: for j in 0..3 { 3 // ... 4 } 5 } ``` Inside the nested loops, `break`/`continue` take an *optional* label argument: | Statement | Effect | | ----------------- | ------------------------------------------------------------------ | | `break` | Exits the **innermost** enclosing loop. | | `break 'outer` | Exits the loop labeled `'outer` (and everything nested inside it). | | `continue` | Skips to the next iteration of the **innermost** loop. | | `continue 'outer` | Skips to the next iteration of the loop labeled `'outer`. | Without a label, control flow always targets the closest loop, exactly like JavaScript. The label simply lets you “reach past” inner loops. ### Walking through the grid search [Section titled “Walking through the grid search”](#walking-through-the-grid-search) ```rust 1 'rows: for (r, row) in grid.iter().enumerate() { // (1) 2 for (c, &val) in row.iter().enumerate() { // (2) 3 if val == target { 4 found = Some((r, c)); // (3) 5 break 'rows; // (4) 6 } 7 } 8 } ``` 1. The outer loop is labeled `'rows`. `.enumerate()` pairs each row with its index `r`. 2. The inner loop is **unlabeled**; we never need to target it specifically. 3. We record the coordinates in `found` (a `mut` variable) before leaving. 4. `break 'rows` exits *both* loops in one statement. A plain `break` here would only end the inner loop, and the outer loop would keep scanning the remaining rows. ### Why a label instead of a flag? [Section titled “Why a label instead of a flag?”](#why-a-label-instead-of-a-flag) Without labels, escaping nested loops in many languages requires a boolean flag that you check after the inner loop: ```rust 1 fn main() { 2 let grid = [[1, 2, 3], [4, 5, 99], [7, 8, 9]]; 3 let target = 99; 4 let mut found = None; 5 6 let mut done = false; // extra bookkeeping 7 for (r, row) in grid.iter().enumerate() { 8 for (c, &val) in row.iter().enumerate() { 9 if val == target { 10 found = Some((r, c)); 11 done = true; 12 break; // only breaks the inner loop 13 } 14 } 15 if done { // ...so we re-check out here 16 break; 17 } 18 } 19 20 println!("{found:?}"); 21 } ``` The labeled version removes the flag and the second check entirely. It is shorter, has no extra mutable state, and makes the intent (“leave the whole search”) obvious at the `break` site. ### `continue 'label` vs `break 'label` [Section titled “continue 'label vs break 'label”](#continue-label-vs-break-label) `continue 'rows` does **not** leave the outer loop. It abandons the rest of the *current* outer iteration and advances `'rows` to its next value. In the matrix example, when a negative number appears we stop summing that row and move straight to the next row, which is why `Row sum:` is never printed for rows containing a negative. ### Labeled `loop` can break *with a value* [Section titled “Labeled loop can break with a value”](#labeled-loop-can-break-with-a-value) This is where Rust goes beyond JavaScript. A plain `loop` can return a value via `break value` (see [Break and Continue](/04-control-flow/04-break-continue/)), and that works through a label too. So you can search nested loops and produce the result as an expression: ```rust 1 fn main() { 2 let needle = 7; 3 let haystack = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; 4 5 // The whole search is an expression that evaluates to the position. 6 let position = 'search: loop { 7 for (r, row) in haystack.iter().enumerate() { 8 for (c, &v) in row.iter().enumerate() { 9 if v == needle { 10 break 'search Some((r, c)); // exits the loop WITH a value 11 } 12 } 13 } 14 break 'search None; // searched everything, found nothing 15 }; 16 17 println!("position = {position:?}"); 18 } ``` Real output: ```text 1 position = Some((2, 0)) ``` Here `'search` labels a `loop`, the inner `for` loops do the scanning, and `break 'search ` jumps out and hands `position` its value. The outer `loop` runs its body exactly once; it exists purely so we have a breakable construct that can carry a value. > **Important:** Only `loop` can `break` with a value. You **cannot** `break 'label some_value` out of a `for` or `while` loop — see the Common Pitfalls section for the exact compiler error. ### Labeled blocks (no loop required) [Section titled “Labeled blocks (no loop required)”](#labeled-blocks-no-loop-required) Since Rust 1.65, you can label a *plain block* and `break` out of it, no loop involved. This is handy for “compute a value with early exits” without a function: ```rust 1 fn classify(score: i32) -> &'static str { 2 let label = 'check: { 3 if score < 0 { 4 break 'check "invalid"; 5 } 6 if score >= 90 { 7 break 'check "A"; 8 } 9 if score >= 80 { 10 break 'check "B"; 11 } 12 "C or below" // the block's final value if no `break` fired 13 }; 14 label 15 } 16 17 fn main() { 18 println!("{}", classify(95)); // A 19 println!("{}", classify(82)); // B 20 println!("{}", classify(50)); // C or below 21 println!("{}", classify(-3)); // invalid 22 } ``` Real output: ```text 1 A 2 B 3 C or below 4 invalid ``` A labeled block uses `break 'label value` to short-circuit, and the block evaluates to that value. JavaScript has no equivalent; its labeled blocks support `break label` but cannot produce a value. *** ## Key Differences from TypeScript/JavaScript [Section titled “Key Differences from TypeScript/JavaScript”](#key-differences-from-typescriptjavascript) | Aspect | JavaScript | Rust | | -------------------------------- | ------------------------------------ | --------------------------------------------------------- | | Label syntax | `outer:` (no sigil) | `'outer:` (leading apostrophe) | | `break label` / `continue label` | Supported | Supported (`break 'label` / `continue 'label`) | | Break **with a value** | Not possible; labels are flow only | A labeled **`loop`** can `break 'label value` | | Label on a plain block | `block: { break block; }` (no value) | `'block: { break 'block value; }` (yields a value, 1.65+) | | Unused label | Silently allowed | Triggers a `warning: unused label` | | Typo’d / unknown label | `SyntaxError: Undefined label` | Compile error `E0426` with a “did you mean” suggestion | | `continue label` to a non-loop | `SyntaxError` | Compile error | The headline differences: Rust requires the apostrophe, treats labels as type-checked control flow (so a wrong name is a compile error, not a runtime surprise), warns when a label is unused, and lets a labeled `loop` carry a value out, something JavaScript labels can never do. > **Tip:** Conventionally Rust labels describe what the loop iterates over (`'rows`, `'pixels`, `'retry`, `'search`) rather than generic `'outer`/`'inner`. A descriptive name makes `break 'rows` read like a sentence. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Forgetting the leading apostrophe [Section titled “Pitfall 1: Forgetting the leading apostrophe”](#pitfall-1-forgetting-the-leading-apostrophe) JavaScript labels have no sigil, so it is natural to write `outer:` in Rust. The compiler rejects it: ```rust 1 fn main() { 2 outer: for i in 0..3 { // does not compile (error: malformed loop label) 3 for j in 0..3 { 4 if i + j == 2 { break outer; } 5 } 6 } 7 } ``` The real error (abridged): ```text 1 error: malformed loop label 2 --> src/main.rs:2:5 3 | 4 2 | outer: for i in 0..3 { // does not compile (error: malformed loop label) 5 | ^^^^^ 6 | 7 help: use the correct loop label format 8 | 9 2 | 'outer: for i in 0..3 { // does not compile (error: malformed loop label) 10 | + ``` The fix is exactly what the compiler suggests: add the `'`, giving `'outer: for ...` and `break 'outer`. ### Pitfall 2: Misspelling the label [Section titled “Pitfall 2: Misspelling the label”](#pitfall-2-misspelling-the-label) A label name that doesn’t exist is a compile-time error (`E0426`), not a runtime failure as in some languages. The compiler even suggests the nearest match: ```rust 1 fn main() { 2 'outer: for i in 0..3 { 3 for j in 0..3 { 4 if i + j == 2 { 5 break 'outr; // does not compile (error[E0426]: use of undeclared label `'outr`) 6 } 7 } 8 } 9 } ``` The real error (abridged): ```text 1 error[E0426]: use of undeclared label `'outr` 2 --> src/main.rs:5:23 3 | 4 2 | 'outer: for i in 0..3 { 5 | ------ a label with a similar name is reachable 6 ... 7 5 | break 'outr; // typo: label doesn't exist 8 | ^^^^^ undeclared label `'outr` 9 | help: try using similarly named label: `'outer` ``` ### Pitfall 3: Trying to `break` with a value out of a labeled `for`/`while` [Section titled “Pitfall 3: Trying to break with a value out of a labeled for/while”](#pitfall-3-trying-to-break-with-a-value-out-of-a-labeled-forwhile) Because plain `loop` is the only construct that returns a value, attempting `break 'label value` from a `for` (or `while`) is rejected with `E0571`: ```rust 1 fn main() { 2 let x = 'outer: for i in 0..5 { 3 for j in 0..5 { 4 if i * j > 6 { 5 break 'outer i * j; // does not compile (error[E0571]) 6 } 7 } 8 }; 9 println!("{x:?}"); 10 } ``` The real error (abridged): ```text 1 error[E0571]: `break` with value from a `for` loop 2 --> src/main.rs:5:17 3 | 4 2 | let x = 'outer: for i in 0..5 { 5 | --------------------- you can't `break` with a value in a `for` loop 6 ... 7 5 | break 'outer i * j; 8 | ^^^^^^^^^^^^^^^^^^ can only break with a value inside `loop` or breakable block 9 help: use `break` on its own without a value inside this `for` loop ``` **Fixes:** either (a) set a `mut` variable before a plain `break 'outer`, or (b) restructure as a labeled `loop` (which *can* carry a value), or (c) use a labeled block. The Real-World Example below uses approach (b). ### Pitfall 4: A label you never target (dead label) [Section titled “Pitfall 4: A label you never target (dead label)”](#pitfall-4-a-label-you-never-target-dead-label) If you label a loop but every `break`/`continue` inside is unlabeled, the label does nothing, and Rust warns you, which often reveals a bug (you *meant* to write `break 'rows`): ```rust 1 fn main() { 2 'outer: for i in 0..3 { // label defined but never targeted 3 for j in 0..3 { 4 if i + j == 2 { 5 break; // breaks only the inner loop 6 } 7 } 8 } 9 } ``` The real warning: ```text 1 warning: unused label 2 --> src/main.rs:2:5 3 | 4 2 | 'outer: for i in 0..3 { // label defined but never targeted 5 | ^^^^^^ 6 | 7 = note: `#[warn(unused_labels)]` on by default ``` ### Pitfall 5: Reaching for labels when an iterator method is clearer [Section titled “Pitfall 5: Reaching for labels when an iterator method is clearer”](#pitfall-5-reaching-for-labels-when-an-iterator-method-is-clearer) Labeled loops are great for genuinely nested control flow, but a flat search over a single sequence is usually better expressed with iterator adapters. Prefer `.find()`/`.position()`/`.any()` (covered in [Loops](/04-control-flow/01-loops/) and the collections section) when there is no real nesting; they are shorter and harder to get wrong than a hand-rolled labeled loop. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Name labels after what they iterate [Section titled “1. Name labels after what they iterate”](#1-name-labels-after-what-they-iterate) `'rows`, `'tiles`, `'retry`, `'connections` read far better at the `break` site than `'outer`/`'l1`. The label is documentation: `continue 'rows` should tell the reader exactly what is being skipped. ### 2. Use a labeled `loop` (not `for`) when you need a value out [Section titled “2. Use a labeled loop (not for) when you need a value out”](#2-use-a-labeled-loop-not-for-when-you-need-a-value-out) If the goal of the search is to produce a result, model it as `let result = 'name: loop { ...; break 'name value; };`. This keeps the result as a single immutable binding instead of a `mut` flag mutated across iterations. ### 3. Only label the loop you actually target [Section titled “3. Only label the loop you actually target”](#3-only-label-the-loop-you-actually-target) Adding a label to a loop you never `break`/`continue` to is noise, and Rust warns about it. Label exactly the loop(s) that need targeting; leave the rest unlabeled. ### 4. Don’t nest deeper than you can read [Section titled “4. Don’t nest deeper than you can read”](#4-dont-nest-deeper-than-you-can-read) Three or more nested labeled loops are a smell. Consider extracting the inner work into a function that returns `Option`/`Result` and using `?` or an early `return`, which often removes the need for labels altogether. (Early `return` from a helper is frequently the cleanest “break out of everything”.) ### 5. Reach for iterators first for non-nested logic [Section titled “5. Reach for iterators first for non-nested logic”](#5-reach-for-iterators-first-for-non-nested-logic) As in Pitfall 5: a label on a single loop scanning one collection usually wants to be `.find()`/`.any()`/`.filter()` instead. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) ### Connection failover with bounded retries [Section titled “Connection failover with bounded retries”](#connection-failover-with-bounded-retries) A common production pattern: try a list of endpoints, and for each, retry a few times before moving on. The moment any attempt succeeds, abandon *all* remaining endpoints and attempts. A labeled `loop` returning a value expresses this cleanly: ```rust 1 #[derive(Debug)] 2 struct Endpoint { 3 name: &'static str, 4 // For the demo: the attempt number on which this endpoint starts working. 5 // 0 means it never succeeds. 6 succeeds_on_attempt: u32, 7 } 8 9 fn try_connect(ep: &Endpoint, attempt: u32) -> Result { 10 if ep.succeeds_on_attempt != 0 && attempt >= ep.succeeds_on_attempt { 11 Ok(format!("connected to {} on attempt {attempt}", ep.name)) 12 } else { 13 Err(format!("{} refused (attempt {attempt})", ep.name)) 14 } 15 } 16 17 fn main() { 18 let endpoints = [ 19 Endpoint { name: "primary", succeeds_on_attempt: 0 }, // never succeeds 20 Endpoint { name: "replica", succeeds_on_attempt: 2 }, // works on the 2nd try 21 Endpoint { name: "fallback", succeeds_on_attempt: 1 }, 22 ]; 23 const MAX_ATTEMPTS: u32 = 3; 24 25 // The whole failover dance is one expression yielding the connection. 26 let connection = 'endpoints: loop { 27 for ep in &endpoints { 28 for attempt in 1..=MAX_ATTEMPTS { 29 match try_connect(ep, attempt) { 30 Ok(conn) => break 'endpoints Some(conn), // success: leave everything 31 Err(e) => { 32 eprintln!("warn: {e}"); 33 if attempt == MAX_ATTEMPTS { 34 eprintln!("giving up on {}, trying next endpoint", ep.name); 35 } 36 } 37 } 38 } 39 } 40 break 'endpoints None; // every endpoint exhausted 41 }; 42 43 match connection { 44 Some(conn) => println!("OK: {conn}"), 45 None => println!("ERROR: all endpoints failed"), 46 } 47 } ``` Real output: ```text 1 warn: primary refused (attempt 1) 2 warn: primary refused (attempt 2) 3 warn: primary refused (attempt 3) 4 giving up on primary, trying next endpoint 5 warn: replica refused (attempt 1) 6 OK: connected to replica on attempt 2 ``` The `break 'endpoints Some(conn)` punches straight out of the attempt loop *and* the endpoint loop *and* the outer `loop`, delivering the successful connection as `connection` in one move. Without the label this would need a flag checked after each loop level, or the whole thing extracted into a helper that `return`s early. > **Tip:** In real networking code each retry would be spaced out with a backoff delay and `try_connect` would be `async`. The control-flow shape — labeled loop, break-with-value on success — stays exactly the same; async control flow is covered later in the guide. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Loop Labels to Disambiguate Between Multiple Loops](https://doc.rust-lang.org/book/ch03-05-control-flow.html#loop-labels-to-disambiguate-between-multiple-loops) * [Rust Reference — Loop labels](https://doc.rust-lang.org/reference/expressions/loop-expr.html#loop-labels) * [Rust Reference — Labelled block expressions](https://doc.rust-lang.org/reference/expressions/loop-expr.html#labelled-block-expressions) * [MDN — `label` statement (JavaScript)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Loops](/04-control-flow/01-loops/): `for`, `while`, and `loop`; `break` returning a value from a single `loop`; no C-style `for`. * [break / continue](/04-control-flow/04-break-continue/): the unlabeled forms these statements build on. * [match](/04-control-flow/02-match/): exhaustive pattern matching, often pairs with labeled loops in search code. * [if let / while let](/04-control-flow/03-if-let-while-let/) — concise pattern matching that can replace some labeled-loop bookkeeping. * [Conditionals](/04-control-flow/00-conditionals/): `if` as an expression; Rust has no truthiness. * [Section 04 overview](/04-control-flow/) * Earlier groundwork: [Getting Started](/01-getting-started/), [Basics](/02-basics/), and the [Introduction](/00-introduction/). * Next up: [Ownership](/05-ownership/) — where the apostrophe shows up again as a *lifetime* (a different concept). *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: First common element [Section titled “Exercise 1: First common element”](#exercise-1-first-common-element) **Difficulty:** Beginner **Objective:** Use a labeled loop to break out of two nested loops at once. **Instructions:** Implement `first_common`, which returns the first element of `a` that also appears in `b` (scanning `a` in order), or `None` if there is no overlap. Break out of both loops as soon as you find a match. ```rust 1 fn first_common(a: &[i32], b: &[i32]) -> Option { 2 // TODO: scan `a`; for each element scan `b`; on a match, break out of BOTH loops. 3 /* ??? */ 4 } 5 6 fn main() { 7 println!("{:?}", first_common(&[1, 2, 3, 4], &[9, 8, 3, 1])); // Some(1) 8 println!("{:?}", first_common(&[1, 2], &[3, 4])); // None 9 } ``` Solution Because a `for` loop cannot `break` with a value, record the hit in a `mut` variable and then break the labeled loop: ```rust 1 fn first_common(a: &[i32], b: &[i32]) -> Option { 2 let mut result = None; 3 'outer: for &x in a { 4 for &y in b { 5 if x == y { 6 result = Some(x); 7 break 'outer; // leave both loops; value is returned below 8 } 9 } 10 } 11 result 12 } 13 14 fn main() { 15 println!("{:?}", first_common(&[1, 2, 3, 4], &[9, 8, 3, 1])); // Some(1) 16 println!("{:?}", first_common(&[1, 2], &[3, 4])); // None 17 } ``` Real output: ```text 1 Some(1) 2 None ``` > **Note:** The answer is `Some(1)`, not `Some(3)`: we scan `a` in order, and `1` (the first element of `a`) is present in `b`, so it matches before we ever reach `3`. ### Exercise 2: Skip invalid rows with `continue 'label` [Section titled “Exercise 2: Skip invalid rows with continue 'label”](#exercise-2-skip-invalid-rows-with-continue-label) **Difficulty:** Intermediate **Objective:** Use `continue` to a label to abandon a whole outer iteration. **Instructions:** Implement `sum_valid_rows`. Sum every row of the grid, but if a row contains a `0`, treat the entire row as invalid and exclude it from the total. Use `continue 'rows` so that hitting a `0` immediately skips the rest of that row. ```rust 1 fn sum_valid_rows(grid: &[[i32; 3]]) -> i32 { 2 // TODO: a labeled outer loop; `continue 'rows` when you see a 0. 3 /* ??? */ 4 } 5 6 fn main() { 7 let grid = [ 8 [1, 2, 3], // valid -> 6 9 [4, 0, 6], // has a 0 -> discarded 10 [7, 8, 9], // valid -> 24 11 ]; 12 println!("{}", sum_valid_rows(&grid)); // 30 13 } ``` Solution ```rust 1 fn sum_valid_rows(grid: &[[i32; 3]]) -> i32 { 2 let mut total = 0; 3 'rows: for row in grid { 4 let mut row_sum = 0; 5 for &v in row { 6 if v == 0 { 7 continue 'rows; // discard this row entirely, move to the next 8 } 9 row_sum += v; 10 } 11 total += row_sum; // only reached if no 0 was found in the row 12 } 13 total 14 } 15 16 fn main() { 17 let grid = [ 18 [1, 2, 3], 19 [4, 0, 6], 20 [7, 8, 9], 21 ]; 22 println!("{}", sum_valid_rows(&grid)); // 30 23 } ``` Real output: ```text 1 30 ``` The key is that `continue 'rows` skips the `total += row_sum;` line for the discarded row, because it jumps directly to the next iteration of the outer loop. ### Exercise 3: 3-D search with a value-returning labeled loop [Section titled “Exercise 3: 3-D search with a value-returning labeled loop”](#exercise-3-3-d-search-with-a-value-returning-labeled-loop) **Difficulty:** Advanced **Objective:** Combine three nested loops with a labeled `loop` that breaks *with a value*. **Instructions:** Implement `find_in_cube`, which searches a 2x2x2 cube of integers and returns the `(x, y, z)` coordinates of the first cell equal to `target`, or `None`. Model the search as a labeled `loop` so you can `break 'search Some((x, y, z))` directly with the coordinates. ```rust 1 fn find_in_cube(cube: &[[[i32; 2]; 2]; 2], target: i32) -> Option<(usize, usize, usize)> { 2 // TODO: 'search: loop { for x { for y { for z { ... break 'search Some(...) } } } break 'search None } 3 /* ??? */ 4 } 5 6 fn main() { 7 let cube = [ 8 [[0, 1], [2, 3]], 9 [[4, 5], [6, 7]], 10 ]; 11 println!("{:?}", find_in_cube(&cube, 6)); // Some((1, 1, 0)) 12 println!("{:?}", find_in_cube(&cube, 99)); // None 13 } ``` Solution ```rust 1 fn find_in_cube(cube: &[[[i32; 2]; 2]; 2], target: i32) -> Option<(usize, usize, usize)> { 2 'search: loop { 3 for (x, plane) in cube.iter().enumerate() { 4 for (y, row) in plane.iter().enumerate() { 5 for (z, &v) in row.iter().enumerate() { 6 if v == target { 7 break 'search Some((x, y, z)); // out of all three loops, with the coords 8 } 9 } 10 } 11 } 12 break 'search None; // searched the whole cube, found nothing 13 } 14 } 15 16 fn main() { 17 let cube = [ 18 [[0, 1], [2, 3]], 19 [[4, 5], [6, 7]], 20 ]; 21 println!("{:?}", find_in_cube(&cube, 6)); // Some((1, 1, 0)) 22 println!("{:?}", find_in_cube(&cube, 99)); // None 23 } ``` Real output: ```text 1 Some((1, 1, 0)) 2 None ``` Wrapping the three `for` loops in a single `'search: loop` gives you one breakable construct that can carry the `Option` straight out of all the nesting: no `mut` flag, no post-loop bookkeeping. *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Labels are identifiers with a leading apostrophe (`'rows:`) placed before `loop`, `while`, or `for`. * `break 'label` exits that loop (and everything nested in it); `continue 'label` skips to its next iteration. * A labeled **`loop`** can `break 'label value`; JavaScript labels can never produce a value. * Labeled **blocks** (1.65+) let you `break` out of a plain block with a value. * The compiler warns on unused labels (`unused_labels`) and rejects misspelled (`E0426`), malformed, and value-breaking-`for` (`E0571`) labels at compile time. **Key differences from JavaScript:** the apostrophe sigil, type-checked labels (errors not runtime surprises), the unused-label warning, and value-returning labeled `loop`s/blocks. # Rust Ownership Explained > Rust ownership replaces garbage collection: stack vs heap, the three rules, borrowing, lifetimes, move/copy/clone, Rc/Arc, and Drop, mapped from TypeScript. **Ownership** is the single biggest idea that separates Rust from garbage-collected TypeScript and JavaScript, and the reason a Rust program can be memory-safe with no garbage collector and no manual `free`. Instead of a runtime that periodically scans for unreachable objects, the Rust compiler tracks, at *compile time*, exactly who owns each value and inserts cleanup at precisely the right moment. This section builds that model from the ground up: the **stack/heap** memory layout it rests on, the three **ownership rules**, **borrowing** with `&` and `&mut`, **lifetimes**, **move/copy/clone** semantics, **reference counting** for shared ownership, and **Drop/RAII** for deterministic cleanup. > **Note:** This is the steepest part of the climb for a TypeScript/JavaScript developer, and the most rewarding. Almost everything that feels strange about Rust at first (why you can’t use a variable after passing it, why the compiler talks about “borrows,” why `&` is everywhere) comes from this one system. Once it clicks, the rest of Rust gets dramatically easier. *** ## What You’ll Learn [Section titled “What You’ll Learn”](#what-youll-learn) * Where values live — the **stack** vs the **heap** — and why Rust makes this visible when TypeScript/JavaScript hides it behind a GC * The **three ownership rules** that replace garbage collection: one owner per value, move on assignment, drop at end of scope * How to **borrow** data with `&` (shared/immutable references) without copying or transferring ownership, and how the borrow checker prevents dangling references * How `&mut` works and the **one-mutable-XOR-many-shared** rule that prevents data races at compile time * Why a value is **moved**, **copied**, or **cloned**, and how to control which one happens (`Copy` vs `Clone`) * What **lifetimes** (`'a`) are, why they exist, and when **elision** lets you omit the annotations entirely * How to share a single value among several owners with `Rc` (single-threaded) and `Arc` (thread-safe) * How the **`Drop` trait** and **RAII** give you deterministic, leak-resistant cleanup: the opposite of JavaScript’s GC and `FinalizationRegistry` *** ## Topics [Section titled “Topics”](#topics) Read these in order; each one builds on the last. The numbered order matches the navigation links inside the files. | # | Topic | What it covers | | - | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | [Stack and Heap](/05-ownership/00-stack-heap/) | The stack vs heap memory model: what lives where, fixed-size handles vs heap buffers, versus JavaScript’s GC-managed heap and value/reference split, and why it matters in Rust. | | 2 | [The Three Ownership Rules](/05-ownership/01-ownership-rules/) | Each value has exactly one **owner**; ownership **moves** on assignment and when passed to a function; the value is **dropped** at the end of the owner’s scope. | | 3 | [Borrowing and References](/05-ownership/02-borrowing/) | Shared/immutable borrows with `&`; how Rust references differ from JavaScript object references; the borrow checker and how dangling references are prevented. | | 4 | [Mutable References](/05-ownership/03-mutable-references/) | `&mut`; the one-mutable-XOR-many-shared rule; non-lexical lifetimes; how data races are ruled out at compile time. | | 5 | [Lifetimes](/05-ownership/04-lifetimes/) | Lifetime annotations (`'a`), why they exist, and how they relate input and output lifetimes in function signatures and structs. | | 6 | [Lifetime Elision](/05-ownership/05-lifetime-elision/) | The three elision rules, when annotations can be omitted, and the common patterns that “just work” without `'a`. | | 7 | [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/) | Move semantics vs JavaScript’s reference copy; the `Copy` trait (cheap stack duplicates); `Clone` (explicit deep copy); exactly when each happens. | | 8 | [Reference Counting (`Rc`/`Arc`)](/05-ownership/07-reference-counting/) | `Rc` (single-thread) and `Arc` (thread-safe) for shared ownership; strong counts. A light intro; the full treatment is in [Section 10](/10-smart-pointers/). | | 9 | [The Drop Trait and RAII](/05-ownership/08-drop-trait/) | RAII and deterministic, scope-based cleanup; the `Drop` trait; drop order; `std::mem::drop`; versus JavaScript GC / `FinalizationRegistry`. | *** ## Learning Objectives [Section titled “Learning Objectives”](#learning-objectives) By the end of this section, a TypeScript/JavaScript developer should be able to: 1. Explain, in plain terms, where a value lives (stack vs heap) and why that determines whether assignment **moves** or **copies** it. 2. State the three ownership rules and trace, by reading the code, exactly when each value is dropped. 3. Choose deliberately between **borrowing**, **cloning**, and **shared ownership** for a given design, and reach for `.clone()` intentionally rather than reflexively. 4. Read and write `&` and `&mut` references that satisfy the borrow checker, and apply the one-mutable-XOR-many-shared rule. 5. Add lifetime annotations (`'a`) only where the compiler genuinely needs them, and recognize where the three elision rules let you omit them. 6. Use `Rc`/`Arc` to model shared ownership and inspect a strong count. 7. Use the `Drop` trait and RAII (and `std::mem::drop` for early cleanup) to manage resources deterministically. 8. Recognize and fix the common borrow-checker errors: `E0382` (use after move), `E0499` (two `&mut`), `E0502` (`&` and `&mut` together), `E0515`/`E0597` (returning/holding a dangling reference). *** ## Prerequisites [Section titled “Prerequisites”](#prerequisites) This section assumes you have completed: * **[Section 03: Functions](/03-functions/)**: typed parameters and return values. Ownership crosses **every** function boundary, so the difference between an owned parameter (`String`) and a borrowed one (`&str`) is central here. * **[Section 04: Control Flow](/04-control-flow/)**: scopes and blocks. A value is dropped at the closing `}` of its scope, so knowing exactly where scopes begin and end is what makes drop timing predictable. If immutability-by-default (`let` vs `let mut`) or the stack/heap idea feels unfamiliar, revisit **[Section 02 — Variables and Mutability](/02-basics/00-variables/)** first. Ownership builds directly on it. > **Tip:** The apostrophe in a lifetime (`'a`) is the same sigil Rust uses for loop **labels** (`'outer:`) in [Section 04 — Labeled Loops](/04-control-flow/05-labeled-loops/). They are unrelated concepts that merely share punctuation; the compiler always knows which one you mean from context. *** ## Estimated Time [Section titled “Estimated Time”](#estimated-time) This is the most important — and most demanding — section in the guide, so budget extra time and work through every example by hand. * **Reading:** 5-6 hours * **Hands-on Practice:** 4-5 hours * **Exercises:** 3-5 hours * **Total:** 12-16 hours > **Tip:** Do not rush this section. The recommended order is the list order above: stack/heap → ownership rules → borrowing → mutable references → lifetimes → lifetime elision → move/copy/clone → reference counting → Drop. Start with [Stack and Heap](/05-ownership/00-stack-heap/): the memory model it builds is the foundation everything else rests on. If a borrow-checker error frustrates you, that frustration *is* the learning — read the compiler’s suggestion, it is unusually good at proposing the fix. *** ## Frequently asked questions [Section titled “Frequently asked questions”](#frequently-asked-questions) ### Why does the compiler say a value was “moved”? [Section titled “Why does the compiler say a value was “moved”?”](#why-does-the-compiler-say-a-value-was-moved) Assigning a non-`Copy` value or passing it to a function transfers ownership, leaving the original binding unusable. Borrow it with `&` to lend access instead, or call `.clone()` when you genuinely need a second independent copy. See [The Three Ownership Rules](/05-ownership/01-ownership-rules/). ### What is the difference between `&` and `&mut`? [Section titled “What is the difference between & and \&mut?”](#what-is-the-difference-between--and-mut) `&T` is a shared, read-only borrow; `&mut T` is an exclusive, writable borrow. The rule is one `&mut` XOR any number of `&` at the same time, which is how Rust rules out data races at compile time. See [Borrowing](/05-ownership/02-borrowing/). ### When do I need `Rc` or `Arc`? [Section titled “When do I need Rc or Arc?”](#when-do-i-need-rc-or-arc) When one value genuinely needs several owners, such as a node pointed to by multiple parents. `Rc` is for a single thread and `Arc` is thread-safe. For single ownership, plain ownership or `&` references are cheaper. See [Reference Counting](/05-ownership/07-reference-counting/). # Stack and Heap: Where Your Data Lives > Where Rust values live: fixed-size data on the stack, growable data like String and Vec on the heap — what JS hides behind its GC, Rust makes explicit. Before you can understand Rust’s ownership system, you need a mental model of **where** values live in memory: the **stack** or the **heap**. In TypeScript/JavaScript a garbage collector hides this distinction from you completely. In Rust the distinction is front and center: it is the reason ownership exists at all. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) The **stack** is a fast, fixed-size region where values of a known, fixed size are stored and freed automatically as functions enter and exit. The **heap** is a larger, flexible region for values whose size can grow or is not known at compile time (like a growable string). In TypeScript/JavaScript every object lives on a garbage-collected heap and you never think about it; in Rust, knowing what lives where explains *moves*, *borrows*, and why there is no garbage collector. > **Note:** This file covers the **memory model**: what the stack and heap are, what lives where, and why it matters. The *rules* that govern this memory (one owner, move-on-assign, drop-at-scope-end) are covered in [The Three Ownership Rules](/05-ownership/01-ownership-rules/), and the mechanics of moving versus copying live in [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/). *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) In JavaScript, you never allocate or free memory yourself. Primitives feel like values; objects, arrays, and functions are handled by reference. A garbage collector reclaims anything you stop using. ```typescript 1 // TypeScript/JavaScript — the engine decides where everything goes 2 const age = 30; // a number (always IEEE-754 f64 under the hood) 3 const name = "Grace Hopper"; // a string 4 const scores = [90, 85, 100]; // an array object 5 6 // Assignment of an object copies the *reference*, not the object itself. 7 const a = { count: 1 }; 8 const b = a; // b points at the SAME object as a 9 b.count = 99; 10 console.log(a.count); // 99 — they are the same object 11 12 // Primitives behave as if copied by value. 13 let x = 5; 14 let y = x; // y is an independent copy 15 y = 6; 16 console.log(x, y); // 5 6 ``` **Key points:** * You never write `malloc`/`free`; the V8 garbage collector (GC) reclaims memory when nothing references it anymore. * Primitives (`number`, `boolean`, `string` handles, `null`, `undefined`) are passed and assigned **by value**. * Objects, arrays, and functions are passed and assigned **by reference**: `b = a` makes `a` and `b` aliases of one heap object. * The cost: GC pauses, extra memory headroom, and non-deterministic cleanup timing. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) Rust makes the same program work without a garbage collector. The size and location of every value is decided at compile time, and cleanup happens deterministically when a value goes out of scope. ```rust 1 fn main() { 2 // --- Lives entirely on the stack: fixed, known size --- 3 let age: i32 = 30; // 4 bytes 4 let price: f64 = 19.99; // 8 bytes 5 let is_active: bool = true; // 1 byte 6 let point: (i32, i32) = (3, 4); // 8 bytes (two i32s, inline) 7 let scores: [i32; 3] = [90, 85, 100]; // 12 bytes (three i32s, inline) 8 9 // --- Uses the heap: size can grow / is not known at compile time --- 10 let name: String = String::from("Grace Hopper"); // text bytes on the heap 11 let numbers: Vec = vec![1, 2, 3, 4, 5]; // elements on the heap 12 13 println!("age = {age}"); 14 println!("point = {point:?}"); 15 println!("name = {name}"); 16 println!("numbers = {numbers:?}"); 17 } ``` **Output (verified):** ```text 1 age = 30 2 point = (3, 4) 3 name = Grace Hopper 4 numbers = [1, 2, 3, 4, 5] ``` **Key points:** * Numbers, `bool`, `char`, fixed-size tuples, and fixed-size arrays live **on the stack**. * `String` and `Vec` store a small **handle** on the stack that points to a buffer **on the heap**. * There is no garbage collector. When a value’s owner goes out of scope, its heap memory is freed immediately and deterministically (see [The Drop Trait and RAII](/05-ownership/08-drop-trait/)). *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### What the stack is [Section titled “What the stack is”](#what-the-stack-is) The **stack** is a region of memory that works exactly like the data-structure of the same name: last-in, first-out. Every time a function is called, a **stack frame** is pushed holding that function’s local variables. When the function returns, its entire frame is popped in one cheap operation. The CPU tracks the top of the stack in a register, so “allocating” a stack value is just moving a pointer, essentially free. The catch: the stack only works for data whose size is **known at compile time**. The compiler must know how many bytes each local needs in order to lay out the frame. The stack is also small (commonly a few megabytes for the main thread), so it is for short-lived, fixed-size values. We can observe stack values sitting next to each other in memory: ```rust 1 fn main() { 2 let a: i32 = 1; 3 let b: i32 = 2; 4 let c: i32 = 3; 5 6 // `{:p}` formats a reference as a pointer (hex address). 7 println!("a is at {:p}", &a); 8 println!("b is at {:p}", &b); 9 println!("c is at {:p}", &c); 10 } ``` **Output (one real run, your addresses will differ):** ```text 1 a is at 0x16d4e1b44 2 b is at 0x16d4e1b48 3 c is at 0x16d4e1b4c ``` Each `i32` sits 4 bytes from the next; they are packed tightly into the current stack frame. > **Warning:** Do not read meaning into the exact addresses or whether they ascend or descend. The actual values depend on the platform, the optimization level, and the OS, and the compiler is free to reorder or even eliminate variables. The point is only that stack locals are cheap, contiguous, fixed-size slots. ### What the heap is [Section titled “What the heap is”](#what-the-heap-is) The **heap** is a larger, general-purpose region for data whose size can change at runtime or is not known up front. Asking the allocator for heap memory (“allocating”) and giving it back (“freeing”) is more expensive than a stack push/pop because the allocator has to find a suitable free block and track it. A `String` is the textbook example. Its growable text cannot live on the stack because you might `push_str` more characters later. So a `String` is really **two parts**: 1. A fixed-size **handle on the stack**: a pointer to the heap buffer, a length, and a capacity. 2. The **actual bytes on the heap**, which the pointer points to. ```rust 1 fn main() { 2 let greeting = String::from("hi"); 3 4 // The String *handle* is a stack local. 5 println!("the String handle (stack) is at {:p}", &greeting); 6 // Its text bytes live somewhere else entirely — on the heap. 7 println!("its heap buffer starts at {:p}", greeting.as_ptr()); 8 } ``` **Output (one real run):** ```text 1 the String handle (stack) is at 0x16d4e1c68 2 its heap buffer starts at 0x600000d9c060 ``` Notice the two addresses are in completely different regions of memory: the handle is on the stack (`0x16d4...`), the buffer is on the heap (`0x6000...`). This split is the single most important picture to keep in your head for the rest of this section. ```text 1 STACK HEAP 2 ┌──────────────┐ ┌───────────────────┐ 3 │ greeting │ │ 'h' 'i' │ 4 │ ptr ───────┼──────────▶ │ (the text bytes) │ 5 │ len = 2 │ └───────────────────┘ 6 │ cap = 2 │ 7 └──────────────┘ ``` ### The size of a handle is fixed [Section titled “The size of a handle is fixed”](#the-size-of-a-handle-is-fixed) Even though the *contents* of a `String` or `Vec` can grow, the *handle* on the stack is always the same fixed size. We can prove it: ```rust 1 fn main() { 2 println!("size_of i32 = {}", std::mem::size_of::()); 3 println!("size_of f64 = {}", std::mem::size_of::()); 4 println!("size_of bool = {}", std::mem::size_of::()); 5 println!("size_of char = {}", std::mem::size_of::()); 6 println!("size_of (i32,i32) = {}", std::mem::size_of::<(i32, i32)>()); 7 println!("size_of [i32; 3] = {}", std::mem::size_of::<[i32; 3]>()); 8 println!("size_of String = {}", std::mem::size_of::()); 9 println!("size_of Vec = {}", std::mem::size_of::>()); 10 println!("size_of &str = {}", std::mem::size_of::<&str>()); 11 println!("size_of Box = {}", std::mem::size_of::>()); 12 } ``` **Output (verified, 64-bit platform):** ```text 1 size_of i32 = 4 2 size_of f64 = 8 3 size_of bool = 1 4 size_of char = 4 5 size_of (i32,i32) = 8 6 size_of [i32; 3] = 12 7 size_of String = 24 8 size_of Vec = 24 9 size_of &str = 16 10 size_of Box = 8 ``` A `String` is 24 bytes on the stack regardless of whether it holds `"hi"` or a megabyte of text; those 24 bytes are the pointer (8) + length (8) + capacity (8). A `Vec` is the same shape. A `&str` is 16 bytes: a pointer plus a length (no capacity, because you cannot grow through a shared reference). A `Box` is just 8 bytes: a single pointer to a heap-allocated `i32`. > **Note:** A `char` is 4 bytes, not 1, because Rust’s `char` is a full Unicode scalar value (any code point from `'a'` to `'\u{1F600}'`), unlike C’s 1-byte `char`. Text encoding is covered more in [Section 02 — Basic Types](/02-basics/01-types/). ### Putting a value on the heap on purpose: `Box` [Section titled “Putting a value on the heap on purpose: Box\”](#putting-a-value-on-the-heap-on-purpose-boxt) Most of the time the standard library decides heap usage for you (`String`, `Vec`). When you want to *explicitly* move a single value to the heap, you use `Box`. The box itself is one pointer on the stack; the value it owns lives on the heap. ```rust 1 fn main() { 2 let boxed: Box = Box::new(42); 3 println!("boxed value = {}", *boxed); // deref to read the heap value 4 5 // The handle is tiny even when the boxed data is large. 6 let on_stack: [u64; 4] = [1, 2, 3, 4]; 7 let on_heap: Box<[u64; 4]> = Box::new([1, 2, 3, 4]); 8 println!("[u64; 4] on the stack = {} bytes", std::mem::size_of_val(&on_stack)); 9 println!("Box<[u64; 4]> handle = {} bytes", std::mem::size_of_val(&on_heap)); 10 println!("on_heap[0] = {}", on_heap[0]); 11 } ``` **Output (verified):** ```text 1 boxed value = 42 2 [u64; 4] on the stack = 32 bytes 3 Box<[u64; 4]> handle = 8 bytes ``` `Box` is the simplest of Rust’s smart pointers; the others (`Rc`, `Arc`, and friends) get a light intro in [Reference Counting with `Rc` and `Arc`](/05-ownership/07-reference-counting/) and full coverage in [Section 10 — Smart Pointers](/10-smart-pointers/). ### Why Rust cares (and JavaScript doesn’t) [Section titled “Why Rust cares (and JavaScript doesn’t)”](#why-rust-cares-and-javascript-doesnt) In JavaScript, the engine puts almost everything on a managed heap and a garbage collector decides when to free it. You trade control for convenience. In Rust there is no GC, so the compiler needs a deterministic rule for *when* heap memory is freed. That rule is **ownership**: each heap value has exactly one owner, and when the owner goes out of scope the value is freed. Because copying a 24-byte handle is cheap but duplicating its heap buffer is not, Rust *moves* the handle by default instead of deep-copying it, which is exactly what you saw in the `let b = a` examples above and what the next files explore in depth. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript/JavaScript | Rust | | ------------------------- | ------------------------------------ | ----------------------------------------------------- | | Who decides stack vs heap | The engine; hidden from you | Determined by type, visible and meaningful | | Freeing memory | Garbage collector, non-deterministic | At end of scope, deterministic (no GC) | | Primitive assignment | Copy by value | Copy by value (for `Copy` types) | | Object/array assignment | Copies the **reference** (aliasing) | **Moves** ownership of the handle | | `number` | Always IEEE-754 f64 | Pick a size: `i8`..`i128`, `u8`..`u128`, `f32`, `f64` | | A growable string | A heap object, GC-managed | `String`: stack handle + heap buffer | | Cost model | Opaque; GC pauses possible | Explicit; you can predict allocations | ### ”Reference” means two different things [Section titled “”Reference” means two different things”](#reference-means-two-different-things) This is the trap that confuses every JavaScript developer. In JavaScript, “reference” describes how *assignment* works: `b = a` makes two names for one heap object, and mutating through one is visible through the other. In Rust, that automatic-aliasing-on-assignment does **not** happen: `let b = a` *moves* ownership (for heap types) or *copies* (for stack `Copy` types). Rust does have references, written `&a`, but they are an explicit, separate concept called **borrowing**, covered in [Borrowing and References](/05-ownership/02-borrowing/). Do not assume Rust assignment behaves like JavaScript object assignment; it does not. ### No “boxing” surprises [Section titled “No “boxing” surprises”](#no-boxing-surprises) In JavaScript even a humble `number` can end up boxed and the engine may move data around behind your back. In Rust the type *is* the layout. An `i32` is four bytes wherever it appears; a `[u8; 1024]` is exactly 1024 contiguous bytes on the stack. There is no hidden indirection unless you write it (`Box`, `Vec`, `String`, a reference, etc.). *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Expecting JavaScript “shared reference” semantics from assignment [Section titled “Pitfall 1: Expecting JavaScript “shared reference” semantics from assignment”](#pitfall-1-expecting-javascript-shared-reference-semantics-from-assignment) Coming from JavaScript, you might expect `let b = a` to make `a` and `b` aliases. For a heap type, Rust *moves* instead, and using the old name afterward is a compile error. ```rust 1 fn main() { 2 let s1 = String::from("hello"); 3 let s2 = s1; // ownership of the heap buffer moves to s2 4 println!("{s1}"); // does not compile (error[E0382]: borrow of moved value: `s1`) 5 println!("{s2}"); 6 } ``` **Real compiler error:** ```text 1 error[E0382]: borrow of moved value: `s1` 2 --> src/main.rs:4:16 3 | 4 2 | let s1 = String::from("hello"); 5 | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 6 3 | let s2 = s1; // ownership of the heap buffer moves to s2 7 | -- value moved here 8 4 | println!("{s1}"); // does not compile (error[E0382]: borrow of moved value: `s1`) 9 | ^^ value borrowed here after move 10 | 11 help: consider cloning the value if the performance cost is acceptable 12 | 13 3 | let s2 = s1.clone(); // ownership of the heap buffer moves to s2 14 | ++++++++ ``` The fix depends on intent: borrow with `&s1` if you only need to read it, or `.clone()` if you genuinely want a second independent heap buffer. Why the move happens (and why `i32` would have been fine here) is the subject of [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/). ### Pitfall 2: Trying to put a recursive type entirely on the stack [Section titled “Pitfall 2: Trying to put a recursive type entirely on the stack”](#pitfall-2-trying-to-put-a-recursive-type-entirely-on-the-stack) A type that contains itself has no finite size, so the compiler cannot lay it out on the stack. This is a real (and famous) error: ```rust 1 // does not compile (error[E0072]: recursive type `List` has infinite size) 2 enum List { 3 Cons(i32, List), 4 Nil, 5 } 6 7 fn main() { 8 let _list = List::Cons(1, List::Cons(2, List::Nil)); 9 } ``` **Real compiler error (key part):** ```text 1 error[E0072]: recursive type `List` has infinite size 2 --> src/main.rs:2:1 3 | 4 2 | enum List { 5 | ^^^^^^^^^ 6 3 | Cons(i32, List), 7 | ---- recursive without indirection 8 | 9 help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle 10 | 11 3 | Cons(i32, Box), 12 | ++++ + ``` The fix is exactly what the compiler suggests: put the recursive part behind a pointer (`Box`) so each node is a fixed size. We use this in Exercise 3. ### Pitfall 3: Overflowing the stack with deep recursion or huge arrays [Section titled “Pitfall 3: Overflowing the stack with deep recursion or huge arrays”](#pitfall-3-overflowing-the-stack-with-deep-recursion-or-huge-arrays) The stack is small. Two ways to blow past it: * **Unbounded recursion**: every call pushes a frame and they are never popped. * **A giant fixed-size array as a local** — `let big = [0u8; 50_000_000];` tries to put 50 MB on the stack. Here is real unbounded recursion: ```rust 1 // Each call pushes another stack frame; this never returns. 2 fn count_up(n: u64) -> u64 { 3 println!("{n}"); 4 count_up(n + 1) // no base case: recurses forever 5 } 6 7 fn main() { 8 count_up(0); 9 } ``` This compiles (with an `unconditional_recursion` warning: *function cannot return without recursing*), but at runtime it aborts once the stack is exhausted: ```text 1 thread 'main' has overflowed its stack 2 fatal runtime error: stack overflow, aborting ``` (The process exits with code 134.) The fix for huge data is to put it on the heap — `Box`, `Vec`, etc. — where there is room to grow. The fix for recursion is a base case, or rewriting as a loop. ### Pitfall 4: Assuming `number`-style behavior from Rust integers [Section titled “Pitfall 4: Assuming number-style behavior from Rust integers”](#pitfall-4-assuming-number-style-behavior-from-rust-integers) In JavaScript `number` is always an f64, so very large integers silently **lose precision** (they do not wrap): ```typescript 1 console.log(9007199254740993); // 9007199254740992 — precision lost, not wrapped ``` In Rust you choose a concrete integer type, and overflow is checked in debug builds (it panics) rather than silently losing precision. This is not strictly a stack/heap issue, but it is the same theme: **Rust makes the representation explicit** instead of hiding it. Integer types and overflow are covered in [Section 02 — Basic Types](/02-basics/01-types/). *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### Prefer the stack; reach for the heap only when you need it [Section titled “Prefer the stack; reach for the heap only when you need it”](#prefer-the-stack-reach-for-the-heap-only-when-you-need-it) Stack values are faster to allocate, faster to access (better cache locality), and freed for free when the frame pops. Use plain values, tuples, and fixed-size arrays for small, fixed-size data. Reach for `String`, `Vec`, or `Box` when the data is growable, large, or its size is unknown at compile time. ### Borrow instead of moving or cloning when you only need to read [Section titled “Borrow instead of moving or cloning when you only need to read”](#borrow-instead-of-moving-or-cloning-when-you-only-need-to-read) If a function just reads a `String`, take `&str` (a borrow), not an owned `String`. You avoid both a move (which would strand the caller’s value) and a clone (which would duplicate the heap buffer). Borrowing is the subject of [Borrowing and References](/05-ownership/02-borrowing/), but the memory reason is here: a borrow is a cheap 16-byte pointer-plus-length, never a heap copy. ```rust 1 // reads without taking ownership or copying the heap buffer 2 fn char_count(text: &str) -> usize { 3 text.chars().count() 4 } 5 6 fn main() { 7 let name = String::from("Grace Hopper"); 8 println!("{}", char_count(&name)); // name is still usable afterward 9 println!("{name}"); 10 } ``` **Output (verified):** ```text 1 12 2 Grace Hopper ``` ### Don’t reach for `Box` reflexively [Section titled “Don’t reach for Box reflexively”](#dont-reach-for-box-reflexively) A JavaScript developer’s instinct is “everything is a reference, so box everything.” In Rust, most values should be plain stack values; the compiler and `Vec`/`String` handle heap allocation where it is actually needed. Use `Box` specifically for recursive types, very large values you want to move cheaply, or trait objects (`Box`, covered later). Boxing a small `i32` for no reason just adds an indirection. ### Let scope-based cleanup do its job [Section titled “Let scope-based cleanup do its job”](#let-scope-based-cleanup-do-its-job) You never call `free`. Design your code so values live exactly as long as they are needed and let them drop at the end of their scope. Deterministic, scope-based cleanup (RAII) is one of Rust’s quiet superpowers and is detailed in [The Drop Trait and RAII](/05-ownership/08-drop-trait/). *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A common back-end task: ingesting telemetry events. A single event mixes small fixed-size fields (IDs, timestamps, flags) with variable-length data (an endpoint string, a list of tags). This is exactly where the stack/heap split shows up in everyday code. ```rust 1 /// A telemetry event ingested from a client. Mixes stack-only fields 2 /// (fixed-size numbers, a bool) with heap-backed fields (String, Vec). 3 #[derive(Debug)] 4 struct TelemetryEvent { 5 user_id: u64, // 8 bytes, stored inline in the struct 6 timestamp_ms: u64, // 8 bytes, inline 7 is_error: bool, // 1 byte, inline 8 endpoint: String, // 24-byte handle inline; the text bytes live on the heap 9 tags: Vec, // 24-byte handle inline; the elements live on the heap 10 } 11 12 impl TelemetryEvent { 13 fn new(user_id: u64, timestamp_ms: u64, endpoint: &str) -> Self { 14 TelemetryEvent { 15 user_id, 16 timestamp_ms, 17 is_error: false, 18 endpoint: endpoint.to_string(), 19 tags: Vec::new(), 20 } 21 } 22 23 /// Render a one-line summary, reading each field. 24 fn summary(&self) -> String { 25 let status = if self.is_error { "ERROR" } else { "ok" }; 26 format!( 27 "user={} ts={} {} {} tags={:?}", 28 self.user_id, self.timestamp_ms, status, self.endpoint, self.tags 29 ) 30 } 31 } 32 33 fn main() { 34 let mut event = TelemetryEvent::new(42, 1_717_000_000_000, "/api/v1/checkout"); 35 event.is_error = true; 36 event.tags.push("payment".to_string()); 37 event.tags.push("timeout".to_string()); 38 39 // The struct value itself is a fixed-size block: the inline fields plus 40 // the String/Vec handles. The variable-length text lives on the heap. 41 println!( 42 "size of TelemetryEvent struct = {} bytes", 43 std::mem::size_of::() 44 ); 45 println!("{}", event.summary()); 46 } ``` **Output (verified):** ```text 1 size of TelemetryEvent struct = 72 bytes 2 user=42 ts=1717000000000 ERROR /api/v1/checkout tags=["payment", "timeout"] ``` The struct is a fixed **72 bytes** no matter how long the endpoint or how many tags it carries: 8 + 8 + 1 (rounded up for alignment) + 24 (the `String` handle) + 24 (the `Vec` handle). The actual endpoint text and tag strings live on the heap, reached through those handles. When `event` goes out of scope at the end of `main`, Rust frees the struct *and* every heap buffer it owns, automatically, with no garbage collector and no `free` call. > **Tip:** Struct field layout (and how `#[derive(Debug)]` lets you print a value with `{:?}`) is covered in [Section 06 — Data Structures](/06-data-structures/). The exact 72 is also affected by alignment and field ordering, which the compiler may optimize. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official documentation [Section titled “Official documentation”](#official-documentation) * [The Rust Book — What Is Ownership? (the stack and the heap)](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#the-stack-and-the-heap) * [The Rust Book — Using `Box` to Point to Data on the Heap](https://doc.rust-lang.org/book/ch15-01-box.html) * [`std::boxed::Box` API documentation](https://doc.rust-lang.org/std/boxed/struct.Box.html) * [`std::mem::size_of` API documentation](https://doc.rust-lang.org/std/mem/fn.size_of.html) ### Related sections in this guide [Section titled “Related sections in this guide”](#related-sections-in-this-guide) * [Section 05 — Ownership (overview)](/05-ownership/): how this fits into the whole ownership story. * [The Three Ownership Rules](/05-ownership/01-ownership-rules/): the three rules that govern this memory. * [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/): why heap handles move and stack values copy. * [Borrowing and References](/05-ownership/02-borrowing/): references (`&T`), the cheap way to read without owning. * [The Drop Trait and RAII](/05-ownership/08-drop-trait/): deterministic, scope-based cleanup instead of a garbage collector. * [Reference Counting with `Rc` and `Arc`](/05-ownership/07-reference-counting/): `Rc`/`Arc` for shared heap ownership. * [Section 02 — Basic Types](/02-basics/01-types/) — sizes of integers, floats, `char`, and arrays. * [Section 06 — Data Structures](/06-data-structures/) — how struct fields are laid out. * [Section 01 — Why Rust?](/01-getting-started/00-why-rust/) — the case for no garbage collector. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Classify each value’s home [Section titled “Exercise 1: Classify each value’s home”](#exercise-1-classify-each-values-home) **Difficulty:** Easy **Objective:** Build the mental model of which values live on the stack and which need the heap. **Instructions:** 1. Create these four bindings: a `u32`, a `String`, a tuple `(f64, f64)`, and a `Vec`. 2. For each, decide whether it lives entirely on the stack or has a stack handle plus heap data. 3. Use `std::mem::size_of_val(&value)` to print the size of each *stack* representation, and add a comment stating where the rest (if any) lives. Solution ```rust 1 fn main() { 2 let count: u32 = 100; // stack only 3 let label = String::from("temp"); // handle on stack, bytes on heap 4 let coords = (1.0_f64, 2.0_f64); // stack only (fixed-size tuple) 5 let readings = vec![9.8, 10.1]; // handle on stack, elements on heap 6 7 println!("count -> stack, {} bytes", std::mem::size_of_val(&count)); 8 println!( 9 "label -> handle on stack ({} bytes) + bytes on heap", 10 std::mem::size_of_val(&label) 11 ); 12 println!("coords -> stack, {} bytes", std::mem::size_of_val(&coords)); 13 println!( 14 "readings -> handle on stack ({} bytes) + elements on heap", 15 std::mem::size_of_val(&readings) 16 ); 17 } ``` **Verified output:** ```text 1 count -> stack, 4 bytes 2 label -> handle on stack (24 bytes) + bytes on heap 3 coords -> stack, 16 bytes 4 readings -> handle on stack (24 bytes) + elements on heap ``` The `u32` and the `(f64, f64)` tuple are pure stack values. The `String` and `Vec` are 24-byte handles on the stack (pointer + length + capacity) that point to buffers on the heap. ### Exercise 2: Return heap-owned data without dangling [Section titled “Exercise 2: Return heap-owned data without dangling”](#exercise-2-return-heap-owned-data-without-dangling) **Difficulty:** Medium **Objective:** See that returning an owned `String` *moves* its heap buffer out to the caller, so nothing dangles: the opposite of the classic “returning a pointer to a local” bug in C. **Instructions:** 1. Write `build_greeting(name: &str) -> String` that creates a new `String`, appends `name` and a `'!'`, and returns it. 2. Use `String::from`, `push_str`, and `push`. 3. Return the `String` as a tail expression (no `return` keyword needed) and print the result from `main`. > **Tip:** Returning the value *moves* ownership of the heap buffer to the caller. Rust statically guarantees the buffer is still alive — you cannot accidentally return a reference to freed memory. Solution ```rust 1 fn build_greeting(name: &str) -> String { 2 let mut greeting = String::from("Hello, "); 3 greeting.push_str(name); 4 greeting.push('!'); 5 greeting // ownership of the heap buffer moves out to the caller 6 } 7 8 fn main() { 9 let msg = build_greeting("Ada"); 10 println!("{msg}"); 11 } ``` **Verified output:** ```text 1 Hello, Ada! ``` The `greeting` buffer was allocated inside the function, but returning it *moves* ownership to `main`. There is no copy of the bytes and no dangling pointer; the heap allocation simply has a new owner. ### Exercise 3: Use `Box` to give a recursive type a finite size [Section titled “Exercise 3: Use Box to give a recursive type a finite size”](#exercise-3-use-box-to-give-a-recursive-type-a-finite-size) **Difficulty:** Medium **Objective:** Fix the “infinite size” error by adding heap indirection, and observe the resulting node size. **Instructions:** 1. Start from this broken definition (it does not compile; ``error[E0072]: recursive type `List` has infinite size``): ```rust 1 enum List { 2 Cons(i32, List), // recursive without indirection 3 Nil, 4 } ``` 2. Wrap the recursive field in a `Box` so each node has a fixed size. 3. Build the list `1 -> 2 -> 3 -> Nil`, then print `size_of::()` and the list itself (derive `Debug`). Solution ```rust 1 #[derive(Debug)] 2 enum List { 3 Cons(i32, Box), // the recursive part now lives behind a pointer 4 Nil, 5 } 6 7 use List::{Cons, Nil}; 8 9 fn main() { 10 let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); 11 println!("size of List node = {} bytes", std::mem::size_of::()); 12 println!("{list:?}"); 13 } ``` **Verified output:** ```text 1 size of List node = 16 bytes 2 Cons(1, Cons(2, Cons(3, Nil))) ``` `Box` is a single pointer of known size, so each `List` node is a fixed 16 bytes (a discriminant for the enum variant plus the `i32` and the pointer, with alignment padding). The nodes themselves live on the heap, chained by their boxes. `Box` is the gateway to the other smart pointers in [Reference Counting with `Rc` and `Arc`](/05-ownership/07-reference-counting/) and [Section 10 — Smart Pointers](/10-smart-pointers/). > **Note:** This compiles and runs; the compiler may emit a harmless `dead_code` warning for the enum fields because it analyzes derived `Debug` impls conservatively. The program output above is unaffected. *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * The **stack** holds fixed-size, short-lived values and is freed automatically as functions return; the **heap** holds growable or unknown-size data. * `String`, `Vec`, and `Box` keep a small fixed-size **handle on the stack** that points to a buffer on the **heap**. * TypeScript/JavaScript hides this behind a garbage collector and aliases objects on assignment; Rust makes it explicit and **moves** heap handles instead. * There is **no garbage collector** in Rust; deterministic, scope-based cleanup is possible precisely because every heap value has a single owner. * Knowing what lives where is the foundation for understanding moves, borrows, and lifetimes in the rest of this section. **Mental model:** * Small + fixed size → stack, copied cheaply. * Growable / large / unknown size → heap, reached through a handle, moved by default. * No `malloc`, no `free`, no GC: the compiler frees heap memory when the owner’s scope ends. # The Three Rules of Rust Ownership > One owner per value, ownership moves on assignment or a function call, and values drop at scope end. How Rust frees memory deterministically without JavaScript's GC. Ownership is the mechanism Rust uses to manage memory **without a garbage collector**. Instead of a runtime that periodically scans for unreachable objects (the JavaScript model), Rust determines when each value is freed from the structure of your code — almost always statically, at scope exit. (In rare cases where a value is only *conditionally* moved, the compiler inserts a hidden runtime “drop flag”, but the rule of thumb holds: cleanup happens at a predictable point, not whenever a GC gets around to it.) The whole system rests on three short rules, and this page is about those rules and nothing more. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) In Rust, every value has exactly one **owner**; ownership **moves** when you assign the value or pass it to a function; and when the owner goes out of scope, the value is **dropped** (its memory freed) automatically. There is no `new`/`delete`, no `free()`, and no garbage collector. The compiler tracks ownership and inserts cleanup for you. For a TypeScript/JavaScript developer, the surprising part is not the cleanup (the GC already hides that) but that *handing a value to someone else can make your own variable unusable*. > **Note:** This page covers the rules themselves and the **move** that happens on assignment and function calls. Borrowing (`&`), which lets you lend access *without* giving up ownership, is the subject of [Borrowing and References](/05-ownership/02-borrowing/). The stack/heap distinction that explains *why* moves matter lives in [Stack and Heap](/05-ownership/00-stack-heap/). *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) In JavaScript and TypeScript you never think about who “owns” a value. You create objects, pass them around, and the **garbage collector** (GC) frees them whenever it decides nothing can reach them anymore. ```typescript 1 // TypeScript/JavaScript: values are shared freely, GC cleans up "eventually" 2 function archiveOrder(orderId: string): void { 3 console.log(`Archiving ${orderId}`); 4 } 5 6 let orderId = "ORD-2026-0531"; 7 let archived = orderId; // both names now refer to the same string value 8 9 console.log(orderId); // "ORD-2026-0531" — still usable 10 console.log(archived); // "ORD-2026-0531" 11 12 archiveOrder(orderId); // pass it to a function... 13 console.log(orderId); // still usable afterward — nothing was "consumed" 14 15 // When `orderId` and `archived` become unreachable, the GC frees the memory 16 // at some unspecified later time. You never decide when. ``` **Key points:** * Assigning `archived = orderId` makes a second handle to the same data (for objects/arrays it is a shared reference; for primitives like strings it is a copy, but either way the original stays valid). * Passing a value to a function does **not** invalidate the caller’s variable. * Cleanup timing is **non-deterministic**: the GC runs when it wants. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) Rust enforces a stricter discipline. The same `let archived = order_id;` **moves** ownership, and the original binding becomes unusable. ```rust 1 fn print_receipt(id: String) { 2 println!("Receipt: {id}"); 3 } // `id` dropped here, its heap buffer freed 4 5 fn main() { 6 // ---- Rule 1 & 2: single owner, move on assignment ---- 7 let order_id = String::from("ORD-2026-0531"); 8 let archived = order_id; // value MOVES; `order_id` is no longer valid 9 println!("Archived order: {archived}"); 10 11 // ---- Rule 3: drop at end of scope ---- 12 { 13 let temp = String::from("temporary buffer"); 14 println!("Inside block: {temp}"); 15 } // `temp` goes out of scope here -> its heap buffer is freed 16 17 // ---- move into a function ---- 18 let receipt = String::from("RCPT-9001"); 19 print_receipt(receipt); // ownership moves into print_receipt 20 // println!("{receipt}"); // would not compile: value moved 21 22 // ---- Copy types are duplicated, not moved ---- 23 let total = 42_u32; 24 let copy = total; // u32 is Copy: bitwise copy, both stay valid 25 println!("total={total}, copy={copy}"); 26 } ``` **Output:** ```plaintext 1 Archived order: ORD-2026-0531 2 Inside block: temporary buffer 3 Receipt: RCPT-9001 4 total=42, copy=42 ``` **Key points:** * `let archived = order_id;` **moves** ownership; `order_id` can no longer be used. * Passing `receipt` to `print_receipt` moves it *into* the function. * `temp` is freed precisely at the closing `}` of its block, deterministically. * Small stack-only types like `u32` are `Copy`, so `let copy = total;` duplicates instead of moving. (`Copy` vs `Clone` is the topic of [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/).) *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) The Rust Book states the three rules verbatim: 1. **Each value in Rust has an owner.** 2. **There can only be one owner at a time.** 3. **When the owner goes out of scope, the value is dropped.** Let’s take them one at a time, line by line, and contrast each with what TypeScript/JavaScript does. ### Rule 1 — Each value has an owner [Section titled “Rule 1 — Each value has an owner”](#rule-1--each-value-has-an-owner) A **value** is a piece of data (a `String`, a `Vec`, a struct, a number). A **variable** (more precisely, a *binding*) is the name that owns it. ```rust 1 let order_id = String::from("ORD-2026-0531"); ``` Here `order_id` is the owner of the heap-allocated string buffer. Ownership is a property the compiler tracks; it is not stored at runtime and costs nothing. There is no header, no reference count, no GC bookkeeping attached to the value. The compiler simply *knows* who the owner is at every point in the program. In JavaScript there is no analogous concept. A value can be reachable through any number of variables and object fields simultaneously, and “who owns it” is a meaningless question because the GC owns everything. ### Rule 2 — One owner at a time: the move [Section titled “Rule 2 — One owner at a time: the move”](#rule-2--one-owner-at-a-time-the-move) This is the rule that bites TypeScript/JavaScript developers. Assigning a value to a new binding **transfers** ownership rather than copying or aliasing it: ```rust 1 let order_id = String::from("ORD-2026-0531"); 2 let archived = order_id; // ownership MOVED into `archived` ``` A `String` is a three-word value on the stack — a pointer to a heap buffer, a length, and a capacity. The move copies those three words into `archived` and then **invalidates** `order_id`. It does *not* copy the heap buffer, and it does *not* create a second valid pointer to the same buffer. If both `order_id` and `archived` were allowed to be used, both would try to free the same buffer when they went out of scope: a classic *double-free* bug. The move rule makes that impossible. > **Tip:** Read `let archived = order_id;` as “`archived` takes over ownership from `order_id`,” not “`archived` is a copy of `order_id`.” After this line, `order_id` is gone — not empty, not null, just *not a thing you can name anymore*. Passing a value to a function is also a move, because the parameter is a new binding: ```rust 1 let receipt = String::from("RCPT-9001"); 2 print_receipt(receipt); // `receipt` moved into the parameter `id` ``` After this call, `receipt` is no longer usable in `main`. Its ownership now belongs to `print_receipt`, and when that function ends, the value is dropped there. **Why doesn’t this happen with numbers?** Types whose data lives entirely on the stack and is cheap to duplicate — integers, floats, `bool`, `char`, and tuples of them — implement the `Copy` trait. For those, `let copy = total;` makes an independent bitwise copy and leaves the original valid. There is no heap buffer to double-free, so copying is safe. `String`, `Vec`, and most structs are **not** `Copy`, so they move. See [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/) for the full story. ### Rule 3 — Dropped at end of scope [Section titled “Rule 3 — Dropped at end of scope”](#rule-3--dropped-at-end-of-scope) When a binding goes out of scope, Rust automatically runs cleanup for the value it owns. For a `String` or `Vec`, that means freeing the heap buffer; for a file handle, closing the file; for a lock guard, releasing the lock. This is **deterministic** and tied to lexical scope: ```rust 1 { 2 let temp = String::from("temporary buffer"); 3 println!("Inside block: {temp}"); 4 } // `temp` dropped HERE — exactly at this `}`, every time ``` You can *see* this happen by implementing the `Drop` trait so the value prints when it is cleaned up: ```rust 1 struct Connection { 2 label: &'static str, 3 } 4 5 impl Drop for Connection { 6 fn drop(&mut self) { 7 println!("Closing connection: {}", self.label); 8 } 9 } 10 11 fn main() { 12 println!("Start of main"); 13 let first = Connection { label: "first" }; 14 let second = Connection { label: "second" }; 15 println!("Opened {} and {}", first.label, second.label); 16 17 { 18 let inner = Connection { label: "inner-block" }; 19 println!("Inside inner block with {}", inner.label); 20 } // `inner` dropped here 21 22 println!("Back in main"); 23 } // `second` then `first` dropped here (reverse declaration order) ``` **Output:** ```plaintext 1 Start of main 2 Opened first and second 3 Inside inner block with inner-block 4 Closing connection: inner-block 5 Back in main 6 Closing connection: second 7 Closing connection: first ``` Two things to notice. First, `inner` is dropped at the inner `}`, *before* `Back in main` prints; cleanup is scope-bound, not deferred to the end of the program. Second, within a scope, values are dropped in **reverse order of declaration** (`second` before `first`), like unwinding a stack. The mechanics of `Drop` and this ordering are explored in depth in [The Drop Trait and RAII](/05-ownership/08-drop-trait/); here the point is simply that **Rule 3 frees memory automatically, at a moment you can predict by reading the code**. This is the polar opposite of JavaScript. The JS engine’s GC frees objects at an unspecified time after they become unreachable, and `FinalizationRegistry` callbacks are explicitly documented as offering *no timing guarantees* and may never run at all. Rust gives you precise, source-visible cleanup with zero runtime tracking. ### What “move” actually invalidates [Section titled “What “move” actually invalidates”](#what-move-actually-invalidates) A natural question: if you give a value away, how do you keep using your data? Three answers, in increasing order of preference: 1. **Get it back.** A function can take ownership and return it (possibly alongside other results): ```rust 1 fn measure(s: String) -> (String, usize) { 2 let len = s.len(); 3 (s, len) // hand the String back to the caller 4 } 5 6 fn main() { 7 let s = String::from("ownership"); 8 let (s, len) = measure(s); // re-bind `s` from the returned tuple 9 println!("'{s}' has length {len}"); 10 } ``` Output: `'ownership' has length 9` 2. **Clone it.** Make an explicit, independent deep copy so each binding owns its own data: ```rust 1 let original = String::from("config.toml"); 2 let backup = original.clone(); // explicit, potentially expensive deep copy 3 println!("original = {original}"); 4 println!("backup = {backup}"); ``` 3. **Borrow it (best).** Lend a reference so no move happens at all; the caller keeps ownership: ```rust 1 fn char_count(s: &str) -> usize { 2 s.chars().count() 3 } 4 5 let path = String::from("/etc/hosts"); 6 let length = char_count(&path); // pass a reference; no move 7 println!("'{path}' has {length} chars"); // `path` still owned here ``` Borrowing is usually the right tool and is the subject of the [next page](/05-ownership/02-borrowing/). Returning ownership and cloning are covered above so you understand the alternatives the compiler keeps suggesting. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript / JavaScript | Rust | | -------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | Who frees memory | Garbage collector, at an unspecified time | The compiler, deterministically at scope exit | | Assignment (`b = a`) | New handle/reference (or value copy for primitives); `a` stays valid | **Move**: ownership transfers; `a` becomes unusable (unless the type is `Copy`) | | Passing to a function | Caller’s variable stays valid | Argument is **moved in**; caller’s variable becomes unusable (unless `Copy` or you pass a reference) | | Number of live references to one value | Unlimited | Exactly one *owner* (plus borrows, governed separately) | | Cleanup timing | Non-deterministic; `FinalizationRegistry` gives no guarantees | Deterministic, at the closing `}` of the owner’s scope | | Runtime cost | GC pauses, allocation headers, tracing | Zero — ownership is a compile-time-only concept | | Double-free / use-after-free | Not possible (GC), but memory leaks via retained references are | Prevented at compile time by the move rule | **The core mental shift:** in JavaScript, *naming a value more times keeps it alive longer*. In Rust, naming a value a second time (by assignment or passing it) usually **consumes the first name**. You move the responsibility of freeing the value, and only one binding can hold that responsibility at a time. > **Note:** “Move” in Rust is a *compile-time bookkeeping* operation. At runtime, moving a `String` is just copying three machine words (pointer, length, capacity); the heap buffer is untouched. The compiler then refuses to let you use the old binding. So a move is cheap *and* safe — you are not paying for a deep copy, and you cannot accidentally alias the buffer. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Using a value after it was moved by assignment [Section titled “Pitfall 1: Using a value after it was moved by assignment”](#pitfall-1-using-a-value-after-it-was-moved-by-assignment) This is the first error nearly every newcomer hits. ```rust 1 fn main() { 2 let order_id = String::from("ORD-2026-0531"); 3 let archived = order_id; 4 println!("{order_id}"); // does not compile (error[E0382]): use after move 5 println!("{archived}"); 6 } ``` The compiler is precise and even suggests the fix: ```plaintext 1 error[E0382]: borrow of moved value: `order_id` 2 --> src/main.rs:4:16 3 | 4 2 | let order_id = String::from("ORD-2026-0531"); 5 | -------- move occurs because `order_id` has type `String`, which does not implement the `Copy` trait 6 3 | let archived = order_id; 7 | -------- value moved here 8 4 | println!("{order_id}"); // does not compile (error[E0382]): use after move 9 | ^^^^^^^^ value borrowed here after move 10 | 11 = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) 12 help: consider cloning the value if the performance cost is acceptable 13 | 14 3 | let archived = order_id.clone(); 15 | ++++++++ ``` **Fix:** clone if you genuinely need two independent owners (`order_id.clone()`), or borrow if you only need to read it. The phrase “does not implement the `Copy` trait” is the compiler telling you *why* this was a move rather than a copy. ### Pitfall 2: Using a value after passing it to a function [Section titled “Pitfall 2: Using a value after passing it to a function”](#pitfall-2-using-a-value-after-passing-it-to-a-function) Coming from JavaScript, you expect the caller’s variable to survive a function call. It does not, if the value was moved in. ```rust 1 fn print_receipt(id: String) { 2 println!("Receipt: {id}"); 3 } 4 5 fn main() { 6 let receipt = String::from("RCPT-9001"); 7 print_receipt(receipt); 8 println!("Still have: {receipt}"); // does not compile (error[E0382]): moved into function 9 } ``` Real compiler output (abbreviated; note the targeted hint about the parameter type): ```plaintext 1 error[E0382]: borrow of moved value: `receipt` 2 --> src/main.rs:8:28 3 | 4 6 | let receipt = String::from("RCPT-9001"); 5 | ------- move occurs because `receipt` has type `String`, which does not implement the `Copy` trait 6 7 | print_receipt(receipt); 7 | ------- value moved here 8 8 | println!("Still have: {receipt}"); // does not compile (error[E0382]): moved into function 9 | ^^^^^^^ value borrowed here after move 10 | 11 note: consider changing this parameter type in function `print_receipt` to borrow instead if owning the value isn't necessary 12 --> src/main.rs:1:22 13 | 14 1 | fn print_receipt(id: String) { 15 | ------------- ^^^^^^ this parameter takes ownership of the value 16 ... (rustc continues with a "help: consider cloning the value" suggestion) ``` **Fix:** if the function only needs to *read* the value, take `&str`/`&String` instead of `String`, so the call borrows rather than moves. (That is exactly what [Borrowing and References](/05-ownership/02-borrowing/) teaches.) ### Pitfall 3: Moving a value inside a loop [Section titled “Pitfall 3: Moving a value inside a loop”](#pitfall-3-moving-a-value-inside-a-loop) A move that is fine once becomes an error when it repeats: ```rust 1 fn print_banner(text: String) { 2 println!("{text}"); 3 } 4 5 fn main() { 6 let banner = String::from("=== Report ==="); 7 for _ in 0..3 { 8 print_banner(banner); // does not compile (error[E0382]): moved in previous iteration 9 } 10 } ``` The compiler specifically calls out the loop (output abbreviated): ```plaintext 1 error[E0382]: use of moved value: `banner` 2 --> src/main.rs:8:22 3 | 4 6 | let banner = String::from("=== Report ==="); 5 | ------ move occurs because `banner` has type `String`, which does not implement the `Copy` trait 6 7 | for _ in 0..3 { 7 | ------------- inside of this loop 8 8 | print_banner(banner); // does not compile (error[E0382]): moved in previous iteration 9 | ^^^^^^ value moved here, in previous iteration of loop 10 ... (rustc continues with "help: consider moving the expression out of the loop" and "help: consider cloning the value" suggestions) ``` **Fix:** pass a borrow (`print_banner(&banner)` with the parameter typed `&str`), or `.clone()` inside the loop if each iteration truly needs its own owned copy. ### Pitfall 4: Partial moves out of a struct [Section titled “Pitfall 4: Partial moves out of a struct”](#pitfall-4-partial-moves-out-of-a-struct) Moving one field out of a struct leaves the struct only *partially* valid: you can still read the fields that were not moved, but you can no longer use the struct as a whole. ```rust 1 #[derive(Debug)] 2 struct User { 3 name: String, 4 email: String, 5 } 6 7 fn main() { 8 let user = User { 9 name: String::from("Grace"), 10 email: String::from("grace@example.com"), 11 }; 12 let name = user.name; // moves the `name` field out 13 println!("{}", user.email); // this field is still readable 14 println!("{:?}", user); // does not compile (error[E0382]): partial move 15 println!("{name}"); 16 } ``` Real output: ```plaintext 1 error[E0382]: borrow of partially moved value: `user` 2 --> src/main.rs:14:22 3 | 4 12 | let name = user.name; // moves the `name` field out 5 | --------- value partially moved here 6 13 | println!("{}", user.email); // this field is still readable 7 14 | println!("{:?}", user); // does not compile (error[E0382]): partial move 8 | ^^^^ value borrowed here after partial move 9 | 10 = note: partial move occurs because `user.name` has type `String`, which does not implement the `Copy` trait ``` **Fix:** clone the field you want to take (`let name = user.name.clone();`), or borrow it (`let name = &user.name;`), so the struct stays whole. *** ## Best Practices [Section titled “Best Practices”](#best-practices) * **Borrow by default; move only when you mean to give the value away.** If a function just reads or inspects data, take `&T` (or `&str`/`&[T]`). Reserve owned parameters (`String`, `Vec`) for functions that store, consume, or transform the value into something they return. This single habit eliminates most `E0382` errors. * **Reach for `.clone()` deliberately, not reflexively.** The compiler’s “consider cloning” hint is a convenient escape hatch, but a clone is a real allocation and copy. When you find yourself sprinkling `.clone()` to silence the borrow checker, pause and ask whether a borrow would do; that is usually the idiomatic answer. * **Let scope drive cleanup.** Don’t look for a `free`, `dispose`, or `close` to call manually. Structure your code so a value’s scope matches its lifetime, and Rule 3 frees it at the right moment. If you need a value gone *early*, that is what `std::mem::drop(value)` is for (covered in [The Drop Trait and RAII](/05-ownership/08-drop-trait/)). * **Return ownership instead of mutating shared state.** Where JavaScript code might push into a shared array passed by reference, idiomatic Rust often takes ownership of a value, transforms it, and returns the new owner: a clear, linear flow of who owns what. * **Name the move when reading code.** When you see `let b = a;` or `f(a)`, train yourself to ask “is `a` `Copy`? If not, `a` is gone after this line.” This makes ownership errors obvious before the compiler points them out. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A common production scenario: an upload pipeline that receives raw bytes from a client, validates and parses them, and produces a stored document. Ownership makes the *stages* explicit: the raw bytes have exactly one owner at each step, and once they are parsed, the original buffer is consumed and cannot be accidentally reused. ```rust 1 /// A raw upload received from a client. 2 struct Upload { 3 filename: String, 4 bytes: Vec, 5 } 6 7 /// A validated, parsed document ready to store. 8 struct Document { 9 filename: String, 10 line_count: usize, 11 } 12 13 /// Consumes the `Upload` (takes ownership) and hands back a `Document`. 14 /// Because `upload` is moved in, the caller can no longer use it afterward — 15 /// the type system guarantees the raw bytes have exactly one owner per stage. 16 fn parse(upload: Upload) -> Result { 17 let text = String::from_utf8(upload.bytes) 18 .map_err(|_| format!("{} is not valid UTF-8", upload.filename))?; 19 let line_count = text.lines().count(); 20 Ok(Document { 21 filename: upload.filename, // move the String out of `upload` 22 line_count, 23 }) 24 } // `text` is dropped here; any unused bytes are freed automatically 25 26 fn main() { 27 let upload = Upload { 28 filename: String::from("notes.txt"), 29 bytes: b"first line\nsecond line\nthird line".to_vec(), 30 }; 31 32 // Ownership of `upload` moves into `parse`; we get a `Document` back. 33 match parse(upload) { 34 Ok(doc) => println!("Stored {} ({} lines)", doc.filename, doc.line_count), 35 Err(e) => eprintln!("Rejected upload: {e}"), 36 } 37 // `upload` is gone here — referencing it would not compile. 38 } ``` **Output:** ```plaintext 1 Stored notes.txt (3 lines) ``` **Why this is idiomatic:** * `parse` takes `upload` by value because it genuinely *consumes* it: once parsed, the raw bytes are no longer meaningful, and the type system enforces that nobody touches the stale upload. * `upload.filename` is moved (not cloned) into the new `Document`, reusing the existing allocation rather than copying the string. * When `parse` returns the `Document`, ownership flows to the `match` arm, and the `Document` is dropped at the end of that arm — no manual cleanup, no leak. * The compiler would reject any attempt to use `upload` after the `parse(upload)` call, catching a whole class of “I accidentally reused the consumed input” bugs that a JavaScript reviewer would have to spot by eye. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — What Is Ownership?](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html): the canonical statement of the three rules. * [Rust by Example — Ownership and Moves](https://doc.rust-lang.org/rust-by-example/scope/move.html) * [`std::mem::drop`](https://doc.rust-lang.org/std/mem/fn.drop.html): drop a value before the end of its scope. ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Stack vs Heap](/05-ownership/00-stack-heap/): *why* moving a `String` is cheap and *why* it cannot be aliased. * [Borrowing](/05-ownership/02-borrowing/) — lend access with `&` so you don’t have to move or clone. * [Mutable References](/05-ownership/03-mutable-references/): the one-mutable-XOR-many-shared rule. * [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/) — when assignment moves vs copies, and what `Copy`/`Clone` mean. * [Reference Counting (`Rc`/`Arc`)](/05-ownership/07-reference-counting/): when you genuinely need *shared* ownership. * [The Drop Trait and RAII](/05-ownership/08-drop-trait/) — the mechanics behind Rule 3, drop order, and early drop. * [Variables and Mutability](/02-basics/00-variables/): immutability by default, the foundation ownership builds on. * [Function Parameters](/03-functions/01-parameters/) — choosing between owned and borrowed parameters. * [Data Structures](/06-data-structures/) — how ownership flows through structs and enums. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1 [Section titled “Exercise 1”](#exercise-1) **Difficulty:** Easy **Objective:** Recognize a use-after-move and fix it with a clone. **Instructions:** The following code does not compile because `username` is moved into `greet` and then used again. Make it compile while still logging the username *and* greeting with it. Do not change the signature of `greet`. ```rust 1 fn greet(name: String) { 2 println!("Welcome, {name}!"); 3 } 4 5 fn main() { 6 let username = String::from("ada_lovelace"); 7 greet(username); 8 println!("Logging in: {username}"); // does not compile (error[E0382]) 9 } ``` Solution Clone before moving so each call has its own owned `String`, and log first for clarity: ```rust 1 fn greet(name: String) { 2 println!("Welcome, {name}!"); 3 } 4 5 fn main() { 6 let username = String::from("ada_lovelace"); 7 let display = username.clone(); // clone so both names own data 8 println!("Logging in: {username}"); 9 greet(display); // move the clone into greet 10 } ``` Output: ```plaintext 1 Logging in: ada_lovelace 2 Welcome, ada_lovelace! ``` > A borrow (`fn greet(name: &str)`) would be even better here, but the exercise fixed the signature, so cloning is the right tool. You’ll learn the borrow version in [Borrowing and References](/05-ownership/02-borrowing/). ### Exercise 2 [Section titled “Exercise 2”](#exercise-2) **Difficulty:** Medium **Objective:** Use the “give it away, get it back” pattern to keep working with a value after a function consumes it. **Instructions:** Write a function `append_event` that takes ownership of a `String` log and an event name (`&str`), appends `" "` to the log, and **returns the updated `String`** so the caller can keep using it. Call it twice in `main` to build up a log, then print it. Starter: ```rust 1 fn main() { 2 let mut log = String::from("event:"); 3 log = append_event(log, "login"); 4 log = append_event(log, "click"); 5 println!("{log}"); // should print: event: login click 6 } 7 8 fn append_event(/* ??? */) -> String { 9 // TODO 10 } ``` Solution ```rust 1 fn main() { 2 let mut log = String::from("event:"); 3 log = append_event(log, "login"); // give it away, get the updated one back 4 log = append_event(log, "click"); 5 println!("{log}"); 6 } 7 8 fn append_event(mut log: String, event: &str) -> String { 9 log.push(' '); 10 log.push_str(event); 11 log // return ownership to the caller 12 } ``` Output: ```plaintext 1 event: login click ``` Note the `mut log: String` parameter: the binding takes ownership *and* is allowed to mutate the value it owns, then hands the same allocation back. No clone, no extra allocation per call. ### Exercise 3 [Section titled “Exercise 3”](#exercise-3) **Difficulty:** Medium/Hard **Objective:** Demonstrate that Rule 3 (drop at end of scope) is deterministic and predict the cleanup order. **Instructions:** Define a `Timer` struct holding a `name: String` and implement `Drop` for it so that dropping prints `[] finished, cleaning up`. In `main`, create a `Timer` named `"handle_request"`, then in an *inner block* create a second `Timer` named `"db_query"` and print `Running query...` inside that block. After the block, print `Sending response...`. Before running it, write down the order in which the two messages will print, then confirm. Solution ```rust 1 struct Timer { 2 name: String, 3 } 4 5 impl Drop for Timer { 6 fn drop(&mut self) { 7 println!("[{}] finished, cleaning up", self.name); 8 } 9 } 10 11 fn main() { 12 println!("Program start"); 13 let _request = Timer { name: String::from("handle_request") }; 14 { 15 let _db = Timer { name: String::from("db_query") }; 16 println!("Running query..."); 17 } // _db dropped here — at the end of the inner block 18 println!("Sending response..."); 19 } // _request dropped here — at the end of main ``` Output: ```plaintext 1 Program start 2 Running query... 3 [db_query] finished, cleaning up 4 Sending response... 5 [handle_request] finished, cleaning up ``` The inner `db_query` timer is cleaned up the instant its block ends, *before* `Sending response...` prints: that is Rule 3 in action. The outer `handle_request` timer survives until the end of `main`. This deterministic, scope-bound cleanup is the foundation of RAII patterns like database transactions and lock guards, explored in [The Drop Trait and RAII](/05-ownership/08-drop-trait/). > **Note:** The bindings are named with a leading underscore (`_request`, `_db`) so the compiler doesn’t warn that they’re “unused”; they exist only for their drop side effect, which is a perfectly legitimate use. # Borrowing & References in Rust > A &T reference lends read-only access without moving a value, so the owner keeps it. Rust's borrow checker proves at compile time no reference ever dangles. In TypeScript and JavaScript, you pass objects and arrays around by reference all day without thinking about it, and you accept the consequence that any function might quietly mutate your data. Rust gives you references too, but with a twist: the compiler tracks every borrow and guarantees, at compile time, that a reference can never outlive the data it points to. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) A **reference** (written `&value`) lets a function read or use a value **without taking ownership of it**: the original owner keeps the value and can use it again afterward. Rust’s **borrow checker** verifies, while compiling, that every reference points to live data, which is how Rust eliminates an entire category of bugs (dangling pointers, use-after-free) that you would otherwise debug at runtime. This page covers **shared (immutable) references**: the `&T` borrow that lets you read. Mutable references (`&mut T`) and their exclusivity rule have their own page: see [Mutable References](/05-ownership/03-mutable-references/). *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) In JavaScript, objects and arrays are always handled through references. When you pass one to a function, the function receives a pointer to the same underlying object, so it can mutate your data, and the change is visible to you afterward. ```typescript 1 // TypeScript/JavaScript - objects are passed by reference 2 interface User { 3 name: string; 4 email: string; 5 loginCount: number; 6 } 7 8 function describe(user: User): string { 9 return `${user.name} <${user.email}> logged in ${user.loginCount} times`; 10 } 11 12 // A function can mutate the object you handed it... 13 function recordLogin(user: User): void { 14 user.loginCount += 1; // mutates the caller's object! 15 } 16 17 const user: User = { name: "Ada", email: "ada@example.com", loginCount: 7 }; 18 19 console.log(describe(user)); // read it 20 recordLogin(user); // and silently change it 21 console.log(user.loginCount); // 8 — the original object changed 22 23 // Aliasing: two names, one object 24 const alias = user; 25 alias.loginCount = 0; 26 console.log(user.loginCount); // 0 — `user` and `alias` ARE the same object ``` **Key points:** * Objects/arrays are passed and assigned **by reference**. * Any holder of the reference can mutate the shared object. * There is no language-level distinction between “I want to read this” and “I want to change this.” You rely on convention, documentation, or defensive copies (`structuredClone`, spreads) to stay safe. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) Rust makes the distinction explicit. A plain `&` reference is a **read-only loan**: the borrower may look at the value but not change it. The owner keeps the value the whole time. ```rust 1 #[derive(Debug)] 2 struct User { 3 name: String, 4 email: String, 5 login_count: u32, 6 } 7 8 // `&User` is a SHARED reference: read-only access, no ownership taken. 9 fn describe(user: &User) -> String { 10 format!( 11 "{} <{}> logged in {} times", 12 user.name, user.email, user.login_count 13 ) 14 } 15 16 fn is_active(user: &User) -> bool { 17 user.login_count > 0 18 } 19 20 fn main() { 21 let user = User { 22 name: String::from("Ada"), 23 email: String::from("ada@example.com"), 24 login_count: 7, 25 }; 26 27 // Borrow the same value as many times as we like. 28 println!("{}", describe(&user)); 29 println!("active? {}", is_active(&user)); 30 31 // `user` is still fully owned here — nothing was moved or consumed. 32 println!("{:?}", user); 33 } ``` **Output (compile-verified):** ```text 1 Ada logged in 7 times 2 active? true 3 User { name: "Ada", email: "ada@example.com", login_count: 7 } ``` **Key points:** * `&user` creates a **shared borrow**; `&User` is the type “shared reference to a `User`.” * The function borrows the value, uses it, and gives it back; `user` is usable afterward. * A shared reference cannot be used to mutate. To change the value, you would need a mutable reference (`&mut`), covered on the next page. > **Note:** Without borrowing, passing `user` into `describe` would **move** it (transfer ownership) and you could no longer use `user` afterward. Borrowing is how you avoid that. See [Ownership Rules](/05-ownership/01-ownership-rules/) and [Move, Copy, Clone](/05-ownership/06-move-copy-clone/) for the full story on moves. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### What a reference actually is [Section titled “What a reference actually is”](#what-a-reference-actually-is) A reference is a pointer: a small value that holds the **memory address** of something else. The `&` operator creates a reference; the `*` operator **dereferences** it (follows the pointer to read the value behind it). ```rust 1 fn main() { 2 let x = 10; 3 let r = &x; // r is a reference to x (a pointer under the hood) 4 5 println!("x = {}", x); 6 println!("*r = {}", *r); // explicitly follow the pointer 7 println!("r = {}", r); // most operations auto-deref, so *r is often optional 8 9 // A reference holds the address of what it points at. 10 println!("address of x : {:p}", &x); 11 println!("value held by r : {:p}", r); 12 println!("equal? {}", std::ptr::eq(&x, r)); 13 } ``` **Output (compile-verified; the exact hex addresses vary by run and platform, but the two are always equal):** ```text 1 x = 10 2 *r = 10 3 r = 10 4 address of x : 0x16d025c44 5 value held by r : 0x16d025c44 6 equal? true ``` The two printed addresses are identical because `r` literally points at `x`. In most everyday code you rarely write `*` yourself; Rust auto-dereferences for method calls and field access (`user.name` works whether `user` is a `User` or a `&User`). You reach for `*` mainly when you need the value itself, such as in arithmetic (`*r + 1`). > **Note:** Unlike a JavaScript reference, a Rust reference is **guaranteed non-null and always valid** for as long as it exists. There is no `null`, no `undefined`, and no dangling pointer; the compiler proves this before your program ever runs. ### Borrowing instead of moving [Section titled “Borrowing instead of moving”](#borrowing-instead-of-moving) Here is the canonical example. We borrow a `String` so the function can measure it without consuming it. ```rust 1 fn main() { 2 let message = String::from("hello, borrow"); 3 4 // Borrow `message` immutably — `len` only needs to read it. 5 let length = calculate_length(&message); 6 7 // `message` is still valid here because we only lent it out. 8 println!("'{}' has length {}", message, length); 9 } 10 11 fn calculate_length(s: &String) -> usize { 12 s.len() 13 } // `s` (the reference) goes out of scope here, but because it is only a 14 // borrow, the underlying String is NOT dropped — the caller still owns it. ``` **Output (compile-verified):** ```text 1 'hello, borrow' has length 13 ``` The action of creating a reference is called **borrowing**. The function `calculate_length` borrows `message`, reads its length, and when `s` goes out of scope only the *reference* is dropped, never the `String` it pointed at. The owner (`main`) gets its value back intact. > **Note:** `&String` is used here purely to mirror the owned-vs-borrowed contrast against the moving version. In production you would write `s: &str` (see [Best Practice #2](#best-practices)), and `cargo clippy` will in fact suggest exactly that via the `clippy::ptr_arg` lint, since `&str` accepts both `&String` and string slices. ### Many shared borrows at once are fine [Section titled “Many shared borrows at once are fine”](#many-shared-borrows-at-once-are-fine) A shared reference promises **read-only** access. Because no one can mutate, it is perfectly safe to hand out any number of shared references simultaneously: ```rust 1 fn main() { 2 let data = vec![1, 2, 3, 4, 5]; 3 4 let first = &data; 5 let second = &data; 6 let third = &data; 7 8 // Many simultaneous shared borrows are allowed: nobody can mutate. 9 println!("sum via first = {}", first.iter().sum::()); 10 println!("len via second = {}", second.len()); 11 println!("max via third = {:?}", third.iter().max()); 12 13 // The original owner is still usable too. 14 println!("data = {:?}", data); 15 } ``` **Output (compile-verified):** ```text 1 sum via first = 15 2 len via second = 5 3 max via third = Some(5) 4 data = [1, 2, 3, 4, 5] ``` This is the “shared” half of Rust’s central borrowing rule: **either many shared (`&`) references, or exactly one mutable (`&mut`) reference, but never both at the same time.** The exclusivity of `&mut` is the subject of the [Mutable References](/05-ownership/03-mutable-references/) page; here it is enough to know that as long as you only borrow with `&`, you can have as many borrows as you want. ### The borrow checker [Section titled “The borrow checker”](#the-borrow-checker) The **borrow checker** is the part of the Rust compiler that tracks the lifetime of every reference and rejects any program where a reference could point at data that is gone or is being mutated out from under it. It runs at compile time, so the checks cost nothing at runtime. The two guarantees most relevant to shared references are: 1. A reference may never outlive the value it borrows (no dangling references). 2. While any shared reference exists, the borrowed value cannot be mutated or moved. (The exception is *interior mutability* — types like `Cell`, `RefCell`, and `Mutex` that allow controlled mutation behind `&T`; they are the subject of [Smart Pointers](/10-smart-pointers/02-refcell-mutex/).) You do not call the borrow checker; it is simply part of `cargo build` / `cargo check`. When it rejects your code you get an error like `E0597` (“does not live long enough”) or `E0502` (“cannot borrow as mutable because it is also borrowed as immutable”). Reading those errors is a core Rust skill — they almost always point at exactly the right fix. ### Dangling references are impossible [Section titled “Dangling references are impossible”](#dangling-references-are-impossible) In C you can return a pointer to a local variable and get undefined behavior. In JavaScript you can’t even express the problem because the GC keeps anything reachable alive. Rust takes a third path: it makes the dangling case a **compile error**. ```rust 1 // does not compile (error[E0106]: missing lifetime specifier) 2 fn dangle() -> &String { 3 let s = String::from("temporary"); 4 &s // returning a reference to `s`... 5 } // ...but `s` is dropped here, so the reference would dangle ``` The compiler refuses this outright (see [Common Pitfalls](#common-pitfalls) for the full message). The fix is to return the **owned** `String` instead of a reference to it. Then ownership moves out to the caller and nothing is left dangling: ```rust 1 fn no_dangle() -> String { 2 let s = String::from("temporary"); 3 s // ownership moves out to the caller — perfectly safe 4 } 5 6 fn main() { 7 let owned = no_dangle(); 8 println!("{}", owned); 9 } ``` *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Aspect | TypeScript/JavaScript | Rust | | --------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------- | | How objects are passed | Always by reference, implicitly | You choose: by value (move/copy) or by reference (`&` / `&mut`) | | Read vs. write intent | Not expressed in the language | `&T` = read-only, `&mut T` = read/write | | Can the callee mutate my data? | Yes, silently, unless you copy defensively | Only if you explicitly lend a `&mut` | | Dangling references | Impossible (GC keeps things alive) | Impossible (compiler rejects them), but **without** a garbage collector | | Multiple holders mutating at once | Allowed (a common bug source) | Forbidden at compile time for `&mut`; unlimited for `&` | | Cost | GC bookkeeping, allocation pressure | Zero runtime cost; checks happen at compile time | | `null` / `undefined` references | Possible (`obj?.field`) | A `&T` is never null; absence is modeled with `Option<&T>` | ### The big mental shift [Section titled “The big mental shift”](#the-big-mental-shift) In JavaScript, “passing a reference” is the *only* option and it always grants full mutation rights to the callee. In Rust, references are **explicit, typed, and read-only by default**. A function signature like `fn save(user: &User)` is a machine-checked promise: “I will look at your `User`, I will not change it, and I will not keep it after I return.” That promise is enforced by the compiler, not by code review. > **Tip:** When migrating a mental model, read `&T` as “a temporary read-only loan of a `T`.” The owner is guaranteed to still have the value when the loan ends. ### Borrowing vs. cloning [Section titled “Borrowing vs. cloning”](#borrowing-vs-cloning) A JavaScript habit is to defensively copy (`{ ...obj }`, `structuredClone(obj)`) to avoid accidental shared mutation. In Rust, borrowing usually replaces that copy entirely: you hand out a cheap `&` reference instead of duplicating data. Reach for `.clone()` only when you genuinely need a second independent owner — see [Move, Copy, Clone](/05-ownership/06-move-copy-clone/). *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Returning a reference to a local value [Section titled “Pitfall 1: Returning a reference to a local value”](#pitfall-1-returning-a-reference-to-a-local-value) This is the dangling-reference attempt. The borrowed value would be destroyed at the end of the function. ```rust 1 // does not compile 2 fn dangle() -> &String { 3 let s = String::from("temporary"); 4 &s 5 } 6 7 fn main() { 8 let r = dangle(); 9 println!("{}", r); 10 } ``` **Real compiler error:** ```text 1 error[E0106]: missing lifetime specifier 2 --> src/main.rs:2:16 3 | 4 2 | fn dangle() -> &String { 5 | ^ expected named lifetime parameter 6 | 7 = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from 8 help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` 9 | 10 2 | fn dangle() -> &'static String { 11 | +++++++ 12 help: instead, you are more likely to want to return an owned value 13 | 14 2 - fn dangle() -> &String { 15 2 + fn dangle() -> String { 16 | ``` **Fix:** Return the owned `String` (drop the `&`). The compiler’s last suggestion is exactly right. When you genuinely need to return a borrow, it must borrow from one of the function’s *inputs*, which is what [Lifetimes](/05-ownership/04-lifetimes/) are about. ### Pitfall 2: Using a value after its referent has been dropped [Section titled “Pitfall 2: Using a value after its referent has been dropped”](#pitfall-2-using-a-value-after-its-referent-has-been-dropped) A subtler version: the reference outlives the value’s scope. ```rust 1 // does not compile 2 fn main() { 3 let reference; 4 { 5 let value = String::from("short-lived"); 6 reference = &value; 7 } // `value` is dropped here 8 println!("{}", reference); // tries to use the freed value 9 } ``` **Real compiler error:** ```text 1 error[E0597]: `value` does not live long enough 2 --> src/main.rs:6:21 3 | 4 5 | let value = String::from("short-lived"); 5 | ----- binding `value` declared here 6 6 | reference = &value; 7 | ^^^^^^ borrowed value does not live long enough 8 7 | } // `value` is dropped here 9 | - `value` dropped here while still borrowed 10 8 | println!("{}", reference); // tries to use the freed value 11 | --------- borrow later used here ``` **Fix:** Make the borrowed value live at least as long as the reference, usually by declaring `value` in the outer scope. In a GC language this code would “work” by keeping `value` alive; Rust instead tells you the lifetimes don’t line up. ### Pitfall 3: Passing by value when you meant to borrow [Section titled “Pitfall 3: Passing by value when you meant to borrow”](#pitfall-3-passing-by-value-when-you-meant-to-borrow) A very common beginner mistake: forgetting the `&`, which **moves** the value into the function. ```rust 1 // does not compile 2 fn print_it(s: String) { 3 println!("{}", s); 4 } 5 6 fn main() { 7 let owned = String::from("data"); 8 print_it(owned); // moves `owned` into the function 9 println!("{}", owned); // try to use it again 10 } ``` **Real compiler error (abridged):** ```text 1 error[E0382]: borrow of moved value: `owned` 2 --> src/main.rs:9:20 3 | 4 7 | let owned = String::from("data"); 5 | ----- move occurs because `owned` has type `String`, which does not implement the `Copy` trait 6 8 | print_it(owned); // moves `owned` into the function 7 | ----- value moved here 8 9 | println!("{}", owned); // try to use it again 9 | ^^^^^ value borrowed here after move 10 | 11 note: consider changing this parameter type in function `print_it` to borrow instead if owning the value isn't necessary 12 help: consider cloning the value if the performance cost is acceptable 13 | 14 8 | print_it(owned.clone()); // moves `owned` into the function 15 | ++++++++ ``` **Fix:** Borrow instead of moving: change the signature to `fn print_it(s: &String)` (or better, `s: &str`) and call it as `print_it(&owned)`. Cloning also compiles, but it needlessly duplicates the data; prefer borrowing. ### Pitfall 4: Trying to mutate through a shared reference [Section titled “Pitfall 4: Trying to mutate through a shared reference”](#pitfall-4-trying-to-mutate-through-a-shared-reference) A shared `&` reference is read-only. Attempting to write through it is a compile error. ```rust 1 // does not compile 2 fn main() { 3 let mut count = 0; 4 let r = &count; // shared (immutable) reference 5 *r += 1; // try to mutate through it 6 println!("{}", count); 7 } ``` **Real compiler error (abridged):** ```text 1 error[E0594]: cannot assign to `*r`, which is behind a `&` reference 2 --> src/main.rs:4:5 3 | 4 4 | *r += 1; // try to mutate through it 5 | ^^^^^^^ `r` is a `&` reference, so the data it refers to cannot be written 6 | 7 help: consider changing this to be a mutable reference 8 | 9 3 | let r = &mut count; 10 | +++ ``` **Fix:** Use `&mut count` to get a mutable reference (and note that `count` is already `let mut`). Mutable borrows have their own exclusivity rule — read [Mutable References](/05-ownership/03-mutable-references/) next. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Borrow by default; take ownership only when you must [Section titled “1. Borrow by default; take ownership only when you must”](#1-borrow-by-default-take-ownership-only-when-you-must) If a function only needs to *read* a value, take it by shared reference. This is the most flexible and cheapest signature — callers keep their data and pay nothing for the call. ```rust 1 // Idiomatic: read-only access via a shared borrow 2 fn word_count(text: &str) -> usize { 3 text.split_whitespace().count() 4 } ``` ### 2. Prefer `&str` over `&String`, and `&[T]` over `&Vec` [Section titled “2. Prefer \&str over \&String, and &\[T\] over \&Vec\”](#2-prefer-str-over-string-and-t-over-vect) A `&str` accepts both string literals and borrowed `String`s (via automatic deref coercion), so it is strictly more general. Likewise, `&[T]` accepts arrays, `Vec`s, and other slices. ```rust 1 fn shout(text: &str) -> String { 2 text.to_uppercase() 3 } 4 5 fn main() { 6 let owned = String::from("hello"); 7 println!("{}", shout(&owned)); // &String coerces to &str 8 println!("{}", shout("world")); // &str literal works too 9 } ``` > **Tip:** Clippy will actively suggest changing `&String` parameters to `&str` and `&Vec` to `&[T]`. Following this makes your APIs accept more callers for free. ### 3. Let the compiler guide you [Section titled “3. Let the compiler guide you”](#3-let-the-compiler-guide-you) The borrow-checker errors are unusually good. When you hit one, read the `help:` lines — they frequently contain the literal fix (add `&`, add `mut`, return an owned value). Treat the borrow checker as a pair programmer, not an adversary. ### 4. Reach for references before `.clone()` [Section titled “4. Reach for references before .clone()”](#4-reach-for-references-before-clone) If you find yourself cloning to “make the error go away,” pause: a borrow is usually the right answer and avoids the allocation. Clone only when you truly need a second independent owner. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small order-processing module. Three functions all **borrow** a slice of orders; none takes ownership, so the caller can run every report in sequence on the same data without any copying. ```rust 1 use std::collections::HashMap; 2 3 #[derive(Debug)] 4 struct Order { 5 id: u32, 6 customer: String, 7 total_cents: u64, 8 status: String, 9 } 10 11 // Borrow the orders read-only; sum the paid ones. 12 fn total_revenue(orders: &[Order]) -> u64 { 13 orders 14 .iter() 15 .filter(|o| o.status == "paid") 16 .map(|o| o.total_cents) 17 .sum() 18 } 19 20 // The returned map borrows customer names FROM `orders`, so its keys 21 // (`&str`) live exactly as long as the borrowed slice. No strings copied. 22 fn revenue_by_customer(orders: &[Order]) -> HashMap<&str, u64> { 23 let mut totals: HashMap<&str, u64> = HashMap::new(); 24 for order in orders { 25 if order.status == "paid" { 26 *totals.entry(order.customer.as_str()).or_insert(0) += order.total_cents; 27 } 28 } 29 totals 30 } 31 32 // Return a borrow of one order that lives as long as the input slice. 33 fn find_order<'a>(orders: &'a [Order], id: u32) -> Option<&'a Order> { 34 orders.iter().find(|o| o.id == id) 35 } 36 37 fn main() { 38 let orders = vec![ 39 Order { id: 1, customer: String::from("Ada"), total_cents: 4_500, status: String::from("paid") }, 40 Order { id: 2, customer: String::from("Linus"), total_cents: 9_900, status: String::from("refunded") }, 41 Order { id: 3, customer: String::from("Ada"), total_cents: 1_200, status: String::from("paid") }, 42 ]; 43 44 println!("Total revenue: ${:.2}", total_revenue(&orders) as f64 / 100.0); 45 46 let by_customer = revenue_by_customer(&orders); 47 let mut rows: Vec<_> = by_customer.iter().collect(); 48 rows.sort(); 49 for (customer, cents) in rows { 50 println!(" {customer}: ${:.2}", *cents as f64 / 100.0); 51 } 52 53 match find_order(&orders, 3) { 54 Some(order) => println!("Found order {}: {} cents", order.id, order.total_cents), 55 None => println!("No such order"), 56 } 57 58 // `orders` is still fully owned and usable here. 59 println!("Processed {} orders.", orders.len()); 60 } ``` **Output (compile-verified):** ```text 1 Total revenue: $57.00 2 Ada: $57.00 3 Found order 3: 1200 cents 4 Processed 3 orders. ``` Notice that `revenue_by_customer` returns a `HashMap<&str, u64>` whose keys are *borrowed* from the orders: the borrow checker guarantees the map cannot outlive the data it points into. And `find_order` uses an explicit lifetime (`'a`) to say “the returned reference lives as long as the `orders` slice you gave me.” That annotation is your bridge to the next topics: [Lifetimes](/05-ownership/04-lifetimes/) and [Lifetime Elision](/05-ownership/05-lifetime-elision/). > **Note:** `cargo clippy` will flag `find_order`’s `'a` with `clippy::needless_lifetimes`, because the elision rules let you write `fn find_order(orders: &[Order], id: u32) -> Option<&Order>` and the compiler infers the same lifetime. The explicit `'a` is shown here on purpose, to make the “returned reference borrows from the input” relationship visible before we cover elision. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — References and Borrowing](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html) * [The Rust Book — The Slice Type](https://doc.rust-lang.org/book/ch04-03-slices.html) * [Rust by Example — Borrowing](https://doc.rust-lang.org/rust-by-example/scope/borrow.html) * [Rust Reference — References (`&` and `&mut`)](https://doc.rust-lang.org/reference/types/pointer.html) > **Note:** This guide targets the repository’s [pinned verification toolchain](/00-introduction/05-version-policy/) and the 2024 edition; `cargo new` selects that edition automatically. The pin is a reproducible baseline, not a moving “latest stable” claim. ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Section 05 — Ownership (overview)](/05-ownership/) * [Stack vs. Heap](/05-ownership/00-stack-heap/): where the value a reference points at actually lives * [Ownership Rules](/05-ownership/01-ownership-rules/): the rules that make borrowing necessary * [Mutable References](/05-ownership/03-mutable-references/): `&mut` and the one-writer-XOR-many-readers rule * [Lifetimes](/05-ownership/04-lifetimes/): naming how long references stay valid * [Lifetime Elision](/05-ownership/05-lifetime-elision/): when you can omit lifetime annotations * [Move, Copy, Clone](/05-ownership/06-move-copy-clone/): what happens when you *don’t* borrow * [Reference Counting (`Rc` / `Arc`)](/05-ownership/07-reference-counting/): shared ownership when a single owner isn’t enough * [Variables and Mutability](/02-basics/00-variables/): `let` vs `let mut`, the foundation for `&` vs `&mut` * [Introduction](/00-introduction/) and [Getting Started](/01-getting-started/): if you need a refresher on setup * [Section 06 — Data Structures](/06-data-structures/): structs and enums you’ll be borrowing next *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Borrow instead of move [Section titled “Exercise 1: Borrow instead of move”](#exercise-1-borrow-instead-of-move) **Difficulty:** Beginner **Objective:** Convert a function that consumes its argument into one that borrows it. **Instructions:** The function below takes ownership of the `Vec`, so the call site can’t use `names` afterward. Rewrite `longest_name` to **borrow** the data so that `main` can still print `names` at the end. Implement the body so it returns a reference to the longest name. ```rust 1 fn longest_name(names: Vec) -> String { 2 // TODO: borrow instead of consuming; return a reference to the longest name 3 /* ??? */ 4 } 5 6 fn main() { 7 let names = vec![ 8 String::from("Bo"), 9 String::from("Alexander"), 10 String::from("Kai"), 11 ]; 12 println!("Longest: {}", longest_name(/* ??? */)); 13 println!("All names still here: {:?}", names); // must still compile 14 } ``` Solution ```rust 1 fn longest_name(names: &[String]) -> &String { 2 let mut longest = &names[0]; 3 for name in names { 4 if name.len() > longest.len() { 5 longest = name; 6 } 7 } 8 longest 9 } 10 11 fn main() { 12 let names = vec![ 13 String::from("Bo"), 14 String::from("Alexander"), 15 String::from("Kai"), 16 ]; 17 println!("Longest: {}", longest_name(&names)); 18 println!("All names still here: {:?}", names); 19 } ``` **Output:** ```text 1 Longest: Alexander 2 All names still here: ["Bo", "Alexander", "Kai"] ``` Taking `&[String]` (a slice) instead of `Vec` borrows the data, so `names` remains owned by `main`. Returning `&String` hands back a reference into that borrowed slice. ### Exercise 2: Return a borrowed slice [Section titled “Exercise 2: Return a borrowed slice”](#exercise-2-return-a-borrowed-slice) **Difficulty:** Intermediate **Objective:** Write a function that returns a `&str` slice borrowed from its input. **Instructions:** Implement `first_word` so it returns the first whitespace-delimited word of the input as a borrowed slice (no allocation, no `String`). It should accept both `&String` and `&str` literals. ```rust 1 fn first_word(s: &str) -> &str { 2 // TODO: return everything up to the first space, or the whole string 3 /* ??? */ 4 } 5 6 fn main() { 7 let sentence = String::from("borrow checker rules"); 8 println!("{}", first_word(&sentence)); // "borrow" 9 println!("{}", first_word("hello world")); // "hello" 10 println!("{}", sentence); // sentence still usable 11 } ``` Solution ```rust 1 fn first_word(s: &str) -> &str { 2 match s.find(' ') { 3 Some(i) => &s[..i], 4 None => s, 5 } 6 } 7 8 fn main() { 9 let sentence = String::from("borrow checker rules"); 10 println!("{}", first_word(&sentence)); 11 println!("{}", first_word("hello world")); 12 println!("{}", sentence); 13 } ``` **Output:** ```text 1 borrow 2 hello 3 borrow checker rules ``` The returned `&str` borrows from `s`, so it can never outlive the string it slices into; the borrow checker enforces that automatically. Accepting `&str` (rather than `&String`) lets the same function serve both a borrowed `String` and a string literal. ### Exercise 3: Prove that shared borrows alias safely [Section titled “Exercise 3: Prove that shared borrows alias safely”](#exercise-3-prove-that-shared-borrows-alias-safely) **Difficulty:** Intermediate **Objective:** Show that multiple shared references to one value can coexist, and that the value remains usable afterward. **Instructions:** Given the `Counter` struct, write a function `read_twice` that takes **two** shared references to a `Counter` and returns the sum of their `value` fields. In `main`, call it with two borrows of the *same* counter, then print the counter to confirm it was never consumed. ```rust 1 #[derive(Debug)] 2 struct Counter { 3 value: i32, 4 } 5 6 fn read_twice(/* ??? */) -> i32 { 7 // TODO 8 /* ??? */ 9 } 10 11 fn main() { 12 let c = Counter { value: 21 }; 13 println!("Doubled: {}", read_twice(/* ??? */)); 14 println!("{:?}", c); 15 } ``` Solution ```rust 1 #[derive(Debug)] 2 struct Counter { 3 value: i32, 4 } 5 6 fn read_twice(a: &Counter, b: &Counter) -> i32 { 7 a.value + b.value 8 } 9 10 fn main() { 11 let c = Counter { value: 21 }; 12 // Two shared borrows of the same value, at once — perfectly legal. 13 println!("Doubled: {}", read_twice(&c, &c)); 14 println!("{:?}", c); 15 } ``` **Output:** ```text 1 Doubled: 42 2 Counter { value: 21 } ``` Passing `&c` twice creates two simultaneous shared borrows. Because neither can mutate, this is allowed, and `c` is still owned by `main` afterward. (Try changing one parameter to `&mut Counter` and calling `read_twice(&mut c, &c)`: the borrow checker will reject it, which is the exclusivity rule covered in [Mutable References](/05-ownership/03-mutable-references/).) # Mutable References > &mut T borrows a value to mutate it under one rule: one writer XOR many readers, catching at compile time the aliasing bugs JS ships to production. A **mutable reference** (`&mut T`) lets you temporarily borrow a value so you can *change* it without taking ownership. It is the counterpart to the shared, read-only borrows you saw in [Borrowing](/05-ownership/02-borrowing/), but it comes with one strict, far-reaching rule that does not exist in TypeScript or JavaScript. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) A mutable reference is a borrow that grants write access. Rust enforces a single rule around them: at any given moment a value may have **either one mutable reference, or any number of shared (read-only) references, never both at once**. This “one writer XOR many readers” rule is checked entirely at compile time, and it is how Rust eliminates whole categories of bugs (iterator invalidation, aliasing surprises, and data races) that a TypeScript/JavaScript developer normally only catches at runtime, if at all. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) In JavaScript, objects and arrays are passed by reference, and *any* number of references can read **and** write the same value at the same time. Nothing stops you. ```typescript 1 // TypeScript - any number of aliases can mutate freely 2 interface Account { 3 balance: number; 4 } 5 6 function deposit(account: Account, amount: number): void { 7 account.balance += amount; 8 } 9 10 const account: Account = { balance: 100 }; 11 12 // Two names for the SAME object - both can mutate it: 13 const alias = account; 14 deposit(account, 50); 15 alias.balance -= 10; 16 17 console.log(account.balance); // 140 18 console.log(alias === account); // true - same reference 19 20 // A classic runtime bug: mutating an array while iterating it 21 const nums = [1, 2, 3]; 22 for (const n of nums) { 23 if (n === 1) nums.push(n + 1); // appends a 2 the first (and only) time n is 1 24 } 25 console.log(nums); // [1, 2, 3, 2] - the loop also visited the pushed 2 ``` Two things are worth noticing: * `alias` and `account` are two bindings to **one** object; mutating through either is visible through the other, and there is no compiler check coordinating them. * Mutating `nums` while a `for...of` loop is iterating it is perfectly legal JavaScript. The `1` matches once, so a single `2` is appended; the loop then visits that newly pushed element before ending, because `for...of` re-reads the live array on each step. It does not throw. With a condition that always matched the new last element (e.g. `if (n < 3) nums.push(n + 1)`), this same pattern becomes an accidental infinite loop, and the compiler would never warn you. > **Note:** The output above is real. In Node v22, `for...of` over an array re-reads the live array on each step, so pushed elements *are* visited. `Array.prototype.forEach`, by contrast, silently *ignores* elements added during iteration. Either way, JavaScript hands you a foot-gun and trusts you not to pull the trigger. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) Rust models “I want to change this value through a borrow” explicitly with `&mut`. The compiler then guarantees no other live reference exists while you hold it. ```rust 1 fn deposit(balance: &mut f64, amount: f64) { 2 *balance += amount; // `*` dereferences to reach the value behind the reference 3 } 4 5 fn main() { 6 let mut balance = 100.0; 7 8 deposit(&mut balance, 50.0); // hand out a mutable borrow, then take it back 9 deposit(&mut balance, 25.0); // and again - each borrow is short-lived 10 11 println!("Balance: {}", balance); 12 } ``` ```text 1 Balance: 175 ``` And the JavaScript loop bug? Rust will not even compile it: ```rust 1 fn main() { 2 let mut nums = vec![1, 2, 3]; 3 for n in &nums { // shared borrow held for the whole loop 4 if *n == 2 { 5 nums.push(99); // does not compile (error[E0502]): mutate while borrowed 6 } 7 } 8 } ``` ```text 1 error[E0502]: cannot borrow `nums` as mutable because it is also borrowed as immutable 2 --> src/main.rs:5:13 3 | 4 3 | for n in &nums { // shared borrow for the whole loop 5 | ----- 6 | | 7 | immutable borrow occurs here 8 | immutable borrow later used here 9 4 | if *n == 2 { 10 5 | nums.push(99); // mutate while iterating 11 | ^^^^^^^^^^^^^ mutable borrow occurs here ``` The very bug that JavaScript ships to production is a compile error in Rust. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Creating and using a `&mut` [Section titled “Creating and using a \&mut”](#creating-and-using-a-mut) There are two pieces of syntax to learn: * `&mut value` — *create* a mutable reference (you must own a `mut` binding to do this). * `*reference` — *dereference*: follow the reference to read or write the underlying value. ```rust 1 fn main() { 2 let mut count = 0; // the binding must be `mut` to be mutably borrowed 3 let r = &mut count; // r has type &mut i32 4 5 *r += 1; // write through the reference 6 *r += 1; 7 8 println!("{}", count); // 2 - the change is visible through the owner 9 } ``` When you call a method on a reference, Rust inserts the `*` for you (this is *automatic dereferencing*), which is why `r.push(...)` works without writing `(*r).push(...)`. ### `&mut self` methods [Section titled “\&mut self methods”](#mut-self-methods) The most common place you will write `&mut` is method receivers. A method that takes `&mut self` can mutate the struct it is called on: ```rust 1 struct Counter { 2 value: i32, 3 } 4 5 impl Counter { 6 fn increment(&mut self) { 7 self.value += 1; 8 } 9 } 10 11 fn bump_twice(c: &mut Counter) { 12 c.increment(); // reborrow of *c happens automatically 13 c.increment(); 14 } 15 16 fn main() { 17 let mut counter = Counter { value: 0 }; 18 bump_twice(&mut counter); 19 println!("value = {}", counter.value); // value = 2 20 } ``` ```text 1 value = 2 ``` > **Tip:** `&mut self`, `&self`, and `self` are the Rust equivalents of asking “does this method mutate the object, just read it, or consume it entirely?” In a TypeScript class every method silently has full mutable access to `this`; in Rust the receiver type makes the answer part of the signature. ### The one rule: mutable XOR shared [Section titled “The one rule: mutable XOR shared”](#the-one-rule-mutable-xor-shared) This is the heart of the topic. While a `&mut T` to a value is alive, **nothing else** may reference that value: not another `&mut`, and not even a read-only `&`. Conversely, while one or more shared `&T` borrows are alive, **no** `&mut` may exist. You get one of two states: | State | Mutable refs | Shared refs | Analogy | | --------------- | ------------ | ----------- | --------------------- | | Exclusive write | exactly 1 | 0 | A `RwLock` write lock | | Shared read | 0 | many | A `RwLock` read lock | This is sometimes called **“Aliasing XOR Mutability”**: you can have aliasing (many references) *or* mutability (the ability to write), but never both for the same data at the same time. ```rust 1 fn main() { 2 let mut data = vec![1, 2, 3]; 3 4 let a = &mut data; 5 let b = &mut data; // does not compile (error[E0499]): second &mut while first is live 6 7 a.push(4); 8 b.push(5); 9 } ``` ```text 1 error[E0499]: cannot borrow `data` as mutable more than once at a time 2 --> src/main.rs:5:13 3 | 4 4 | let a = &mut data; 5 | --------- first mutable borrow occurs here 6 5 | let b = &mut data; // does not compile (error[E0499]): second &mut while first is live 7 | ^^^^^^^^^ second mutable borrow occurs here 8 6 | 9 7 | a.push(4); 10 | - first borrow later used here ``` ### Non-lexical lifetimes (NLL): a borrow ends at its last use [Section titled “Non-lexical lifetimes (NLL): a borrow ends at its last use”](#non-lexical-lifetimes-nll-a-borrow-ends-at-its-last-use) Earlier (pre-2018) the rule above would have been painfully strict, because a borrow used to last until the end of its enclosing `{}` block. Modern Rust uses **non-lexical lifetimes**: a borrow ends at its **last use**, not at the closing brace. That makes the rule far more pleasant in practice: borrows you are “done with” stop counting immediately. ```rust 1 fn main() { 2 let mut scores = vec![10, 20, 30]; 3 4 let first = &scores[0]; // shared borrow starts 5 println!("First score: {}", first); // ...and ends here (last use of `first`) 6 7 scores.push(40); // mutable borrow is fine now - the shared borrow already ended 8 println!("Scores: {:?}", scores); 9 } ``` ```text 1 First score: 10 2 Scores: [10, 20, 30, 40] ``` Both borrows touch `scores`, yet this compiles: the shared borrow’s lifetime ends after the first `println!`, so the later mutable borrow does not overlap it. If you reordered the code so `first` were used *after* `scores.push(40)`, the borrows would overlap and you would get the `E0502` error from the Quick Overview. > **Note:** “Lifetime” here means the span of code over which a reference is actually used, not a wall-clock duration and not the lexical scope. Annotated lifetimes (`'a`) are a related but separate topic; see [Lifetimes](/05-ownership/04-lifetimes/). ### Why this prevents data races at compile time [Section titled “Why this prevents data races at compile time”](#why-this-prevents-data-races-at-compile-time) A **data race** is when two or more threads access the same memory at the same time, at least one access is a write, and there is no synchronization. The mutable-XOR-shared rule makes data races *structurally impossible*: if writing requires exclusive access, two threads can never both hold a writer to the same data. The borrow checker enforces this even across threads. Here two scoped threads each try to mutate the same counter: ```rust 1 use std::thread; 2 3 fn main() { 4 let mut total = 0u64; 5 6 thread::scope(|s| { 7 s.spawn(|| { 8 total += 1; // does not compile (error[E0499]): both closures want &mut total 9 }); 10 s.spawn(|| { 11 total += 1; 12 }); 13 }); 14 15 println!("{}", total); 16 } ``` ```text 1 error[E0499]: cannot borrow `total` as mutable more than once at a time 2 --> src/main.rs:10:17 3 | 4 6 | thread::scope(|s| { 5 | - has type `&'1 Scope<'1, '_>` 6 7 | s.spawn(|| { 7 | - -- first mutable borrow occurs here 8 | _________| 9 | | 10 8 | | total += 1; // both closures want &mut total 11 | | ----- first borrow occurs due to use of `total` in closure 12 9 | | }); 13 | |__________- argument requires that `total` is borrowed for `'1` 14 10 | s.spawn(|| { 15 | ^^ second mutable borrow occurs here ``` The exact code that would be a *runtime* heisenbug in JavaScript (if JavaScript had shared-memory threads) is a *compile-time* error in Rust. To actually share writable state across threads you must reach for a synchronization type such as `Mutex` or `Arc>`, which re-establish the “one writer at a time” guarantee at runtime, covered in [Reference Counting](/05-ownership/07-reference-counting/) and Section 10. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript/JavaScript | Rust | | ------------------------------ | ------------------------------------------------------- | -------------------------------------------------------- | | Mutating through an alias | Always allowed; unlimited aliases can write | At most one `&mut` at a time; checked at compile time | | Read + write at once | Allowed (reader sees writer’s changes mid-flight) | Forbidden: `&mut` excludes all `&` and vice versa | | Mutating a collection mid-loop | Allowed; silent bugs / accidental infinite loops | Compile error (`E0502`) | | Data races | N/A (single-threaded) / possible with SharedArrayBuffer | Impossible by construction; the type system forbids them | | When a “borrow” ends | No concept; GC reclaims when unreachable | At the reference’s last use (non-lexical lifetimes) | | Declaring intent to mutate | Implicit: every method can mutate `this` | Explicit: `&mut` in the type, `mut` on the binding | ### Mutability is a property of the borrow, not just the value [Section titled “Mutability is a property of the borrow, not just the value”](#mutability-is-a-property-of-the-borrow-not-just-the-value) In JavaScript, a `const` binding to an object still lets you mutate the object. Rust separates three independent questions: 1. Is the **binding** mutable? (`let` vs `let mut`) 2. Is this particular **borrow** allowed to write? (`&` vs `&mut`) 3. Does anyone *else* hold a borrow right now? (the XOR rule) You need a `mut` binding to create a `&mut`, the reference type must be `&mut`, and the borrow checker must confirm exclusivity. All three line up to make “who can change this, and when” explicit. ### `&mut` is not a “pointer you copy around” [Section titled “\&mut is not a “pointer you copy around””](#mut-is-not-a-pointer-you-copy-around) A `&mut T` cannot be freely copied the way a JavaScript reference can. If you pass it along, the original is **reborrowed** (temporarily lent out) and you cannot use it until the reborrow ends. This is what keeps “exactly one writer” true even as the reference travels through function calls. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Forgetting `mut` on the binding [Section titled “Pitfall 1: Forgetting mut on the binding”](#pitfall-1-forgetting-mut-on-the-binding) You cannot take a `&mut` to a value bound with plain `let`. ```rust 1 fn main() { 2 let config = String::from("debug=false"); 3 let r = &mut config; // does not compile (error[E0596]) 4 r.push_str(";verbose=true"); 5 println!("{}", config); 6 } ``` Real compiler output: ```text 1 error[E0596]: cannot borrow `config` as mutable, as it is not declared as mutable 2 --> src/main.rs:3:13 3 | 4 3 | let r = &mut config; // cannot borrow immutable binding as mutable 5 | ^^^^^^^^^^^ cannot borrow as mutable 6 | 7 help: consider changing this to be mutable 8 | 9 2 | let mut config = String::from("debug=false"); 10 | +++ ``` **Fix:** change `let config` to `let mut config`. The compiler even tells you exactly that. ### Pitfall 2: Holding a shared borrow, then trying to mutate [Section titled “Pitfall 2: Holding a shared borrow, then trying to mutate”](#pitfall-2-holding-a-shared-borrow-then-trying-to-mutate) This is the iterator-invalidation bug from the intro, but it also bites in simpler code: ```rust 1 fn main() { 2 let mut items = vec![1, 2, 3]; 3 4 let shared = &items; // shared (immutable) borrow 5 items.push(4); // does not compile (error[E0502]) 6 println!("{:?}", shared); // shared borrow used here, so it is still live 7 } ``` Real compiler output: ```text 1 error[E0502]: cannot borrow `items` as mutable because it is also borrowed as immutable 2 --> src/main.rs:5:5 3 | 4 4 | let shared = &items; // shared (immutable) borrow 5 | ------ immutable borrow occurs here 6 5 | items.push(4); // mutable borrow while shared borrow is live 7 | ^^^^^^^^^^^^^ mutable borrow occurs here 8 6 | println!("{:?}", shared); // shared borrow used here 9 | ------ immutable borrow later used here ``` **Fix:** finish using `shared` *before* the mutation (NLL will then let the borrow end), or take a fresh `&items` after the `push`. ### Pitfall 3: Two `&mut` to the same value [Section titled “Pitfall 3: Two \&mut to the same value”](#pitfall-3-two-mut-to-the-same-value) Expecting JavaScript-style aliasing, a newcomer might hand a function the same mutable reference twice, or split out two mutable borrows. The borrow checker stops the second one (`E0499`, shown in the Detailed Explanation). **Fix:** restructure so only one `&mut` is live at a time, or — for genuinely disjoint parts of a collection — use `split_at_mut` (see Best Practices). ### Pitfall 4: Assuming `*` is optional everywhere [Section titled “Pitfall 4: Assuming \* is optional everywhere”](#pitfall-4-assuming--is-optional-everywhere) Method calls auto-dereference, so `r.push(4)` works. But plain operators do not: writing `r += 1` when `r: &mut i32` fails because you are trying to add to the reference, not the value. You must write `*r += 1`. The rule of thumb: **use `*` whenever you read or assign the value itself** rather than calling a method on it. *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### Keep mutable borrows as short as possible [Section titled “Keep mutable borrows as short as possible”](#keep-mutable-borrows-as-short-as-possible) Because a `&mut` blocks all other access, the idiom is to take it, do the mutation, and let it end immediately. NLL rewards this: the sooner the reference’s last use, the sooner the value is free again. ```rust 1 fn main() { 2 let mut log = Vec::new(); 3 log.push("started"); // implicit short-lived &mut log 4 let len = log.len(); // read access after the &mut already ended 5 println!("{len} entries: {log:?}"); 6 } ``` ### Use `iter_mut()` to mutate every element of a collection [Section titled “Use iter\_mut() to mutate every element of a collection”](#use-iter_mut-to-mutate-every-element-of-a-collection) Do not index in a loop with manual bookkeeping; ask the collection for mutable references to its elements. ```rust 1 fn restock_all(inventory: &mut [u32], extra: u32) { 2 for stock in inventory.iter_mut() { // each `stock` is &mut u32 3 *stock += extra; 4 } 5 } 6 7 fn main() { 8 let mut levels = [10, 20, 30]; 9 restock_all(&mut levels, 5); 10 println!("{levels:?}"); // [15, 25, 35] 11 } ``` ### Reach for `split_at_mut` when you truly need two `&mut` into one collection [Section titled “Reach for split\_at\_mut when you truly need two \&mut into one collection”](#reach-for-split_at_mut-when-you-truly-need-two-mut-into-one-collection) The XOR rule forbids two `&mut` to the *same* value, but two `&mut` to *disjoint* parts are perfectly safe. The standard library exposes this with `split_at_mut`, which hands you two non-overlapping mutable slices: ```rust 1 fn main() { 2 let mut data = vec![1, 2, 3, 4, 5, 6]; 3 4 // Split into two non-overlapping mutable slices. 5 let (left, right) = data.split_at_mut(3); 6 7 left[0] += 100; 8 right[0] += 200; 9 10 println!("{:?}", data); // [101, 2, 3, 204, 5, 6] 11 } ``` ```text 1 [101, 2, 3, 204, 5, 6] ``` ### Prefer `std::mem::swap` / `take` / `replace` over fighting the borrow checker [Section titled “Prefer std::mem::swap / take / replace over fighting the borrow checker”](#prefer-stdmemswap--take--replace-over-fighting-the-borrow-checker) When you need to move a value *out* through a `&mut` (e.g. to reset a field), these helpers do it without a second borrow: ```rust 1 fn main() { 2 let mut a = String::from("first"); 3 let mut b = String::from("second"); 4 std::mem::swap(&mut a, &mut b); 5 println!("a={a}, b={b}"); // a=second, b=first 6 7 // `take` leaves Default::default() behind and returns the old value. 8 let mut owned = vec![1, 2, 3]; 9 let stolen = std::mem::take(&mut owned); 10 println!("stolen={stolen:?}, owned={owned:?}"); // stolen=[1, 2, 3], owned=[] 11 } ``` ```text 1 a=second, b=first 2 stolen=[1, 2, 3], owned=[] ``` ### Expose mutation through `&mut self` methods, not public fields [Section titled “Expose mutation through \&mut self methods, not public fields”](#expose-mutation-through-mut-self-methods-not-public-fields) Returning a `&mut` to internal state ties that reference’s lifetime to the borrow of `self`, so the XOR rule still protects your invariants: ```rust 1 struct Config { 2 retries: u32, 3 } 4 5 impl Config { 6 fn retries_mut(&mut self) -> &mut u32 { 7 &mut self.retries 8 } 9 } 10 11 fn main() { 12 let mut config = Config { retries: 3 }; 13 *config.retries_mut() += 2; 14 println!("retries = {}", config.retries); // retries = 5 15 } ``` *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small inventory service. Notice how each mutation goes through a clearly scoped mutable borrow: `apply_sale` borrows one product, `restock_all` borrows the whole slice, and the final read-only loop only runs once all mutable borrows have ended. ```rust 1 #[derive(Debug)] 2 struct Product { 3 name: String, 4 stock: u32, 5 price_cents: u64, 6 } 7 8 /// Applies a sale to a single product: drops the price by `percent` 9 /// and reserves `qty` units. Mutates in place through `&mut Product`. 10 fn apply_sale(product: &mut Product, percent: u64, qty: u32) { 11 product.price_cents -= product.price_cents * percent / 100; 12 product.stock = product.stock.saturating_sub(qty); 13 } 14 15 /// Bulk update: one mutable borrow of the whole slice, mutating each element. 16 fn restock_all(inventory: &mut [Product], extra: u32) { 17 for product in inventory.iter_mut() { 18 product.stock += extra; 19 } 20 } 21 22 fn main() { 23 let mut inventory = vec![ 24 Product { name: "Keyboard".into(), stock: 12, price_cents: 7999 }, 25 Product { name: "Mouse".into(), stock: 30, price_cents: 2999 }, 26 ]; 27 28 // One mutable borrow at a time, scoped to the call: 29 apply_sale(&mut inventory[0], 25, 2); 30 31 // A mutable borrow of the whole slice for a bulk update: 32 restock_all(&mut inventory, 5); 33 34 // Read-only pass: no &mut is alive here, so a shared borrow is fine. 35 for product in &inventory { 36 println!( 37 "{:<10} stock={:>3} price=${:.2}", 38 product.name, 39 product.stock, 40 product.price_cents as f64 / 100.0 41 ); 42 } 43 } ``` ```text 1 Keyboard stock= 15 price=$60.00 2 Mouse stock= 35 price=$29.99 ``` In TypeScript you would write the equivalent freely, with several aliases all able to mutate `inventory` concurrently. The Rust version reads almost the same, but the compiler has verified that no two parts of the program can mutate the same product at the same time, and it cost you nothing at runtime. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — References and Borrowing](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html) * [The Rust Book — Fearless Concurrency](https://doc.rust-lang.org/book/ch16-00-concurrency.html) * [The Rust Reference — Lifetime and Borrow Rules](https://doc.rust-lang.org/reference/expressions.html#mutability) * [`slice::split_at_mut`](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut) * [`std::mem::swap`](https://doc.rust-lang.org/std/mem/fn.swap.html), [`std::mem::take`](https://doc.rust-lang.org/std/mem/fn.take.html), [`std::mem::replace`](https://doc.rust-lang.org/std/mem/fn.replace.html) ### Related Topics in This Guide [Section titled “Related Topics in This Guide”](#related-topics-in-this-guide) * [Borrowing](/05-ownership/02-borrowing/): shared `&` references, where mutable references build from * [Ownership Rules](/05-ownership/01-ownership-rules/): the rules that mutable references operate within * [Stack and Heap](/05-ownership/00-stack-heap/): what `&mut` actually points at * [Lifetimes](/05-ownership/04-lifetimes/) and [Lifetime Elision](/05-ownership/05-lifetime-elision/) — how long a reference is valid * [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/): moving values vs borrowing them * [Reference Counting](/05-ownership/07-reference-counting/) — shared *ownership* (`Rc`/`Arc`) when one owner is not enough * [Variables and Mutability](/02-basics/00-variables/): the `mut` keyword on bindings * [Data Structures](/06-data-structures/) — `&mut self` methods on structs and enums *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Increment in Place [Section titled “Exercise 1: Increment in Place”](#exercise-1-increment-in-place) **Difficulty:** Easy **Objective:** Write a function that adds 1 to every element of a slice through a mutable reference. **Instructions:** Complete `increment_all` so that it mutates the caller’s data in place (no allocation, no return value). ```rust 1 fn increment_all(values: &mut [i32]) { 2 // TODO: add 1 to every element 3 } 4 5 fn main() { 6 let mut nums = vec![1, 2, 3]; 7 increment_all(&mut nums); 8 println!("{nums:?}"); // expected: [2, 3, 4] 9 } ``` Solution ```rust 1 fn increment_all(values: &mut [i32]) { 2 for v in values.iter_mut() { 3 *v += 1; // `v` is &mut i32, so dereference to write 4 } 5 } 6 7 fn main() { 8 let mut nums = vec![1, 2, 3]; 9 increment_all(&mut nums); 10 println!("{nums:?}"); // [2, 3, 4] 11 } ``` Output: ```text 1 [2, 3, 4] ``` ### Exercise 2: Normalize a Vector [Section titled “Exercise 2: Normalize a Vector”](#exercise-2-normalize-a-vector) **Difficulty:** Medium **Objective:** Mutate a vector of `f64` in place so every value is divided by the maximum, without holding two conflicting borrows. **Instructions:** Implement `normalize`. First compute the maximum (a read-only pass), then scale every element (a mutable pass). The trick is to make sure the read borrow has fully ended before the write borrow begins; non-lexical lifetimes will allow this if you compute `max` into its own variable first. ```rust 1 fn normalize(values: &mut Vec) { 2 // TODO: divide every element by the maximum value 3 } 4 5 fn main() { 6 let mut samples = vec![2.0, 4.0, 8.0]; 7 normalize(&mut samples); 8 println!("{samples:?}"); // expected: [0.25, 0.5, 1.0] 9 } ``` Solution ```rust 1 fn normalize(values: &mut Vec) { 2 // Read pass: the shared borrow inside `iter()` ends when `max` is computed. 3 let max = values.iter().cloned().fold(f64::MIN, f64::max); 4 if max == 0.0 { 5 return; // avoid dividing by zero 6 } 7 // Write pass: now the &mut is exclusive. 8 for v in values.iter_mut() { 9 *v /= max; 10 } 11 } 12 13 fn main() { 14 let mut samples = vec![2.0, 4.0, 8.0]; 15 normalize(&mut samples); 16 println!("{samples:?}"); // [0.25, 0.5, 1.0] 17 } ``` Output: ```text 1 [0.25, 0.5, 1.0] ``` ### Exercise 3: A Counter with `&mut self` [Section titled “Exercise 3: A Counter with \&mut self”](#exercise-3-a-counter-with-mut-self) **Difficulty:** Medium **Objective:** Build a `Counter` struct whose `increment` method mutates the counter through `&mut self` and returns the new value. **Instructions:** Implement `Counter::new` and `Counter::increment`. Calling `increment` twice on a fresh counter should print `1` then `2`. Think about why the binding in `main` must be `mut`. ```rust 1 struct Counter { 2 count: u32, 3 } 4 5 impl Counter { 6 fn new() -> Self { 7 // TODO 8 } 9 10 fn increment(&mut self) -> u32 { 11 // TODO: bump the count and return the new value 12 } 13 } 14 15 fn main() { 16 let mut c = Counter::new(); 17 println!("{}", c.increment()); // 1 18 println!("{}", c.increment()); // 2 19 } ``` Solution ```rust 1 struct Counter { 2 count: u32, 3 } 4 5 impl Counter { 6 fn new() -> Self { 7 Counter { count: 0 } 8 } 9 10 fn increment(&mut self) -> u32 { 11 self.count += 1; 12 self.count 13 } 14 } 15 16 fn main() { 17 let mut c = Counter::new(); // `mut` is required to call a &mut self method 18 println!("{}", c.increment()); // 1 19 println!("{}", c.increment()); // 2 20 } ``` Output: ```text 1 1 2 2 ``` `main`’s binding must be `let mut c` because `increment` takes `&mut self`: calling it implicitly creates a mutable borrow of `c`, which is only allowed on a mutable binding. # Rust Lifetimes Explained > Rust lifetimes ('a) prove every reference stays valid without a garbage collector. Learn what TypeScript's GC hides and when to annotate functions and structs. In TypeScript and JavaScript, the garbage collector quietly keeps any object alive for as long as *something* still points at it. Rust has no garbage collector, so the compiler must instead **prove** that every reference points at data that is still alive. **Lifetimes** are the labels the compiler uses to track and relate how long borrowed data stays valid, and `'a` annotations are how you spell those relationships out when the compiler cannot figure them out alone. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) A **lifetime** is a region of code during which a reference is guaranteed to be valid. The borrow checker (introduced in [Borrowing](/05-ownership/02-borrowing/) and [Mutable References](/05-ownership/03-mutable-references/)) already tracks lifetimes for *every* reference automatically; **lifetime annotations** like `'a` are simply the syntax you use to describe the *relationship between* the lifetimes of inputs and outputs when a function or struct hands out a reference. They never change how long anything actually lives; they are a contract the compiler verifies, not a runtime mechanism. For a TypeScript developer, the closest mental model is “type-level documentation that the borrow points back into one of the arguments,” enforced at compile time. > **Note:** Lifetimes are the part of Rust that feels most alien at first. The good news: thanks to [lifetime elision](/05-ownership/05-lifetime-elision/), you will write explicit `'a` annotations far less often than you fear. This page explains what they *mean* so the elided cases make sense. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) In TypeScript, a function that returns “whichever string is longer” is trivial, and a class that holds a reference to a shared object is just as easy. Nothing forces you to think about how long the underlying data lives: strings.ts ```typescript 1 // Returns whichever argument is longer. The returned string is the 2 // SAME object as one of the inputs (strings are immutable, but the point 3 // stands for objects: no copy is made). 4 function longest(x: string, y: string): string { 5 return x.length > y.length ? x : y; 6 } 7 8 // A class that holds a reference to data it does NOT own. 9 class Parser { 10 private position = 0; 11 12 // `source` is just a reference; the GC keeps it alive as long as 13 // this Parser (or anything else) can still reach it. 14 constructor(private source: string) {} 15 16 nextWord(): string | null { 17 const remaining = this.source.slice(this.position); 18 const match = remaining.match(/\S+/); 19 if (!match) return null; 20 this.position += (match.index ?? 0) + match[0].length; 21 return match[0]; 22 } 23 } 24 25 const result = longest("a long string", "short"); 26 console.log(result); // "a long string" 27 28 const parser = new Parser(" hello world "); 29 let word: string | null; 30 while ((word = parser.nextWord()) !== null) { 31 console.log(word); // "hello", then "world" 32 } ``` **What the runtime does for you here:** * `longest` returns one of its arguments by reference. JavaScript does not care which — the GC keeps both alive until nobody references them. * `Parser` stores `source`. Even if the original variable that created the string goes out of scope, the GC keeps the string alive because the `Parser` still points at it. This “the GC will sort it out” freedom is exactly what Rust removes, and lifetimes are what replace it. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) The same two patterns in Rust require us to *name* the lifetime relationship with `'a`: ```rust 1 // A function that returns one of its borrowed inputs must declare a 2 // lifetime: the output borrows for as long as BOTH inputs are valid. 3 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { 4 if x.len() > y.len() { x } else { y } 5 } 6 7 // A struct that holds a reference must declare the lifetime of that reference. 8 // `Parser<'a>` means "this Parser cannot outlive the str it borrows". 9 struct Parser<'a> { 10 source: &'a str, 11 position: usize, 12 } 13 14 impl<'a> Parser<'a> { 15 fn new(source: &'a str) -> Parser<'a> { 16 Parser { source, position: 0 } 17 } 18 19 // The returned slice borrows from `self.source`, so it shares lifetime 'a. 20 fn next_word(&mut self) -> Option<&'a str> { 21 let remaining = &self.source[self.position..]; 22 let start = remaining.find(|c: char| !c.is_whitespace())?; 23 let rest = &remaining[start..]; 24 let end = rest.find(char::is_whitespace).unwrap_or(rest.len()); 25 self.position += start + end; 26 Some(&rest[..end]) 27 } 28 } 29 30 fn main() { 31 let result = longest("a long string", "short"); 32 println!("{result}"); // a long string 33 34 let text = String::from(" hello world "); 35 let mut parser = Parser::new(&text); 36 while let Some(word) = parser.next_word() { 37 println!("{word:?}"); // "hello", then "world" 38 } 39 } ``` Running it prints exactly what the TypeScript version did: ```text 1 a long string 2 "hello" 3 "world" ``` The `<'a>` after the function or struct name introduces a **generic lifetime parameter**, just like `` introduces a generic type parameter. The annotations `&'a str` then say “this reference lives for the region `'a`,” and reusing the same `'a` across multiple spots is how you tie them together. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Why does Rust need lifetimes at all? [Section titled “Why does Rust need lifetimes at all?”](#why-does-rust-need-lifetimes-at-all) Every reference in Rust has a lifetime: the span during which it is valid. Usually the compiler infers it silently (you have already written dozens of references without annotations). Annotations become necessary only when the compiler **cannot tell, from the signature alone, how the lifetimes relate**. Consider `longest` without any annotation: ```rust 1 fn longest(x: &str, y: &str) -> &str { // does not compile (error[E0106]) 2 if x.len() > y.len() { x } else { y } 3 } ``` The compiler rejects this. The returned `&str` borrows from *something*, but which one — `x` or `y`? The body decides at runtime based on `len()`, so the signature is genuinely ambiguous. The real error is: ```text 1 error[E0106]: missing lifetime specifier 2 --> src/main.rs:1:33 3 | 4 1 | fn longest(x: &str, y: &str) -> &str { 5 | ---- ---- ^ expected named lifetime parameter 6 | 7 = help: this function's return type contains a borrowed value, but the 8 signature does not say whether it is borrowed from `x` or `y` 9 help: consider introducing a named lifetime parameter 10 | 11 1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { 12 | ++++ ++ ++ ++ ``` The compiler even suggests the fix. By writing `<'a>` and tagging `x`, `y`, and the return with the same `'a`, you promise: *the returned reference is valid only for as long as **both** `x` and `y` are valid.* That is enough information to check every call site. ### Annotations describe relationships, not durations [Section titled “Annotations describe relationships, not durations”](#annotations-describe-relationships-not-durations) This is the single most important idea, and the one that trips up newcomers most: > **`'a` does not make anything live longer or shorter. It only describes how the lifetime of one reference relates to another.** `<'a>` is generic. At each call site, the compiler picks the *smallest concrete region* that satisfies all the `'a` constraints, typically the overlap of all the inputs’ lifetimes. You are not choosing a duration; you are stating a rule the borrow checker must respect. ### How the constraint is enforced at the call site [Section titled “How the constraint is enforced at the call site”](#how-the-constraint-is-enforced-at-the-call-site) The annotation pays off when references have *different* scopes. This compiles, because both inputs and the use of `result` all overlap: ```rust 1 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { 2 if x.len() > y.len() { x } else { y } 3 } 4 5 fn main() { 6 let s1 = String::from("a long string"); 7 let result; 8 { 9 let s2 = String::from("short"); 10 result = longest(s1.as_str(), s2.as_str()); 11 println!("The longest string is: {result}"); // used INSIDE the inner scope 12 } 13 } ``` ```text 1 The longest string is: a long string ``` But move the `println!` *out* of the inner block, so `result` is used after `s2` has been dropped, and the borrow checker stops you, because the return *might* have borrowed from `s2`: ```rust 1 fn main() { 2 let s1 = String::from("a long string"); 3 let result; 4 { 5 let s2 = String::from("short"); 6 result = longest(s1.as_str(), s2.as_str()); 7 } // s2 dropped here 8 println!("The longest string is: {result}"); // does not compile (error[E0597]) 9 } ``` The real compiler error is precise about the cause: ```text 1 error[E0597]: `s2` does not live long enough 2 --> src/main.rs:10:39 3 | 4 9 | let s2 = String::from("short"); 5 | -- binding `s2` declared here 6 10 | result = longest(s1.as_str(), s2.as_str()); 7 | ^^ borrowed value does not live long enough 8 11 | } // s2 dropped here 9 | - `s2` dropped here while still borrowed 10 12 | println!("The longest string is: {result}"); // does not compile (error[E0597]) 11 | ------ borrow later used here ``` In TypeScript this code is perfectly fine: the GC keeps `s2`’s string alive because `result` still references it. Rust has no GC, so it forbids the dangling reference *at compile time*. This is the same dangling-prevention guarantee from [Borrowing](/05-ownership/02-borrowing/), now lifted to function boundaries. ### Lifetimes in struct definitions [Section titled “Lifetimes in struct definitions”](#lifetimes-in-struct-definitions) When a struct field is a reference, the struct must be parameterized by that reference’s lifetime. `Parser<'a>` means “a `Parser` that borrows a `str` for the region `'a`, and therefore the `Parser` itself cannot outlive that `str`.” ```rust 1 struct Highlight<'a> { 2 text: &'a str, 3 } ``` If you let the borrowed data drop while the struct still holds the reference, the same `E0597` fires: ```rust 1 struct Highlight<'a> { 2 text: &'a str, 3 } 4 5 fn main() { 6 let highlight; 7 { 8 let sentence = String::from("important note"); 9 highlight = Highlight { text: &sentence }; // does not compile (error[E0597]) 10 } // sentence dropped here while still borrowed 11 println!("{}", highlight.text); 12 } ``` ```text 1 error[E0597]: `sentence` does not live long enough 2 --> src/main.rs:9:39 3 | 4 8 | let sentence = String::from("important note"); 5 | -------- binding `sentence` declared here 6 9 | highlight = Highlight { text: &sentence }; 7 | ^^^^^^^^^ borrowed value does not live long enough 8 10 | } 9 | - `sentence` dropped here while still borrowed 10 11 | println!("{}", highlight.text); 11 | -------------- borrow later used here ``` > **Tip:** If a struct holding a reference starts causing lifetime headaches, ask whether it should **own** its data instead (`text: String`). Owning is the simpler default; borrowing structs are an optimization for short-lived “view” types like parsers and iterators. See [Move, Copy, Clone](/05-ownership/06-move-copy-clone/) and [`Rc`/`Arc`](/05-ownership/07-reference-counting/) for the owning alternatives. ### Lifetimes on `impl` blocks [Section titled “Lifetimes on impl blocks”](#lifetimes-on-impl-blocks) The `impl<'a> Parser<'a>` line declares `'a` for the impl and then uses it on the type. The `<'a>` after `impl` is the *declaration*; the `<'a>` after `Parser` is the *use*, exactly mirroring how generic types work with `impl Wrapper`. ### Distinct lifetimes when inputs are unrelated [Section titled “Distinct lifetimes when inputs are unrelated”](#distinct-lifetimes-when-inputs-are-unrelated) You do **not** have to give every reference the same `'a`. If the output only ever borrows from *one* argument, give the others their own lifetime so callers are not over-constrained: ```rust 1 // The return is only ever borrowed from `text`, never from `prefix`, 2 // so they get independent lifetimes 'a and 'b. 3 fn strip_prefix<'a, 'b>(text: &'a str, prefix: &'b str) -> &'a str { 4 if let Some(rest) = text.strip_prefix(prefix) { 5 rest 6 } else { 7 text 8 } 9 } 10 11 fn main() { 12 let path = String::from("/api/users"); 13 let stripped; 14 { 15 let prefix = String::from("/api"); // shorter-lived than `path` 16 stripped = strip_prefix(&path, &prefix); 17 } // `prefix` dropped here — fine, the output never borrowed from it 18 println!("{stripped}"); // /users 19 } ``` ```text 1 /users ``` Had we forced `prefix: &'a str`, the compiler would have demanded `prefix` live as long as the output — an unnecessary restriction that would reject this perfectly safe program. **Use the loosest annotations that are still correct.** ### The `'static` lifetime [Section titled “The 'static lifetime”](#the-static-lifetime) `'static` is a special, built-in lifetime meaning “valid for the entire program.” Every string literal has type `&'static str` because the text is baked into the binary: ```rust 1 fn app_name() -> &'static str { 2 "ts-to-rust" 3 } ``` > **Warning:** Do not reach for `'static` to silence a lifetime error — it almost always makes the problem worse by demanding data live *forever*. The fix is usually to relate lifetimes correctly or to own the data, not to claim `'static`. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript / JavaScript | Rust | | ---------------------------------- | ------------------------------------------ | -------------------------------------------------------- | | Keeping referenced data alive | Garbage collector, at runtime | Lifetimes, proven at compile time | | Returning “one of the arguments” | Always fine | Needs a lifetime relating output to inputs | | A class/struct holding a reference | Free; GC tracks reachability | Struct must declare `<'a>`; cannot outlive borrowed data | | Dangling reference | Impossible (GC) but stale data is possible | Rejected by the compiler (`E0597`/`E0515`) | | Runtime cost of the mechanism | GC pauses, extra memory | **Zero**: lifetimes are erased after checking | | ”Forever” reference | Any long-lived object | `'static` | | Where you write it | Nowhere | Function signatures, struct defs, `impl` blocks | The deepest contrast: **lifetimes are a compile-time-only construct.** Like TypeScript types (which are erased before the JS runs), `'a` annotations vanish entirely after the borrow checker has done its job. There is no runtime representation of a lifetime, no overhead, and no equivalent of a GC pause. They are pure, checked documentation of an invariant the machine code already upholds. A second contrast worth internalizing: in TypeScript, a *reference is the cheap default* and copying is the thing you opt into. In Rust, the borrow checker treats every borrow as a liability it must account for, which is precisely why borrowed return values and borrowed struct fields require you to spell out the relationship. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Returning a reference to a local variable [Section titled “Pitfall 1: Returning a reference to a local variable”](#pitfall-1-returning-a-reference-to-a-local-variable) A classic mistake, and one no annotation can rescue, because the data genuinely dies: ```rust 1 fn dangling<'a>() -> &'a str { 2 let s = String::from("temporary"); 3 &s // does not compile (error[E0515]): returns a reference to data owned by the current function 4 } ``` ```text 1 error[E0515]: cannot return reference to local variable `s` 2 --> src/main.rs:3:5 3 | 4 3 | &s // does not compile (error[E0515]): returns a reference to data owned by the current function 5 | ^^ returns a reference to data owned by the current function ``` `s` is dropped when `dangling` returns, so any reference to it would dangle. The compiler is not asking for a lifetime annotation here — it is telling you the design is impossible. **Fix:** return the owned `String` instead of a reference. ### Pitfall 2: Thinking `'a` extends a value’s life [Section titled “Pitfall 2: Thinking 'a extends a value’s life”](#pitfall-2-thinking-a-extends-a-values-life) Newcomers often add `'static` or a longer-looking lifetime hoping to “keep the data around.” It does the opposite of what you imagine — it *requires* the caller to supply data that lives that long, usually producing a worse error. `'a` constrains; it never extends. The fix for “this data does not live long enough” is almost always to restructure ownership (own the data, or make the borrower’s scope shorter), not to relabel the lifetime. ### Pitfall 3: Over-constraining with a single shared lifetime [Section titled “Pitfall 3: Over-constraining with a single shared lifetime”](#pitfall-3-over-constraining-with-a-single-shared-lifetime) Reusing one `'a` for every parameter when the output only borrows from one of them forces callers to keep *all* arguments alive as long as the result. This compiles but needlessly rejects valid call sites (as in the `strip_prefix` example). Give unrelated inputs their own lifetimes. ### Pitfall 4: Forgetting the lifetime on the `impl` [Section titled “Pitfall 4: Forgetting the lifetime on the impl”](#pitfall-4-forgetting-the-lifetime-on-the-impl) Writing `impl Parser` instead of `impl<'a> Parser<'a>` fails because `Parser` is not a complete type without its lifetime parameter, just as `impl Wrapper` would fail for `Wrapper`. You must declare `<'a>` after `impl` and then apply it to the type. ### Pitfall 5: Reaching for annotations the compiler does not want [Section titled “Pitfall 5: Reaching for annotations the compiler does not want”](#pitfall-5-reaching-for-annotations-the-compiler-does-not-want) Because of [lifetime elision](/05-ownership/05-lifetime-elision/), many single-reference functions need *no* annotation. Adding `<'a>` everywhere is noise; Clippy’s `needless_lifetimes` lint will flag the redundant ones. Write annotations only when the compiler asks for them or when elision would pick the wrong relationship. *** ## Best Practices [Section titled “Best Practices”](#best-practices) * **Let elision do the work.** Annotate only when the compiler demands it. See [Lifetime Elision](/05-ownership/05-lifetime-elision/) for the three rules that cover the common cases. * **Prefer owning over borrowing in stored data.** If a struct does not *need* to borrow, store `String`/`Vec` instead of `&str`/`&[T]`. Lifetime-parameterized structs ripple their `'a` through every function that touches them. * **Use the loosest correct lifetimes.** Give unrelated references distinct lifetime parameters so you do not over-constrain callers. * **Name lifetimes meaningfully when there are several.** `'a` is conventional for one; for multiple, descriptive names like `<'input, 'config>` read better than `<'a, 'b, 'c>`. * **Reach for `'static` only when the data truly is static** (literals, leaked allocations, lazily-initialized globals), never as a quick fix. * **When fighting the borrow checker, change the design, not the annotation.** Shorter borrows, cloning ([Move, Copy, Clone](/05-ownership/06-move-copy-clone/)), or shared ownership via [`Rc`/`Arc`](/05-ownership/07-reference-counting/) are usually the real answer. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A log-line parser is a realistic place where borrowing (and therefore lifetimes) earns its keep: parsing a `&str` into structured fields that are *slices of the original buffer* avoids allocating new strings for every line, important in a hot logging path. ```rust 1 /// A parsed log line whose fields borrow directly from the original buffer. 2 /// `LogLine<'a>` cannot outlive the string it was parsed from. 3 #[derive(Debug)] 4 struct LogLine<'a> { 5 level: &'a str, 6 message: &'a str, 7 } 8 9 /// Parse "LEVEL: message" into borrowed slices. The `'_` in the return type 10 /// is the elided lifetime tied to `line` (covered in lifetime-elision.md). 11 fn parse_log_line(line: &str) -> Option> { 12 let (level, message) = line.split_once(": ")?; 13 Some(LogLine { 14 level, 15 message: message.trim(), 16 }) 17 } 18 19 fn main() { 20 let raw = String::from("WARN: disk almost full"); 21 let parsed = parse_log_line(&raw).expect("well-formed line"); 22 23 println!("{parsed:?}"); 24 assert_eq!(parsed.level, "WARN"); 25 assert_eq!(parsed.message, "disk almost full"); 26 println!("ok"); 27 } ``` ```text 1 LogLine { level: "WARN", message: "disk almost full" } 2 ok ``` No heap allocation happens during parsing: `level` and `message` are *windows into* `raw`. The `LogLine<'a>` annotation is what lets the compiler guarantee those windows can never outlive `raw`. In TypeScript you would get this “view” behavior for free (substrings reference the same backing data, GC permitting), but you would also have zero compile-time protection against using a `LogLine` after its source buffer became unreachable. Rust makes the same zero-copy design *and* makes the dangling case a compile error. > **Tip:** When a borrowing parser like this becomes awkward to thread through your program, switch the fields to owned `String`s. You trade a small allocation per line for freedom from lifetime parameters, often the right call outside of hot paths. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Validating References with Lifetimes](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) * [The Rust Book — Lifetime Annotations in Struct Definitions](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotations-in-struct-definitions) * [Rust by Example — Lifetimes](https://doc.rust-lang.org/rust-by-example/scope/lifetime.html) * [Rust Reference — Lifetime elision](https://doc.rust-lang.org/reference/lifetime-elision.html) * [The `'static` lifetime (std docs)](https://doc.rust-lang.org/std/keyword.static.html) * [Error index — E0106](https://doc.rust-lang.org/error_codes/E0106.html), [E0515](https://doc.rust-lang.org/error_codes/E0515.html), [E0597](https://doc.rust-lang.org/error_codes/E0597.html) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Borrowing](/05-ownership/02-borrowing/): references `&`, shared borrows, and dangling prevention (the foundation for lifetimes) * [Mutable References](/05-ownership/03-mutable-references/): `&mut`, the borrow rules, and non-lexical lifetimes * [Lifetime Elision](/05-ownership/05-lifetime-elision/) — the three rules that let you *omit* most `'a` annotations * [Ownership Rules](/05-ownership/01-ownership-rules/): owners, moves, and scope-based drops that lifetimes build on * [Move, Copy, Clone](/05-ownership/06-move-copy-clone/): owning your data as an alternative to borrowing it * [Reference Counting (`Rc`/`Arc`)](/05-ownership/07-reference-counting/) — shared ownership when a single lifetime will not do * [Stack vs Heap](/05-ownership/00-stack-heap/): where the borrowed data actually lives * [Functions — Parameters](/03-functions/01-parameters/): why slice and `&str` parameters are borrowed * [Basics — Types](/02-basics/01-types/) — the underlying types (`str`, slices) you borrow * [Data Structures](/06-data-structures/): structs and enums that may carry lifetime parameters * [Ownership — Section Overview](/05-ownership/) *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Your first explicit lifetime [Section titled “Exercise 1: Your first explicit lifetime”](#exercise-1-your-first-explicit-lifetime) **Difficulty:** Easy **Objective:** Write a function that returns a borrowed slice of one of its inputs, declaring the lifetime relationship yourself. **Instructions:** Implement `first_word` so it returns the first whitespace-delimited word of `s` as a `&str` borrowed from `s`. If there is no space, return the whole string. Make `first_word("hello world")` return `"hello"` and `first_word("single")` return `"single"`. Write the lifetime annotation explicitly (do not rely on elision for this exercise). ```rust 1 fn first_word<'a>(s: &'a str) -> &'a str { 2 // TODO: find the first space; return the slice before it, or all of `s`. 3 todo!() 4 } ``` Solution ```rust 1 fn first_word<'a>(s: &'a str) -> &'a str { 2 match s.find(' ') { 3 Some(i) => &s[..i], 4 None => s, 5 } 6 } 7 8 fn main() { 9 assert_eq!(first_word("hello world"), "hello"); 10 assert_eq!(first_word("single"), "single"); 11 println!("ex1 ok"); 12 } ``` The single `'a` ties the output slice to the input string, so the compiler knows the returned `&str` cannot outlive `s`. (Thanks to elision, you could drop the annotations entirely here, but writing them out shows what the compiler infers.) ### Exercise 2: A struct that borrows [Section titled “Exercise 2: A struct that borrows”](#exercise-2-a-struct-that-borrows) **Difficulty:** Medium **Objective:** Define a struct that holds a reference, parameterized by a lifetime, and add a method that returns borrows tied to that same lifetime. **Instructions:** Define `Tokenizer<'a>` holding an `input: &'a str`. Add `new(input: &'a str) -> Self` and a method `tokens(&self) -> Vec<&'a str>` that splits `input` on commas and trims whitespace from each token. For input `"a, b ,c"`, `tokens()` must return `vec!["a", "b", "c"]`. Solution ```rust 1 struct Tokenizer<'a> { 2 input: &'a str, 3 } 4 5 impl<'a> Tokenizer<'a> { 6 fn new(input: &'a str) -> Self { 7 Tokenizer { input } 8 } 9 10 fn tokens(&self) -> Vec<&'a str> { 11 self.input.split(',').map(|t| t.trim()).collect() 12 } 13 } 14 15 fn main() { 16 let data = String::from("a, b ,c"); 17 let tk = Tokenizer::new(&data); 18 assert_eq!(tk.tokens(), vec!["a", "b", "c"]); 19 println!("ex2 ok"); 20 } ``` Note that `tokens` returns `Vec<&'a str>`, not `Vec<&str>` tied to `&self`: each token borrows from `self.input` (lifetime `'a`), so the returned slices are valid for as long as the original `data` string lives, independent of how long the `Tokenizer` itself does. ### Exercise 3: Borrowing from a slice and handling emptiness [Section titled “Exercise 3: Borrowing from a slice and handling emptiness”](#exercise-3-borrowing-from-a-slice-and-handling-emptiness) **Difficulty:** Medium–Hard **Objective:** Return a reference into a borrowed collection, relating the output lifetime to the slice, while handling the empty case without panicking. **Instructions:** Implement `longest_in(items: &[String]) -> Option<&str>` that returns the longest string in the slice as a borrowed `&str`, or `None` if the slice is empty. The returned reference must borrow from `items` (so it stays valid as long as `items` does). `longest_in(&["a".into(), "ccc".into(), "bb".into()])` should be `Some("ccc")`; an empty slice should give `None`. Solution ```rust 1 fn longest_in<'a>(items: &'a [String]) -> Option<&'a str> { 2 items 3 .iter() 4 .max_by_key(|s| s.len()) 5 .map(|s| s.as_str()) 6 } 7 8 fn main() { 9 let words = vec![ 10 String::from("a"), 11 String::from("ccc"), 12 String::from("bb"), 13 ]; 14 assert_eq!(longest_in(&words), Some("ccc")); 15 16 let empty: Vec = Vec::new(); 17 assert_eq!(longest_in(&empty), None); 18 println!("ex3 ok"); 19 } ``` `max_by_key` returns `Option<&String>` borrowed from the slice; `.map(|s| s.as_str())` converts the inner `&String` to a `&str` *without* changing the lifetime, so the result is `Option<&'a str>`. The `None` arm handles the empty slice: there is no panic and no need for a sentinel value, mirroring the TypeScript `string | null` return but enforced by the type system. As in Exercise 1, the `'a` here is elidable: `fn longest_in(items: &[String]) -> Option<&str>` compiles identically and is what Clippy’s `needless_lifetimes` lint will nudge you toward. It is written explicitly above to keep the input→output borrow relationship visible while you are still learning to read it. # Lifetime Elision > Rust's three lifetime elision rules let you omit 'a annotations the compiler can infer. See exactly when they apply and why TypeScript never needs them. In the [previous topic](/05-ownership/04-lifetimes/) you saw that references in function signatures sometimes need explicit lifetime annotations like `'a`. But you’ve also written (and will write) hundreds of functions that take and return references *without* a single `'a` in sight. That’s not magic. It’s a small, deterministic set of rules the compiler applies for you called **lifetime elision**. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) **Lifetime elision** is a set of three rules the Rust compiler uses to *infer* lifetimes in function and method signatures so you don’t have to write them by hand. When the rules fully determine every lifetime, you can omit the annotations entirely; when they don’t, the compiler asks you to be explicit. Understanding the rules tells you *exactly* when annotations are required and when they’re just noise: the difference between fighting the borrow checker and ignoring it. **The point:** Elision is not inference of *behavior*; it’s a fixed, predictable shorthand. The same rules run every time, and you can apply them in your head. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) TypeScript has no concept of lifetimes at all, so there’s nothing to elide. The garbage collector keeps any object alive as long as *something* references it, and the type system says nothing about how long a returned reference stays valid: ```typescript 1 // TypeScript: a function that returns part of its input. 2 // The returned string and the input are completely independent 3 // objects as far as the type system is concerned. 4 function firstWord(sentence: string): string { 5 const spaceIndex = sentence.indexOf(" "); 6 return spaceIndex === -1 ? sentence : sentence.slice(0, spaceIndex); 7 } 8 9 class Headers { 10 constructor(private raw: string) {} 11 12 // Returns a substring of `this.raw`. TypeScript tracks the *type* 13 // (string), but nothing about how the result relates to `this`. 14 get(name: string): string | undefined { 15 for (const line of this.raw.split("\n")) { 16 const [key, value] = line.split(": "); 17 if (key.toLowerCase() === name.toLowerCase()) return value; 18 } 19 return undefined; 20 } 21 } 22 23 const headers = new Headers("Host: example.com\nAccept: */*"); 24 console.log(headers.get("host")); // example.com ``` > **Note:** In JavaScript, `slice` returns a brand-new string, so there is no “borrowing” relationship to track. The whole problem lifetime elision solves (*which input does this returned reference borrow from?*) simply does not exist in a garbage-collected language. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) Here is the same code in Rust. Notice that **none** of these signatures carry a `'a` annotation, even though they take and return references. The elision rules fill them in: ```rust 1 // One reference in, one reference out: the compiler infers the lifetime. 2 fn first_word(s: &str) -> &str { 3 let bytes = s.as_bytes(); 4 for (i, &byte) in bytes.iter().enumerate() { 5 if byte == b' ' { 6 return &s[..i]; 7 } 8 } 9 s 10 } 11 12 struct Headers<'a> { 13 raw: &'a str, 14 } 15 16 impl<'a> Headers<'a> { 17 fn new(raw: &'a str) -> Self { 18 Headers { raw } 19 } 20 21 // `&self` plus another reference parameter, returning a reference: 22 // elision ties the output to `&self`, so no annotation is needed. 23 fn get(&self, name: &str) -> Option<&str> { 24 self.raw 25 .lines() 26 .filter_map(|line| line.split_once(": ")) 27 .find(|(key, _)| key.eq_ignore_ascii_case(name)) 28 .map(|(_, value)| value) 29 } 30 } 31 32 fn main() { 33 let sentence = String::from("hello world rust"); 34 println!("first word: {}", first_word(&sentence)); 35 36 let headers = Headers::new("Host: example.com\nAccept: */*"); 37 println!("host: {:?}", headers.get("host")); 38 } ``` Running it: ```plaintext 1 first word: hello 2 host: Some("example.com") ``` The signatures are as clean as the TypeScript versions. That’s the whole point of elision. But unlike TypeScript, the compiler has *still verified* that every returned reference is valid for exactly as long as the thing it borrows from. You get the safety guarantee without the syntactic ceremony. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### What “elision” actually means [Section titled “What “elision” actually means”](#what-elision-actually-means) Every reference in Rust has a lifetime; that’s non-negotiable. **Elision** (from “to elide,” meaning to omit) is purely about whether *you* have to write that lifetime down. When you write: ```rust 1 fn first_word(s: &str) -> &str { 2 s.split_whitespace().next().unwrap_or("") 3 } ``` the compiler treats it *exactly* as if you had written the fully annotated version: ```rust 1 fn first_explicit<'a>(s: &'a str) -> &'a str { 2 s.split_whitespace().next().unwrap_or("") 3 } ``` Both compile and behave identically; the second is just the *desugared* form of the first. The compiler runs a mechanical procedure to expand the short form into the long form. If that procedure succeeds, the annotations were unnecessary. If it gets stuck, it stops and asks you to annotate. > **Tip:** “Elided” does not mean “ignored” or “inferred from the function body.” The compiler decides lifetimes purely from the *signature*, before it ever looks at the body. This is deliberate: changing a function’s body can never silently change its public lifetime contract. ### The three elision rules [Section titled “The three elision rules”](#the-three-elision-rules) The compiler applies these rules **in order**, to the function signature: 1. **Each elided lifetime in the *parameters* gets its own distinct lifetime.** `fn f(x: &str, y: &str)` is treated as `fn f<'a, 'b>(x: &'a str, y: &'b str)`. The two parameters get *separate* lifetimes. The compiler never assumes two input references live the same length. 2. **If there is exactly one input lifetime (elided or not), it is assigned to every elided *output* lifetime.** `fn f(x: &str) -> &str` becomes `fn f<'a>(x: &'a str) -> &'a str`. The single input is the only thing the output could possibly borrow from, so the compiler wires them together. 3. **If there are multiple input lifetimes *but one of them is `&self` or `&mut self`*, the lifetime of `self` is assigned to every elided output lifetime.** This is the rule that makes methods so ergonomic: a method that returns a reference almost always returns a piece of `self`, so the compiler defaults to that. If, after applying all three rules, any output lifetime is *still* unassigned, elision **fails** and the compiler demands explicit annotations. ### Walking the rules through `first_word` [Section titled “Walking the rules through first\_word”](#walking-the-rules-through-first_word) ```rust 1 fn first_word(s: &str) -> &str { /* ... */ } ``` * **Rule 1** gives the single parameter its own lifetime: `fn first_word<'a>(s: &'a str) -> &str`. * **Rule 2** applies because there is exactly one input lifetime, so it’s copied to the output: `fn first_word<'a>(s: &'a str) -> &'a str`. * Every output lifetime is now assigned. Elision succeeds. No annotation required. ### Walking the rules through `Headers::get` [Section titled “Walking the rules through Headers::get”](#walking-the-rules-through-headersget) ```rust 1 fn get(&self, name: &str) -> Option<&str> { /* ... */ } ``` * **Rule 1** gives each parameter its own lifetime: `&self` gets `'s`, `name` gets `'n`. * **Rule 2** does *not* apply: there are two input lifetimes, not one. * **Rule 3** applies because one parameter is `&self`. The output’s elided lifetime becomes `'s` (the `self` lifetime): `fn get<'s, 'n>(&'s self, name: &'n str) -> Option<&'s str>`. * Output lifetime assigned. Elision succeeds. This is exactly what you want: `get` returns a slice of `self.raw`, and the result is valid for as long as the `Headers` value is borrowed, *not* for as long as the `name` argument lives. ### When the rules run out: multiple inputs, no `self` [Section titled “When the rules run out: multiple inputs, no self”](#when-the-rules-run-out-multiple-inputs-no-self) ```rust 1 fn longest(a: &str, b: &str) -> &str { /* ... */ } // does not compile (error E0106) ``` * **Rule 1** gives `a` lifetime `'a` and `b` lifetime `'b`. * **Rule 2** does not apply (two input lifetimes). * **Rule 3** does not apply (no `self`). * The output lifetime is still unassigned. **Elision fails**, so you must annotate. The real compiler error spells this out precisely: ```plaintext 1 error[E0106]: missing lifetime specifier 2 --> src/main.rs:1:33 3 | 4 1 | fn longest(a: &str, b: &str) -> &str { 5 | ---- ---- ^ expected named lifetime parameter 6 | 7 = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b` 8 help: consider introducing a named lifetime parameter 9 | 10 1 | fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { 11 | ++++ ++ ++ ++ ``` Notice the help text: *“the signature does not say whether it is borrowed from `a` or `b`.”* That’s the elision rules failing in plain English: the compiler genuinely cannot pick for you, so you have to. (The fix, and *why* you write `'a` here, is the subject of the [lifetimes topic](/05-ownership/04-lifetimes/).) *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript/JavaScript | Rust | | -------------------------------------- | ----------------------------------- | ----------------------------------------- | | Tracking how long a reference is valid | Not tracked; GC keeps things alive | Tracked at compile time via lifetimes | | Writing lifetime annotations | N/A — no such thing | Required only when elision can’t infer | | Where lifetimes are inferred from | N/A | The **signature**, never the body | | Returning a substring | New independent string (heap copy) | A *borrow* whose lifetime must be derived | | ”One input → output” case | No annotation (nothing to annotate) | No annotation (rule 2 handles it) | | Method returning part of `self` | No annotation | No annotation (rule 3 handles it) | | Function with two refs, returning one | No annotation | **Annotation required** (elision fails) | ### Elision applies to functions and methods — *not* to structs [Section titled “Elision applies to functions and methods — not to structs”](#elision-applies-to-functions-and-methods--not-to-structs) An important boundary: the three rules apply only to **function and method signatures**. They do **not** apply to struct (or enum) definitions. A struct that holds a reference must always name the lifetime explicitly: ```rust 1 struct Wrapper { 2 text: &str, // does not compile (error E0106): no elision for struct fields 3 } ``` The real error: ```plaintext 1 error[E0106]: missing lifetime specifier 2 --> src/main.rs:3:11 3 | 4 3 | text: &str, // no elision for struct fields 5 | ^ expected named lifetime parameter 6 | 7 help: consider introducing a named lifetime parameter 8 | 9 2 ~ struct Wrapper<'a> { 10 3 ~ text: &'a str, // no elision for struct fields 11 | ``` The fix is `struct Wrapper<'a> { text: &'a str }`. Storing a reference is a long-term commitment, and the compiler refuses to guess how long it must stay valid. See [Lifetimes](/05-ownership/04-lifetimes/) for structs that hold references. ### Elision is about *omitting*, not *changing*, the rules [Section titled “Elision is about omitting, not changing, the rules”](#elision-is-about-omitting-not-changing-the-rules) A common misconception is that elided code is “less strict” than annotated code. It isn’t. The elided form `fn first_word(s: &str) -> &str` enforces *exactly* the same contract as the explicit `fn first_word<'a>(s: &'a str) -> &'a str`. Elision changes the *amount you type*, never the *guarantees you get*. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Assuming the output borrows from “the obvious” parameter [Section titled “Pitfall 1: Assuming the output borrows from “the obvious” parameter”](#pitfall-1-assuming-the-output-borrows-from-the-obvious-parameter) When a method takes `&self` **and** another reference, rule 3 *always* ties the elided output to `self`, even if your code returns a slice of the *other* parameter. This produces a confusing error if your intent differs: ```rust 1 struct Config { 2 name: String, 3 } 4 5 impl Config { 6 // Intent: return a slice of `text`. But rule 3 ties the output to `&self`. 7 fn extract<'b>(&self, text: &'b str) -> &str { // does not compile 8 &text[..3] 9 } 10 } ``` The real compiler error makes the mismatch explicit: ```plaintext 1 error: lifetime may not live long enough 2 --> src/main.rs:8:9 3 | 4 7 | fn extract<'b>(&self, text: &'b str) -> &str { 5 | -- - let's call the lifetime of this reference `'1` 6 | | 7 | lifetime `'b` defined here 8 8 | &text[..3] 9 | ^^^^^^^^^^ method was supposed to return data with lifetime `'1` but it is returning data with lifetime `'b` 10 | 11 help: consider reusing a named lifetime parameter and update trait if needed 12 | 13 7 | fn extract<'b>(&self, text: &'b str) -> &'b str { 14 | ++ ``` The compiler is saying: elision made the return `&'self`, but your body returns `&'b` data. The fix is to *opt out* of rule 3 by annotating explicitly: write `-> &'b str` so the output borrows from `text`, exactly as the help suggests. > **Warning:** Rule 3 is a convenience, not a mind reader. When a method returns a borrow of a *parameter* rather than of `self`, you must annotate to override the default. ### Pitfall 2: Trying to return a reference with no input to borrow from [Section titled “Pitfall 2: Trying to return a reference with no input to borrow from”](#pitfall-2-trying-to-return-a-reference-with-no-input-to-borrow-from) If a function returns a reference but has *no* reference parameters, none of the rules can supply an output lifetime, because there is nothing to borrow from: ```rust 1 fn make_greeting() -> &str { // does not compile (error E0106) 2 "hello" 3 } ``` The real error even guesses your two likely intentions: ```plaintext 1 error[E0106]: missing lifetime specifier 2 --> src/main.rs:1:23 3 | 4 1 | fn make_greeting() -> &str { 5 | ^ expected named lifetime parameter 6 | 7 = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from 8 help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` 9 | 10 1 | fn make_greeting() -> &'static str { 11 | +++++++ 12 help: instead, you are more likely to want to return an owned value 13 | 14 1 - fn make_greeting() -> &str { 15 1 + fn make_greeting() -> String { 16 | ``` For a string literal, `&'static str` is correct (literals live for the whole program). But the more common fix, as the compiler hints, is to return an **owned** `String`. Returning owned data is the right move whenever the function *creates* the value rather than borrowing it from a caller; see [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/). ### Pitfall 3: Believing two input references share a lifetime [Section titled “Pitfall 3: Believing two input references share a lifetime”](#pitfall-3-believing-two-input-references-share-a-lifetime) Rule 1 gives each input its *own* lifetime. TypeScript developers sometimes assume both arguments “must be the same,” but Rust deliberately keeps them separate so the most flexible signature is the default: ```rust 1 // Elided form... 2 fn f(a: &str, b: &str) -> usize { a.len() + b.len() } 3 // ...desugars to TWO distinct lifetimes, NOT one: 4 fn f_explicit<'a, 'b>(a: &'a str, b: &'b str) -> usize { a.len() + b.len() } ``` This only matters once you start *returning* a reference. At that point you usually *do* want to relate the lifetimes, which is why such functions can’t be elided and force you to annotate (Pitfall 1’s `longest` is the canonical case). ### Pitfall 4: Expecting elision in closures or function pointers the same way [Section titled “Pitfall 4: Expecting elision in closures or function pointers the same way”](#pitfall-4-expecting-elision-in-closures-or-function-pointers-the-same-way) The three rules are defined for `fn` and method signatures. Closures infer lifetimes through a separate mechanism, and trait objects / `dyn` types have their own default-lifetime rules. Don’t assume a hand-written `fn(&str) -> &str` type alias elides the same way a function definition does. When in doubt, annotate. (Closures are covered in [Arrow Functions vs Closures](/03-functions/03-arrow-vs-closures/).) *** ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Omit lifetimes whenever elision allows it [Section titled “1. Omit lifetimes whenever elision allows it”](#1-omit-lifetimes-whenever-elision-allows-it) Idiomatic Rust does **not** write annotations the compiler can infer. Adding `'a` where it isn’t needed is noise that the Clippy lint `needless_lifetimes` will flag: ```rust 1 // Unidiomatic: explicit lifetime the rules would infer anyway. 2 fn first<'a>(s: &'a str) -> &'a str { s.split_whitespace().next().unwrap_or("") } 3 4 // Idiomatic: let elision do its job. 5 fn first(s: &str) -> &str { s.split_whitespace().next().unwrap_or("") } ``` ### 2. Reach for annotations only when the compiler asks [Section titled “2. Reach for annotations only when the compiler asks”](#2-reach-for-annotations-only-when-the-compiler-asks) The right workflow is: write the clean, elided signature *first*. If it compiles, you’re done. If you get `E0106` or a “lifetime may not live long enough” error, *then* add exactly the annotation the error suggests. Don’t preemptively annotate “to be safe.” ### 3. Run the three rules in your head before reaching for `'a` [Section titled “3. Run the three rules in your head before reaching for 'a”](#3-run-the-three-rules-in-your-head-before-reaching-for-a) When you hit a lifetime error, mentally apply rules 1–3 to the signature. If you can see *why* the output lifetime is unassigned (multiple inputs, no `self`) or *wrong* (rule 3 picked `self` but you meant a parameter), the fix is obvious and you avoid guessing. ### 4. Prefer owned return types when the function creates the data [Section titled “4. Prefer owned return types when the function creates the data”](#4-prefer-owned-return-types-when-the-function-creates-the-data) If a function builds a new string or vector rather than borrowing from an argument, return `String` / `Vec`, not a reference. There’s no lifetime to elide because there’s no borrow, and the API is simpler for callers. Borrow in, own out is a common and healthy pattern. > **Tip:** A function whose every reference is handled by elision is a sign of a well-shaped API: it either passes borrows straight through (one input → output) or returns part of `self`. When you find yourself needing many explicit `'a`s, consider whether returning owned data would be cleaner. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A small, production-flavored HTTP header parser. It borrows the raw request text and hands back slices into it. Almost every method relies on elision. The only explicit lifetime is on the struct itself (where elision never applies) and on a free function that overrides rule defaults: ```rust 1 /// A zero-copy view over raw HTTP header text. 2 /// The struct borrows the buffer, so it needs an explicit lifetime. 3 struct Headers<'a> { 4 raw: &'a str, 5 } 6 7 impl<'a> Headers<'a> { 8 fn new(raw: &'a str) -> Self { 9 Headers { raw } 10 } 11 12 // Rule 3: `&self` + `name: &str` → output borrows from `&self`. 13 // No annotation needed; the returned slice points into `self.raw`. 14 fn get(&self, name: &str) -> Option<&str> { 15 self.raw 16 .lines() 17 .filter_map(|line| line.split_once(": ")) 18 .find(|(key, _)| key.eq_ignore_ascii_case(name)) 19 .map(|(_, value)| value) 20 } 21 22 // Rule 3 again: returns a slice of `self.raw`. 23 fn first_line(&self) -> &str { 24 self.raw.lines().next().unwrap_or("") 25 } 26 } 27 28 // Free function, one reference input → rule 2 supplies the output lifetime. 29 // `prefix` is NOT a reference we return, so its lifetime is irrelevant here. 30 fn trim_prefix<'p>(s: &'p str, prefix: &str) -> &'p str { 31 s.strip_prefix(prefix).unwrap_or(s) 32 } 33 34 fn main() { 35 let raw = "Host: example.com\nContent-Type: text/html\nAccept: */*"; 36 let headers = Headers::new(raw); 37 38 println!("first line: {}", headers.first_line()); 39 println!("content-type: {:?}", headers.get("content-type")); 40 println!("missing: {:?}", headers.get("authorization")); 41 42 println!("trimmed: {}", trim_prefix("v1.2.3", "v")); 43 } ``` Real output: ```plaintext 1 first line: Host: example.com 2 content-type: Some("text/html") 3 missing: None 4 trimmed: 1.2.3 ``` > **Note:** `trim_prefix` is written with an explicit `'p` *only on `s`* to document that the return value borrows from `s`, not `prefix`. Rule 2 would actually elide this correctly because `prefix`’s lifetime never reaches the output, but here the annotation is intentional documentation, not a requirement. This is the rare case where being explicit aids the reader. The struct’s `<'a>`, by contrast, is mandatory: elision never applies to fields. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Lifetime Elision](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-elision) — the canonical explanation of the three rules. * [The Rustonomicon — Lifetime Elision](https://doc.rust-lang.org/nomicon/lifetime-elision.html) — a precise, table-driven restatement with edge cases. * [Rust Reference — Lifetime Elision](https://doc.rust-lang.org/reference/lifetime-elision.html) — the formal specification, including `impl` and trait-object defaults. ### Related Topics in This Guide [Section titled “Related Topics in This Guide”](#related-topics-in-this-guide) * [Lifetimes](/05-ownership/04-lifetimes/) — what the `'a` annotations *mean* and why they exist; required reading if elision fails. * [Borrowing](/05-ownership/02-borrowing/) — shared references, the foundation lifetimes track. * [Mutable References](/05-ownership/03-mutable-references/) — `&mut` and non-lexical lifetimes. * [Ownership Rules](/05-ownership/01-ownership-rules/) — moves, owners, and scope-based drop. * [Move, Copy, Clone](/05-ownership/06-move-copy-clone/) — when to return owned data instead of a borrow. * [Section 05 overview](/05-ownership/) — the full ownership system. * [Functions: parameters](/03-functions/01-parameters/) and [arrow vs closures](/03-functions/03-arrow-vs-closures/) — where references in signatures first appear. * [Variables and Mutability](/02-basics/00-variables/) — the immutability foundation. * [Data Structures](/06-data-structures/) — structs that own vs. borrow their fields. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Spot the elision [Section titled “Exercise 1: Spot the elision”](#exercise-1-spot-the-elision) **Difficulty:** Easy **Objective:** Predict whether a signature needs annotations, then confirm by writing the desugared form. **Instructions:** For the function below, decide whether lifetime elision succeeds. Then write out the fully annotated (desugared) signature the compiler produces, and add a second function `last_char` that returns `Option` from a `&str`. ```rust 1 fn last_char(s: &str) -> Option { 2 /* ??? */ 3 } 4 5 fn main() { 6 assert_eq!(last_char("abc"), Some('c')); 7 assert_eq!(last_char(""), None); 8 println!("ok"); 9 } ``` Solution Elision **succeeds**: there is one input lifetime (rule 1), and rule 2 copies it to any elided output lifetime. Here the return type is `Option` — `char` is an owned `Copy` type, not a reference — so there’s no output lifetime to assign at all. No annotation is needed either way. ```rust 1 fn last_char(s: &str) -> Option { 2 s.chars().last() 3 } 4 5 fn main() { 6 assert_eq!(last_char("abc"), Some('c')); 7 assert_eq!(last_char(""), None); 8 println!("ok"); 9 } ``` The desugared signature is simply `fn last_char<'a>(s: &'a str) -> Option`; the lifetime exists on the input but is never used in the output. ### Exercise 2: Make a method compile via rule 3 [Section titled “Exercise 2: Make a method compile via rule 3”](#exercise-2-make-a-method-compile-via-rule-3) **Difficulty:** Medium **Objective:** Use a method that returns a borrow of `self`, relying on elision rule 3. **Instructions:** Complete the `head` method so it returns the first `n` bytes of the buffer as a `&str` (clamped to the buffer’s length). Do **not** add any explicit lifetime annotations to `head` — let elision handle it. ```rust 1 struct Buffer { 2 data: String, 3 } 4 5 impl Buffer { 6 fn head(&self, n: usize) -> &str { 7 /* ??? */ 8 } 9 } 10 11 fn main() { 12 let buf = Buffer { data: "hello world".to_string() }; 13 assert_eq!(buf.head(5), "hello"); 14 assert_eq!(buf.head(100), "hello world"); 15 println!("ok"); 16 } ``` Solution Rule 3 ties the elided output lifetime to `&self`, so the returned slice borrows from `self.data` with no annotation needed: ```rust 1 struct Buffer { 2 data: String, 3 } 4 5 impl Buffer { 6 fn head(&self, n: usize) -> &str { 7 &self.data[..n.min(self.data.len())] 8 } 9 } 10 11 fn main() { 12 let buf = Buffer { data: "hello world".to_string() }; 13 assert_eq!(buf.head(5), "hello"); 14 assert_eq!(buf.head(100), "hello world"); 15 println!("ok"); 16 } ``` `n.min(self.data.len())` clamps the index so slicing never panics past the end. ### Exercise 3: When elision fails [Section titled “Exercise 3: When elision fails”](#exercise-3-when-elision-fails) **Difficulty:** Hard **Objective:** Recognize a signature elision cannot resolve and supply the correct annotation. **Instructions:** Write `pick` so it returns `a` when `flag` is true and `b` otherwise. Because the function has two reference parameters and no `self`, elision fails, so you must add the annotation that says “the result lives as long as both inputs.” Explain in one sentence why elision can’t do this for you. ```rust 1 fn pick(flag: bool, a: &str, b: &str) -> &str { 2 /* ??? */ 3 } 4 5 fn main() { 6 assert_eq!(pick(true, "yes", "no"), "yes"); 7 assert_eq!(pick(false, "yes", "no"), "no"); 8 println!("ok"); 9 } ``` Solution Rule 1 gives `a` and `b` separate lifetimes; rule 2 doesn’t apply (two inputs); rule 3 doesn’t apply (no `self`). With the output lifetime unassigned, elision fails. Tie both inputs and the output to a single lifetime `'a`: ```rust 1 fn pick<'a>(flag: bool, a: &'a str, b: &'a str) -> &'a str { 2 if flag { a } else { b } 3 } 4 5 fn main() { 6 assert_eq!(pick(true, "yes", "no"), "yes"); 7 assert_eq!(pick(false, "yes", "no"), "no"); 8 println!("ok"); 9 } ``` **Why elision can’t decide:** the compiler infers lifetimes from the *signature alone*, and the signature is honest that the result could come from `a` *or* `b` — so it cannot know which input the output borrows from without you saying so. The `bool` parameter (which the body uses to choose) is invisible to the elision rules. See [Lifetimes](/05-ownership/04-lifetimes/) for the full meaning of `'a` here. *** ## Summary [Section titled “Summary”](#summary) **What you’ve learned:** * Lifetime **elision** lets you omit `'a` annotations the compiler can infer from a signature. * The **three rules**, applied in order: (1) each input reference gets its own lifetime; (2) one input lifetime → copied to all outputs; (3) `&self` present → its lifetime → all outputs. * Elision runs on the **signature**, never the body, and never changes the guarantees, only the typing. * It applies to **functions and methods only**; struct fields always need explicit lifetimes. * When the rules leave an output lifetime unassigned (multiple inputs, no `self`) or assign the *wrong* one (rule 3 picks `self` but you meant a parameter), the compiler asks you to annotate. **Mental model:** * Write the clean, un-annotated signature first. * If it compiles, elision handled it. Leave it alone. * If it doesn’t, run the three rules in your head, find the unassigned output, and add exactly that one annotation. # Move, Copy, and Clone > Rust's move, Copy, and Clone replace JavaScript's invisible reference sharing. See when assignment transfers ownership, duplicates a value, or deep-copies. In TypeScript and JavaScript, assigning one variable to another never duplicates the underlying object; both names point at the same garbage-collected value, and you rarely think about *who owns what*. Rust replaces that quiet sharing with three precise behaviors: a value is **moved** (ownership transfers, the old name becomes unusable), **copied** (a cheap bit-for-bit duplicate of a stack value), or **cloned** (an explicit, possibly expensive deep duplicate). Understanding which one happens, and when, is the difference between fighting the compiler and writing fluent Rust. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) When you write `let b = a;` in Rust, one of two things happens: if `a`’s type implements the **`Copy`** trait (small, stack-only values like `i32` or `bool`), `a` is duplicated and both `a` and `b` stay usable; otherwise the value is **moved**: `b` takes ownership and `a` becomes inaccessible. To get a duplicate of a non-`Copy` value (like a `String` or `Vec`), you call **`.clone()`** explicitly, which performs a deep copy you can *see* in the source. This is the opposite of JavaScript, where `const b = a` for an object simply aliases the same heap value and the GC keeps it alive for as long as anyone references it. > **Note:** This page builds directly on [Ownership Rules](/05-ownership/01-ownership-rules/) (each value has one owner; assignment moves it) and [Stack vs Heap](/05-ownership/00-stack-heap/) (why some types are cheap to copy and others are not). If those ideas are new, skim them first. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) In JavaScript, every variable holding an object holds a *reference*. Assignment copies the reference, never the object, and primitives are copied by value. You never explicitly “move” or “clone”; the engine and garbage collector handle lifetime for you. inventory.ts ```typescript 1 interface Product { 2 sku: string; 3 tags: string[]; 4 } 5 6 // Assigning an object copies the REFERENCE, not the object. 7 const original: Product = { sku: "A-100", tags: ["new"] }; 8 const alias = original; 9 alias.tags.push("sale"); 10 console.log(original.tags); // [ 'new', 'sale' ] — they are the SAME object 11 12 // Primitives are copied by value. 13 let count = 5; 14 let backup = count; 15 backup += 1; 16 console.log(count, backup); // 5 6 — independent copies 17 18 // To get a genuinely separate object you must clone EXPLICITLY, 19 // and even then a shallow clone shares nested arrays/objects. 20 const shallow = { ...original }; 21 const deep = structuredClone(original); 22 shallow.tags.push("shallow"); // mutates original.tags too! 23 deep.tags.push("deep"); // safe: deep has its own array 24 console.log(original.tags); // [ 'new', 'sale', 'shallow' ] 25 console.log(deep.tags); // [ 'new', 'sale', 'deep' ] 26 27 // Passing an object to a function passes the reference; the original 28 // is still usable afterward, and mutations leak back to the caller. 29 function addTag(p: Product, tag: string): void { 30 p.tags.push(tag); 31 } 32 addTag(original, "fn"); // original is fine after the call ``` **What the runtime does for you here:** * `const alias = original` aliases the same heap object. Both names see each other’s mutations. * `let backup = count` copies the primitive `5`. The two numbers are independent. * After passing `original` to `addTag`, you can keep using `original`. The function only borrowed a reference, and the GC keeps the object alive. Rust makes every one of these distinctions explicit and enforces them at compile time. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) The same three behaviors (alias-by-reference, copy-by-value, and explicit deep copy) become **move**, **`Copy`**, and **`Clone`**: ```rust 1 #[derive(Debug, Clone)] 2 struct Account { 3 id: u64, 4 owner: String, 5 balance: f64, 6 } 7 8 // Takes ownership of `account` (it is MOVED in), mutates it, and returns it. 9 fn deposit(mut account: Account, amount: f64) -> Account { 10 account.balance += amount; 11 account 12 } 13 14 fn main() { 15 // --- Copy: i32 is a small stack value, so `=` duplicates it. --- 16 let x = 5; 17 let y = x; // x is COPIED, not moved 18 println!("x = {x}, y = {y}"); // both usable 19 20 // --- Move: String owns heap data, so `=` transfers ownership. --- 21 let s1 = String::from("hello"); 22 let s2 = s1; // s1 is MOVED into s2 23 println!("{s2}"); 24 // println!("{s1}"); // does not compile (error[E0382]): s1 was moved 25 26 // --- Clone: ask explicitly for a deep copy. --- 27 let s3 = String::from("world"); 28 let s4 = s3.clone(); // duplicates the heap buffer 29 println!("{s3} {s4}"); // BOTH valid 30 31 // --- Move into a function and get ownership back via the return. --- 32 let acct = Account { id: 1, owner: String::from("Ada"), balance: 100.0 }; 33 let acct = deposit(acct, 50.0); // `acct` moved in, rebound to the result 34 println!("{acct:?}"); 35 36 // --- Clone a whole struct (deep-copies the String inside). --- 37 let original = Account { id: 2, owner: String::from("Bob"), balance: 0.0 }; 38 let copy = original.clone(); 39 println!("{}", original.owner); // original still usable 40 println!("{}", copy.owner); 41 } ``` This compiles and prints: ```text 1 x = 5, y = 5 2 hello 3 world world 4 Account { id: 1, owner: "Ada", balance: 150.0 } 5 Bob 6 Bob ``` Notice what is *visible* in the source: every potentially expensive duplication is a `.clone()` call you can grep for, and every place a value becomes unusable is an ordinary `=` or function call. There is no hidden deep-copy and no hidden aliasing. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### Move: the default for owned, heap-backed values [Section titled “Move: the default for owned, heap-backed values”](#move-the-default-for-owned-heap-backed-values) A `String` is a three-word handle on the stack: a pointer to a heap buffer, a length, and a capacity (see [Stack vs Heap](/05-ownership/00-stack-heap/)). When you write `let s2 = s1;`, Rust copies those three stack words into `s2`. But now two handles would point at the *same* heap buffer, and when both went out of scope they would each try to free it — a double-free. Rust’s solution is the **move**: after the assignment, `s1` is statically marked invalid, so only `s2` is responsible for the buffer. The copy of the stack handle is cheap; what is forbidden is *continuing to use the old name*. This is the single biggest mental shift from JavaScript. In JS, `const s2 = s1` gives you a second name for the same live object. In Rust, the second name *takes over* and the first is retired. ### Copy: opting out of move for cheap stack values [Section titled “Copy: opting out of move for cheap stack values”](#copy-opting-out-of-move-for-cheap-stack-values) Some types are so cheap to duplicate that moving them would be pointless ceremony. Integers, floats, `bool`, `char`, and tuples/arrays of such types implement the **`Copy`** trait. For these, `let y = x;` performs a bitwise copy and leaves `x` fully usable — exactly like a JavaScript primitive: ```rust 1 fn square(n: i32) -> i32 { 2 n * n 3 } 4 5 fn main() { 6 let n = 7; 7 let sq = square(n); // n is COPIED into the function 8 println!("{n} squared is {sq}"); // n still usable 9 } ``` ```text 1 7 squared is 49 ``` A type can be `Copy` only if *all* of its parts are `Copy` and it does not own a heap allocation or any other resource. That is why `String` (owns heap memory) and `Vec` are **not** `Copy`, while `(i32, bool)` is. ### Clone: explicit, deep, possibly expensive [Section titled “Clone: explicit, deep, possibly expensive”](#clone-explicit-deep-possibly-expensive) `Copy` happens implicitly because it is guaranteed cheap. Anything that might be expensive (duplicating a heap buffer, recursively copying a tree) must be requested with **`.clone()`**. `String::clone` allocates a new buffer and copies the bytes; `Vec::clone` allocates a new array and clones each element. The cost is real, so Rust makes you write it down. ```rust 1 let words = vec![String::from("a"), String::from("b")]; 2 // `iter()` borrows; `cloned()` clones each &String into an owned String. 3 let joined: String = words.iter().cloned().collect::>().join("-"); 4 println!("{joined}"); // a-b 5 println!("still have words: {words:?}"); // words was only borrowed, still valid ``` ```text 1 a-b 2 still have words: ["a", "b"] ``` ### Moving into functions (and getting ownership back) [Section titled “Moving into functions (and getting ownership back)”](#moving-into-functions-and-getting-ownership-back) Passing a non-`Copy` value to a function *moves* it, just like assignment. The function now owns it; the caller does not. If the caller still needs the value, the idiomatic options are (in rough order of preference): **borrow** instead of moving (`&T`, covered in [Borrowing](/05-ownership/02-borrowing/)), **return the value back** out of the function (as `deposit` does above), or **clone** before the call. Borrowing is almost always the right answer when the function only needs to *read or temporarily mutate* the data. ### Partial moves out of a struct [Section titled “Partial moves out of a struct”](#partial-moves-out-of-a-struct) You can move a single field out of a struct. After that, the field is invalid but `Copy` fields remain readable: ```rust 1 #[derive(Debug)] 2 struct Profile { 3 username: String, 4 age: u32, 5 } 6 7 fn main() { 8 let p = Profile { username: String::from("rustacean"), age: 30 }; 9 let name = p.username; // partial MOVE of the String field out of p 10 let age = p.age; // u32 is Copy, so this is a copy 11 println!("{name} is {age}"); 12 // println!("{:?}", p); // does not compile: p is partially moved 13 } ``` ```text 1 rustacean is 30 ``` *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Aspect | TypeScript/JavaScript | Rust | | ------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `let b = a` for an object | Copies the **reference**; both names alias one live object | **Moves** ownership; `a` becomes unusable (unless the type is `Copy`) | | `let b = a` for a number | Copies the value; both independent | **Copies** (`Copy` types); both independent | | Deep duplicate | `structuredClone(a)` / spread (shallow) | `a.clone()` (depth depends on the type’s `Clone` impl) | | Passing to a function | Passes a reference (objects) or a copy (primitives); original stays usable | **Moves** the value in (non-`Copy`), or copies it (`Copy`); to keep it, borrow or clone | | Who frees the memory | The garbage collector, eventually | The single current owner, deterministically at end of scope ([Drop](/05-ownership/08-drop-trait/)) | | Cost of duplication | Invisible; sharing is implicit and free | Explicit: cheap `Copy` is automatic, expensive `Clone` is spelled out | The deepest difference is *honesty about cost*. JavaScript hides whether you are sharing or copying; the GC papers over the lifetime question entirely. Rust forces the choice into the open: a move is a cheap ownership transfer, a `Copy` is a guaranteed-cheap duplicate, and a `Clone` is the one place where an allocation might happen, and it is always visible in the source. > **Tip:** A quick rule of thumb: if a type is `Copy`, treating it like a JavaScript primitive (free to duplicate, no aliasing surprises) is exactly right. If it is not `Copy`, assume `=` and function calls *consume* the value unless you borrow with `&`. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Using a value after it was moved [Section titled “Pitfall 1: Using a value after it was moved”](#pitfall-1-using-a-value-after-it-was-moved) This is the error every TypeScript developer hits first, because in JavaScript the original would still be valid: ```rust 1 fn main() { 2 let s1 = String::from("hello"); 3 let s2 = s1; // value moved here 4 println!("{s1}"); // does not compile (error[E0382]) 5 println!("{s2}"); 6 } ``` The real compiler output: ```text 1 error[E0382]: borrow of moved value: `s1` 2 --> src/main.rs:4:16 3 | 4 2 | let s1 = String::from("hello"); 5 | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 6 3 | let s2 = s1; 7 | -- value moved here 8 4 | println!("{s1}"); 9 | ^^ value borrowed here after move 10 | 11 = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) 12 help: consider cloning the value if the performance cost is acceptable 13 | 14 3 | let s2 = s1.clone(); 15 | ++++++++ ``` The fix is whichever matches your intent: `let s2 = s1.clone();` if you genuinely need two owners, or `let s2 = &s1;` if you only need a second *view* ([Borrowing](/05-ownership/02-borrowing/)). ### Pitfall 2: Moving a value into a function, then using it again [Section titled “Pitfall 2: Moving a value into a function, then using it again”](#pitfall-2-moving-a-value-into-a-function-then-using-it-again) Same error, but it surprises people because the move is hidden inside a call: ```rust 1 fn print_len(s: String) { 2 println!("{}", s.len()); 3 } 4 5 fn main() { 6 let name = String::from("Grace Hopper"); 7 print_len(name); // value moved here 8 println!("{name}"); // does not compile (error[E0382]) 9 } ``` The real output even tells you the better design: ```text 1 error[E0382]: borrow of moved value: `name` 2 --> src/main.rs:8:16 3 | 4 6 | let name = String::from("Grace Hopper"); 5 | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait 6 7 | print_len(name); 7 | ---- value moved here 8 8 | println!("{name}"); 9 | ^^^^ value borrowed here after move 10 | 11 note: consider changing this parameter type in function `print_len` to borrow instead if owning the value isn't necessary 12 --> src/main.rs:1:17 13 | 14 1 | fn print_len(s: String) { 15 | --------- ^^^^^^ this parameter takes ownership of the value 16 | | 17 | in this function 18 = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) 19 help: consider cloning the value if the performance cost is acceptable 20 | 21 7 | print_len(name.clone()); 22 | ++++++++ ``` The idiomatic fix is to make `print_len` borrow: `fn print_len(s: &str)` and call `print_len(&name)`. The function never needed to own the string. ### Pitfall 3: Deriving `Copy` on a type that owns heap data [Section titled “Pitfall 3: Deriving Copy on a type that owns heap data”](#pitfall-3-deriving-copy-on-a-type-that-owns-heap-data) You cannot make a `String`-holding struct `Copy`, because copying it bit-for-bit would create two owners of one buffer, exactly what move semantics prevent: ```rust 1 #[derive(Copy, Clone)] // does not compile (error[E0204]) 2 struct Wrapper { 3 name: String, 4 } ``` ```text 1 error[E0204]: the trait `Copy` cannot be implemented for this type 2 --> src/main.rs:1:10 3 | 4 1 | #[derive(Copy, Clone)] 5 | ^^^^ 6 2 | struct Wrapper { 7 3 | name: String, 8 | ------------ this field does not implement `Copy` ``` The fix: derive only `Clone` (`#[derive(Clone)]`) and call `.clone()` when you need a duplicate. ### Pitfall 4: Reaching for the whole struct after a partial move [Section titled “Pitfall 4: Reaching for the whole struct after a partial move”](#pitfall-4-reaching-for-the-whole-struct-after-a-partial-move) Moving one field out invalidates the *whole* binding for purposes of using it as a unit: ```rust 1 let _name = p.username; // moves the String field out 2 println!("{:?}", p); // does not compile ``` ```text 1 error[E0382]: borrow of partially moved value: `p` 2 --> src/main.rs:10:22 3 | 4 9 | let _name = p.username; 5 | ---------- value partially moved here 6 10 | println!("{:?}", p); 7 | ^ value borrowed here after partial move 8 | 9 = note: partial move occurs because `p.username` has type `String`, which does not implement the `Copy` trait 10 = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) ``` If you need both the field and the rest of the struct, clone the field (`p.username.clone()`) or borrow it (`&p.username`). ### Pitfall 5: “Cloning” to silence the borrow checker [Section titled “Pitfall 5: “Cloning” to silence the borrow checker”](#pitfall-5-cloning-to-silence-the-borrow-checker) When the compiler suggests `.clone()`, it is one valid fix — but reaching for it reflexively can hide a design that should borrow instead, adding needless allocations on a hot path. Clone is a tool, not a panic button. Prefer borrowing first (see [Borrowing](/05-ownership/02-borrowing/)); clone when you genuinely need two independent owners. *** ## Best Practices [Section titled “Best Practices”](#best-practices) * **Borrow before you clone before you move.** If a function only reads, take `&T`. If it temporarily mutates, take `&mut T`. Move (take `T`) only when the function truly needs to *own* and store the value. Reserve `.clone()` for when you genuinely need a second independent owner. * **Derive `Copy` for small, plain value types**: IDs, coordinates, enums without data, fixed-size numeric wrappers. It makes them ergonomic to pass around exactly like JavaScript primitives. Always derive `Clone` alongside `Copy` (`#[derive(Clone, Copy)]`); `Copy` requires `Clone` as a supertrait. * **Never derive `Copy` on anything that owns a heap allocation or other resource** (`String`, `Vec`, `Box`, file handles). Those are move-only by design. * **Make clones visible and intentional.** When you do clone, a brief comment on *why* (e.g. “config is shared across threads”) helps reviewers distinguish a deliberate clone from a borrow-checker workaround. * **Return ownership instead of cloning when a function transforms a value.** The `deposit` pattern (`value in → modified value out`) avoids both a clone and a borrow dance. * **Reach for `Rc`/`Arc` for shared ownership, not repeated `.clone()` of large data.** When many parts of a program need to *share* one immutable value, reference counting is cheaper than deep cloning. See [Reference Counting](/05-ownership/07-reference-counting/). *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A job-processing loop shows all three behaviors working together: a `Copy` ID type passed freely, a shared `Config` that is only *borrowed* (never cloned), and jobs that are *moved* into the processor and consumed. ```rust 1 use std::collections::HashMap; 2 3 /// Shared, read-only configuration loaded once at startup. 4 #[derive(Debug, Clone)] 5 struct Config { 6 service_name: String, 7 max_retries: u32, 8 endpoints: Vec, 9 } 10 11 /// A small, all-`Copy` value: cheap to pass and store by value. 12 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 13 struct JobId(u64); 14 15 #[derive(Debug)] 16 struct Job { 17 id: JobId, 18 payload: String, 19 } 20 21 /// Consumes the job (it is MOVED in) and only BORROWS the config, 22 /// so one `Config` serves every job without a single clone. 23 fn process(job: Job, config: &Config) -> String { 24 let _ = &config.endpoints; // (would be used to dispatch in real code) 25 format!( 26 "[{}] job {} -> {} (retries={})", 27 config.service_name, job.id.0, job.payload, config.max_retries 28 ) 29 } 30 31 fn main() { 32 let config = Config { 33 service_name: String::from("ingest"), 34 max_retries: 3, 35 endpoints: vec![String::from("https://a"), String::from("https://b")], 36 }; 37 38 let jobs = vec![ 39 Job { id: JobId(1), payload: String::from("resize-image") }, 40 Job { id: JobId(2), payload: String::from("send-email") }, 41 ]; 42 43 let mut results: HashMap = HashMap::new(); 44 for job in jobs { 45 let id = job.id; // JobId is Copy: this does NOT consume `job` 46 // `job` is moved into `process`; `&config` is only borrowed. 47 let summary = process(job, &config); 48 results.insert(id, summary); 49 } 50 51 // `config` is still fully usable — it was only ever borrowed. 52 println!("service still available: {}", config.service_name); 53 let mut ids: Vec<_> = results.keys().copied().collect(); 54 ids.sort_by_key(|j| j.0); 55 for id in ids { 56 println!("{}", results[&id]); 57 } 58 } ``` This compiles and prints: ```text 1 service still available: ingest 2 [ingest] job 1 -> resize-image (retries=3) 3 [ingest] job 2 -> send-email (retries=3) ``` The key design wins, all enforced at compile time: `JobId` being `Copy` lets us grab `job.id` for the map key *without* consuming `job`; the `&config` borrow means the single config object is reused for every iteration with zero allocations; and each `Job` is moved into `process` and dropped at the end of that call, so memory is reclaimed deterministically with no GC pauses. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — What Is Ownership? (Move semantics)](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html) * [The Rust Book — References and Borrowing](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html) * [`std::marker::Copy` (trait docs)](https://doc.rust-lang.org/std/marker/trait.Copy.html) * [`std::clone::Clone` (trait docs)](https://doc.rust-lang.org/std/clone/trait.Clone.html) * [Rust by Example — Ownership and moves](https://doc.rust-lang.org/rust-by-example/scope/move.html) * [Error index — E0382 (use of moved value)](https://doc.rust-lang.org/error_codes/E0382.html), [E0204 (cannot derive `Copy`)](https://doc.rust-lang.org/error_codes/E0204.html) ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [Stack vs Heap](/05-ownership/00-stack-heap/) — why `Copy` types are cheap and `String`/`Vec` are not. * [Ownership Rules](/05-ownership/01-ownership-rules/): one owner per value; move on assignment; drop at end of scope. * [Borrowing](/05-ownership/02-borrowing/) — `&T` shared references: the alternative to moving or cloning. * [Mutable References](/05-ownership/03-mutable-references/): `&mut T` for in-place mutation without moving. * [Lifetimes](/05-ownership/04-lifetimes/) — relating the validity of borrows you hand out. * [Reference Counting (`Rc`/`Arc`)](/05-ownership/07-reference-counting/): shared ownership without deep clones. * [The `Drop` Trait](/05-ownership/08-drop-trait/) — what happens when the final owner goes out of scope. * [Section 05 overview](/05-ownership/): the full ownership system. * [Functions — Parameters](/03-functions/01-parameters/) — choosing between by-value, `&`, and `&mut` parameters. * [Basics — Variables and Mutability](/02-basics/00-variables/) and [Types](/02-basics/01-types/): the building blocks moved and copied here. * [Data Structures](/06-data-structures/) — structs and enums that own vs. borrow their fields. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1: Clone to keep two owners [Section titled “Exercise 1: Clone to keep two owners”](#exercise-1-clone-to-keep-two-owners) **Difficulty:** Easy **Objective:** Recognize a move and fix it by cloning when two independent owners are genuinely required. **Instructions:** The snippet below does not compile because `a` is moved into `b` and then used again. Make it compile *without* changing the fact that both `a` and `b` must be usable afterward and must hold equal contents. ```rust 1 fn main() { 2 let a = String::from("data"); 3 let b = a; // TODO: a gets used below — make this work 4 println!("{a} {b}"); 5 assert_eq!(a, b); 6 } ``` Solution ```rust 1 fn main() { 2 let a = String::from("data"); 3 let b = a.clone(); // explicit deep copy: now there are two owners 4 println!("{a} {b}"); 5 assert_eq!(a, b); 6 println!("ex1 ok"); 7 } ``` `a.clone()` allocates a second heap buffer with the same bytes, so `a` and `b` are independent owners. (If you only needed to *read* `a` through `b`, `let b = &a;` would have avoided the allocation entirely, but the exercise requires two owners.) ### Exercise 2: Make a type `Copy` [Section titled “Exercise 2: Make a type Copy”](#exercise-2-make-a-type-copy) **Difficulty:** Medium **Objective:** Derive `Copy` on a small value type so it can be used multiple times without moving. **Instructions:** Define a `Meters(f64)` newtype and an `add(a: Meters, b: Meters) -> Meters` function. Then call `add(d, d)` using the *same* variable `d` twice and also read `d` afterward. This is only possible if `Meters` is `Copy`; add the right derive. ```rust 1 struct Meters(f64); // TODO: derive what you need 2 3 fn add(a: Meters, b: Meters) -> Meters { 4 // TODO 5 todo!() 6 } 7 8 fn main() { 9 let d = Meters(3.0); 10 let total = add(d, d); // d used twice 11 assert_eq!(total.0, 6.0); 12 assert_eq!(d.0, 3.0); // d still usable 13 println!("ok"); 14 } ``` Solution ```rust 1 #[derive(Debug, Clone, Copy, PartialEq)] 2 struct Meters(f64); 3 4 fn add(a: Meters, b: Meters) -> Meters { 5 Meters(a.0 + b.0) 6 } 7 8 fn main() { 9 let d = Meters(3.0); 10 let total = add(d, d); // `d` is COPIED into each argument, not moved 11 assert_eq!(total, Meters(6.0)); 12 assert_eq!(d, Meters(3.0)); // still usable 13 println!("ok"); 14 } ``` Because `f64` is `Copy`, a `Meters` wrapping a single `f64` can derive `Copy` too. `Copy` requires `Clone` as a supertrait, so you must derive both. Now passing `d` twice copies it twice instead of moving it. ### Exercise 3: Borrow, then own only what you keep [Section titled “Exercise 3: Borrow, then own only what you keep”](#exercise-3-borrow-then-own-only-what-you-keep) **Difficulty:** Medium–Hard **Objective:** Take borrowed input, clone only the pieces you decide to keep, and return owned data: the common “build an owned result from borrowed inputs” pattern. **Instructions:** Implement `collect_unique(items: &[&str]) -> Vec` that returns the input strings in first-seen order with duplicates removed. The parameter is borrowed (the function does not own the strings), so you must turn each kept `&str` into an owned `String`. `collect_unique(&["a", "b", "a", "c", "b"])` must return `vec!["a", "b", "c"]` (as `String`s). ```rust 1 fn collect_unique(items: &[&str]) -> Vec { 2 // TODO: keep first occurrences, return owned Strings 3 todo!() 4 } 5 6 fn main() { 7 let result = collect_unique(&["a", "b", "a", "c", "b"]); 8 assert_eq!(result, vec!["a".to_string(), "b".to_string(), "c".to_string()]); 9 println!("ok"); 10 } ``` Solution ```rust 1 fn collect_unique(items: &[&str]) -> Vec { 2 let mut out: Vec = Vec::new(); 3 for &item in items { 4 let owned = item.to_string(); // clone the borrowed &str into an owned String 5 if !out.contains(&owned) { 6 out.push(owned); // moved into the Vec; `out` now owns it 7 } 8 } 9 out 10 } 11 12 fn main() { 13 let result = collect_unique(&["a", "b", "a", "c", "b"]); 14 assert_eq!(result, vec!["a".to_string(), "b".to_string(), "c".to_string()]); 15 println!("ok"); 16 } ``` The slice `&[&str]` is borrowed, so the original strings stay owned by the caller. `item.to_string()` allocates an owned `String` (a kind of clone from a borrowed `&str`), which is then *moved* into `out`. The function returns the owned `Vec` by value: ownership transfers to the caller, no borrow-checker gymnastics required. # Reference Counting with `Rc` and `Arc` > Rust's Rc and Arc give a value multiple owners with deterministic cleanup, the explicit counterpart to JavaScript's shared object references and garbage collector. So far every value in Rust has had exactly **one owner**. But some data is genuinely shared — a configuration object read by many parts of a program, a node referenced by several edges in a graph. Reference counting is Rust’s safe, opt-in way to give one value **multiple owners**, with cleanup that still happens automatically and deterministically. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) `Rc` (**reference counted**) and `Arc` (**atomically reference counted**) are smart pointers that let a value have more than one owner. Each holds a count of how many owners currently exist; when the last one goes away, the value is dropped. For a TypeScript/JavaScript developer this feels like normal shared object references, except the “garbage collector” here is a tiny integer counter the compiler manages, with `Rc` for single-threaded code and `Arc` for sharing across threads. > **Note:** This page is a focused, practical introduction. `Rc`/`Arc` are *smart pointers*, and the full family (`Box`, `RefCell`, `Weak`, reference cycles, and the `Deref`/`Drop` machinery behind them) is covered in [Section 10: Smart Pointers](/10-smart-pointers/). Here we cover what you need to share ownership safely and read a strong count. *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) In JavaScript and TypeScript, sharing is the default and invisible. Assigning an object to another variable, pushing it into an array, or storing it on another object all create additional references to the **same** object. The garbage collector (GC) frees it only once nothing can reach it anymore. ```typescript 1 // TypeScript/JavaScript: object references are shared implicitly 2 interface Config { 3 baseUrl: string; 4 timeoutMs: number; 5 } 6 7 const config: Config = { 8 baseUrl: "https://api.example.com", 9 timeoutMs: 5000, 10 }; 11 12 // Every one of these is the SAME object, not a copy: 13 const forLogger = config; 14 const forRetry = config; 15 const sinks = [config, forLogger]; // array also holds the same reference 16 17 forRetry.timeoutMs = 10000; // mutating through one handle... 18 console.log(forLogger.timeoutMs); // 10000 — ...is visible through all of them 19 20 // You never count references and never free anything. When `config`, 21 // `forLogger`, `forRetry`, and `sinks` all become unreachable, the GC 22 // reclaims the object at some unspecified later time. ``` **Key points:** * Assignment shares a reference; there is no count you can see and no `free` to call. * Any handle can mutate the shared object, and the change is visible everywhere. * Cleanup is **non-deterministic** — the GC decides when. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) Rust will not let you alias an owned value freely (that is the whole point of [the ownership rules](/05-ownership/01-ownership-rules/)). To opt into shared ownership, you wrap the value in `Rc` and create additional owners with `Rc::clone`. Each clone bumps a strong count; each drop lowers it; the value is freed when the count hits zero. ```rust 1 use std::rc::Rc; 2 3 #[derive(Debug)] 4 struct Config { 5 base_url: String, 6 timeout_ms: u32, 7 } 8 9 fn main() { 10 // ---- Rc basics: shared ownership, strong count ---- 11 let config = Rc::new(Config { 12 base_url: String::from("https://api.example.com"), 13 timeout_ms: 5000, 14 }); 15 println!("after create: count = {}", Rc::strong_count(&config)); 16 17 let for_logger = Rc::clone(&config); 18 println!("after 1 clone: count = {}", Rc::strong_count(&config)); 19 20 { 21 let for_retry = Rc::clone(&config); 22 println!("inside block: count = {}", Rc::strong_count(&config)); 23 println!("retry sees timeout = {}", for_retry.timeout_ms); 24 } // `for_retry` dropped here -> count goes back down 25 26 println!("after block: count = {}", Rc::strong_count(&config)); 27 println!("logger sees url = {}", for_logger.base_url); 28 } ``` **Output:** ```text 1 after create: count = 1 2 after 1 clone: count = 2 3 inside block: count = 3 4 retry sees timeout = 5000 5 after block: count = 2 6 logger sees url = https://api.example.com ``` **Key points:** * `Rc::new(value)` puts `value` on the heap with a strong count of `1`. * `Rc::clone(&config)` creates another owner and increments the count. It does **not** deep-copy the `Config`. * When `for_retry` goes out of scope at the inner `}`, the count drops back from `3` to `2`, deterministically. * The `Config` is freed exactly when the last `Rc` is dropped — no GC, no leak (caveat: reference cycles, covered below). *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### What `Rc` actually is [Section titled “What Rc\ actually is”](#what-rct-actually-is) `Rc` is a smart pointer that owns a heap allocation containing two things: your value of type `T`, and a small bookkeeping header with the counts. Conceptually: ```text 1 stack heap 2 ┌─────────┐ ┌───────────────────────────────┐ 3 │ config │ ─────▶ │ strong: 2 | weak: 0 | T value │ 4 └─────────┘ └───────────────────────────────┘ 5 ┌─────────┐ ▲ 6 │for_logger│ ─────────┘ (both Rc handles point at the same allocation) 7 └─────────┘ ``` Each `Rc` handle is a pointer; cloning one copies the pointer and bumps `strong` by one. The value `T` is allocated **once** and shared. This is the same layout idea as a JavaScript object reference (multiple variables pointing at one heap object), but the count is explicit and the deallocation is driven by it. ### `Rc::clone` is cheap and explicit [Section titled “Rc::clone is cheap and explicit”](#rcclone-is-cheap-and-explicit) ```rust 1 let for_logger = Rc::clone(&config); ``` Read this as “make another owner of the same allocation.” It copies a pointer and increments an integer — it is **not** the expensive deep `clone()` you saw in [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/). `Rc` does implement the `Clone` trait, so `config.clone()` works and does exactly the same thing. The community convention is to write `Rc::clone(&config)` (the *associated-function* form) rather than `config.clone()`, because it makes clear at the call site that you are cheaply bumping a refcount, not deep-copying the data. You can confirm all clones point at the same heap value: `Rc::as_ptr` returns the address of the inner `T`, and it is identical across clones (the exact address varies per run): ```rust 1 use std::rc::Rc; 2 3 fn main() { 4 let original = Rc::new(vec![10, 20, 30]); 5 let via_method = original.clone(); // calls Rc's Clone impl 6 let via_assoc = Rc::clone(&original); // identical behavior 7 8 println!("original ptr: {:p}", Rc::as_ptr(&original)); 9 println!("method ptr: {:p}", Rc::as_ptr(&via_method)); 10 println!("assoc ptr: {:p}", Rc::as_ptr(&via_assoc)); 11 println!("strong_count = {}", Rc::strong_count(&original)); 12 } ``` All three printed pointers are the same address, and `strong_count` is `3`. No `Vec` was copied. ### The strong count and automatic drop [Section titled “The strong count and automatic drop”](#the-strong-count-and-automatic-drop) `Rc::strong_count(&rc)` reports how many owners exist right now. The count goes up on every clone and down on every drop, including the implicit drop at the end of a scope and the explicit `std::mem::drop` (the same `drop` from [the Drop trait page](/05-ownership/08-drop-trait/)): ```rust 1 use std::rc::Rc; 2 3 fn main() { 4 let data = Rc::new(vec![1, 2, 3]); // count = 1 5 let a = Rc::clone(&data); // count = 2 6 let b = Rc::clone(&data); // count = 3 7 println!("after two clones: {}", Rc::strong_count(&data)); 8 9 drop(a); // count = 2 10 println!("after drop(a): {}", Rc::strong_count(&data)); 11 12 drop(b); // count = 1 13 println!("after drop(b): {}", Rc::strong_count(&data)); 14 15 println!("data still usable: {:?}", data); 16 } ``` **Output:** ```text 1 after two clones: 3 2 after drop(a): 2 3 after drop(b): 1 4 data still usable: [1, 2, 3] ``` When the count would reach `0`, the inner `Vec` is dropped and its heap memory freed. This is still **deterministic** RAII (it happens at a point you can identify by reading the code); it is just that “the owner” is now “the last surviving handle” instead of a single binding. ### Shared means read-only [Section titled “Shared means read-only”](#shared-means-read-only) `Rc` gives you shared ownership, and shared access in Rust means **immutable** access (the same one-mutable-XOR-many-shared rule from [Mutable References](/05-ownership/03-mutable-references/), now enforced at the type level). You cannot get a `&mut T` out of an `Rc` while it might be shared, so you cannot mutate the inner value directly. To have shared *and* mutable data, you combine `Rc` with a cell type that provides **interior mutability**, almost always `RefCell` (single-threaded): ```rust 1 use std::cell::RefCell; 2 use std::rc::Rc; 3 4 fn main() { 5 // Shared, mutable counter: many handles, interior mutability via RefCell. 6 let hits = Rc::new(RefCell::new(0u32)); 7 8 let a = Rc::clone(&hits); 9 let b = Rc::clone(&hits); 10 11 *a.borrow_mut() += 1; // mutate through one handle 12 *b.borrow_mut() += 1; // mutate through another 13 14 println!("hits = {}", hits.borrow()); 15 println!("strong_count = {}", Rc::strong_count(&hits)); 16 } ``` **Output:** ```text 1 hits = 2 2 strong_count = 3 ``` `RefCell` moves the borrow-checking from compile time to *runtime* (it panics if you break the borrowing rules), and it is the standard partner for `Rc`. The full `RefCell` story lives in [Section 10](/10-smart-pointers/); the takeaway here is the pattern `Rc>` for shared mutable state on one thread. ### `Arc`: the same idea, safe across threads [Section titled “Arc\: the same idea, safe across threads”](#arct-the-same-idea-safe-across-threads) `Rc` deliberately uses a **non-atomic** counter, which is fast but not safe to touch from multiple threads. The compiler enforces this: an `Rc` is not `Send`, so you cannot move one into another thread. When you need to share across threads, switch to `Arc` — identical API, but the count is updated with **atomic** operations: ```rust 1 use std::sync::Arc; 2 use std::thread; 3 4 #[derive(Debug)] 5 struct Settings { 6 workers: u32, 7 region: String, 8 } 9 10 fn main() { 11 let settings = Arc::new(Settings { 12 workers: 4, 13 region: String::from("eu-west-1"), 14 }); 15 16 let mut handles = Vec::new(); 17 for id in 0..3 { 18 let settings = Arc::clone(&settings); // each thread gets its own handle 19 let handle = thread::spawn(move || { 20 println!( 21 "worker {id} reading region={} workers={}", 22 settings.region, settings.workers 23 ); 24 }); 25 handles.push(handle); 26 } 27 28 for handle in handles { 29 handle.join().unwrap(); 30 } 31 32 println!("final strong_count = {}", Arc::strong_count(&settings)); 33 } ``` **Output** (the three worker lines may appear in any order because they run concurrently; the final count is always `1`): ```text 1 worker 0 reading region=eu-west-1 workers=4 2 worker 1 reading region=eu-west-1 workers=4 3 worker 2 reading region=eu-west-1 workers=4 4 final strong_count = 1 ``` Each `Arc::clone` gives a thread its own owner; the `move` closure takes that clone with it. After all threads `join`, their clones have been dropped, so the count is back to `1` (just the original binding). For shared *mutable* state across threads you pair `Arc` with `Mutex` or `RwLock` (`Arc>`), the thread-safe analog of `Rc>`. Threads and these locks are the subject of [Section 26: Systems Programming](/26-systems-programming/); for now, just remember the swap: **`Rc` + `RefCell`** on one thread, **`Arc` + `Mutex`** across threads. > **Note:** Rust’s compiler will *stop you* from using `Rc` across threads — you don’t have to remember the rule. The error (`Rc<...> cannot be sent between threads safely`) is shown in the Pitfalls section below. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript / JavaScript | Rust `Rc` / `Arc` | | ----------------------- | ------------------------------------------ | --------------------------------------------------------- | | Shared ownership | Default and implicit (object references) | Opt-in: wrap in `Rc`/`Arc` and `clone` | | How sharing happens | Assignment / passing aliases the object | `Rc::clone(&x)` bumps a strong count | | Cleanup trigger | GC decides when nothing can reach it | Strong count reaches `0` → value dropped | | Cleanup timing | Non-deterministic | Deterministic (at the drop of the last owner) | | Mutation of shared data | Any handle can mutate freely | Shared = immutable; need `RefCell`/`Mutex` for mutation | | Cost of a “reference” | Hidden GC bookkeeping + tracing | One pointer + one integer increment/decrement | | Thread safety | Single-threaded model (event loop) | `Rc` single-thread only; `Arc` for threads (atomic count) | | Cycles | Collected by the GC’s reachability tracing | **Leak** unless broken with `Weak` | **The core mental shift:** in JavaScript, *every* object reference is traced for you (the engine periodically walks reachability and frees what it can no longer reach), invisibly, by the runtime. In Rust, the default is single ownership with zero runtime cost; `Rc`/`Arc` is how you *deliberately* buy shared ownership, and you pay a small, visible price (a counter, an allocation header, and, for `Arc`, atomic operations). > **Tip:** Reach for `Rc`/`Arc` only when ownership is *genuinely* shared and you cannot express the relationship with a plain borrow (`&T`). Many designs that look like they need shared ownership are actually fine with borrowing plus [lifetimes](/05-ownership/04-lifetimes/). Shared ownership is a tool, not a default. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Trying to mutate the value inside an `Rc` [Section titled “Pitfall 1: Trying to mutate the value inside an Rc”](#pitfall-1-trying-to-mutate-the-value-inside-an-rc) Coming from JavaScript, you expect `rc.field = x` or `*rc += 1` to just work, because shared objects are mutable there. In Rust, `Rc` only gives shared (immutable) access: ```rust 1 use std::rc::Rc; 2 3 fn main() { 4 let count = Rc::new(0u32); 5 let _other = Rc::clone(&count); 6 *count += 1; // does not compile (error[E0594]): can't mutate through Rc 7 println!("{count}"); 8 } ``` Real compiler output: ```text 1 error[E0594]: cannot assign to data in an `Rc` 2 --> src/main.rs:6:5 3 | 4 6 | *count += 1; // does not compile (error[E0594]): can't mutate through Rc 5 | ^^^^^^^^^^^ cannot assign 6 | 7 = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc` ``` **Fix:** wrap the inner value in a cell type for interior mutability, `Rc>` (single-threaded) or `Arc>` (across threads), then mutate through `.borrow_mut()` / `.lock()`. See the `Rc>` example above. ### Pitfall 2: Sending an `Rc` to another thread [Section titled “Pitfall 2: Sending an Rc to another thread”](#pitfall-2-sending-an-rc-to-another-thread) `Rc` uses a non-atomic counter, so the compiler refuses to move it into a thread: ```rust 1 use std::rc::Rc; 2 use std::thread; 3 4 fn main() { 5 let shared = Rc::new(String::from("config")); 6 let handle = Rc::clone(&shared); 7 thread::spawn(move || { 8 println!("{handle}"); // does not compile (error[E0277]): Rc is not Send 9 }); 10 } ``` Real compiler output (trimmed): ```text 1 error[E0277]: `Rc` cannot be sent between threads safely 2 --> src/main.rs:7:19 3 | 4 7 | thread::spawn(move || { 5 | ------------- ^------ 6 | | | 7 | _____|_____________within this `{closure@src/main.rs:7:19: 7:26}` 8 | | | 9 | | required by a bound introduced by this call 10 ... | 11 | |_____^ `Rc` cannot be sent between threads safely 12 | 13 = help: within `{closure@src/main.rs:7:19: 7:26}`, the trait `Send` is not implemented for `Rc` ``` **Fix:** use `Arc` instead of `Rc`. The API is identical; just swap the type and `use std::sync::Arc;`. ### Pitfall 3: Reference cycles leak memory [Section titled “Pitfall 3: Reference cycles leak memory”](#pitfall-3-reference-cycles-leak-memory) This is the one case where reference counting fails to clean up. If two `Rc`s point at each other (directly or through a chain), their counts never reach zero, so neither is ever dropped — a real memory **leak**, even in safe Rust: ```rust 1 use std::cell::RefCell; 2 use std::rc::Rc; 3 4 struct Node { 5 name: String, 6 next: RefCell>>, 7 } 8 9 impl Drop for Node { 10 fn drop(&mut self) { 11 println!("dropping node {}", self.name); 12 } 13 } 14 15 fn main() { 16 let a = Rc::new(Node { name: String::from("A"), next: RefCell::new(None) }); 17 let b = Rc::new(Node { name: String::from("B"), next: RefCell::new(None) }); 18 19 // Create a cycle: a -> b -> a 20 *a.next.borrow_mut() = Some(Rc::clone(&b)); 21 *b.next.borrow_mut() = Some(Rc::clone(&a)); 22 23 println!("a strong_count = {}", Rc::strong_count(&a)); 24 println!("b strong_count = {}", Rc::strong_count(&b)); 25 println!("end of main — watch for drop messages..."); 26 } // a and b go out of scope, but each is still held by the other -> NO drops -> leak ``` **Output** (note: the `dropping node` messages **never print**; the nodes leak): ```text 1 a strong_count = 2 2 b strong_count = 2 3 end of main — watch for drop messages... ``` This compiles and runs fine; it is not a crash, just a leak. A JavaScript GC would collect this cycle via reachability tracing; reference counting alone cannot. **Fix:** make one direction of the cycle a **non-owning** reference using `Weak` (a weak handle that does not contribute to the strong count). `Weak`, cycles, and the parent/child pattern are covered in depth in [Section 10: Smart Pointers](/10-smart-pointers/). ### Pitfall 4: Using `.clone()` and not realizing it is cheap (or expecting it to be cheap when it isn’t) [Section titled “Pitfall 4: Using .clone() and not realizing it is cheap (or expecting it to be cheap when it isn’t)”](#pitfall-4-using-clone-and-not-realizing-it-is-cheap-or-expecting-it-to-be-cheap-when-it-isnt) Two confusions, opposite directions: * On an `Rc`, calling `.clone()` is cheap — it bumps a count, it does not copy `BigThing`. New Rust developers sometimes avoid it thinking it is expensive. * On a plain `BigThing` (no `Rc`), `.clone()` *is* a deep copy. Wrapping in `Rc` is precisely how you turn an expensive clone into a cheap one when the data is shared and read-only. **Fix:** prefer the explicit `Rc::clone(&x)` / `Arc::clone(&x)` form so reviewers can see at a glance that a clone is a refcount bump, not a deep copy. *** ## Best Practices [Section titled “Best Practices”](#best-practices) * **Borrow first; reach for `Rc`/`Arc` only for genuine shared ownership.** If a single owner with borrowed access (`&T`) models your data, use that; it is faster and has no runtime bookkeeping. Use `Rc`/`Arc` when *multiple parts of the program must independently keep the value alive* and you cannot tie them to a single owner’s lifetime. * **Use the associated-function clone form.** Write `Rc::clone(&x)` / `Arc::clone(&x)`, not `x.clone()`, so the intent (“share, don’t deep-copy”) is obvious at the call site. This is the idiom recommended by the Rust Book. * **Pick `Rc` by default, upgrade to `Arc` only when crossing threads.** `Arc`’s atomic counter is slightly slower; don’t pay for thread safety you don’t use. And you won’t accidentally use the wrong one — the compiler enforces it via `Send`/`Sync`. * **Combine with the right cell for mutation.** `Rc>` for single-threaded shared-mutable; `Arc>` (or `Arc>`) across threads. Keep the locked/borrowed region small. * **Watch for cycles in graph-shaped data.** Parent→child links can own (`Rc`/`Arc`); child→parent back-links should be `Weak` to avoid leaks. If you find yourself building doubly-linked structures, design the ownership direction up front. * **Reading the count is for diagnostics, not control flow.** `strong_count` is great in tests and logging, but in concurrent code it can change the instant after you read it. Don’t branch on it to decide whether you are the “last” owner; use the value’s own logic instead. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A common production scenario: a parsed form/schema where many fields reuse the **same** validated field type. Instead of cloning the `FieldType` into every `Field` (wasteful) or threading borrows and lifetimes through the whole structure (awkward when fields are built dynamically), you share one canonical `FieldType` via `Rc`. Every field is an independent owner of the shared type; the type is freed only when the last field referencing it is gone. ```rust 1 use std::rc::Rc; 2 3 /// A reusable field definition that several form sections share. 4 #[derive(Debug)] 5 struct FieldType { 6 name: String, 7 max_len: usize, 8 } 9 10 #[derive(Debug)] 11 struct Field { 12 label: String, 13 ty: Rc, // shared, not owned exclusively 14 } 15 16 fn main() { 17 // One canonical "email" type, shared by every field that is an email. 18 let email_type = Rc::new(FieldType { 19 name: String::from("email"), 20 max_len: 254, 21 }); 22 23 let fields = vec![ 24 Field { label: String::from("Primary email"), ty: Rc::clone(&email_type) }, 25 Field { label: String::from("Billing email"), ty: Rc::clone(&email_type) }, 26 Field { label: String::from("Backup email"), ty: Rc::clone(&email_type) }, 27 ]; 28 29 for field in &fields { 30 println!( 31 "{:<14} -> type '{}' (max {} chars)", 32 field.label, field.ty.name, field.ty.max_len 33 ); 34 } 35 36 // 3 fields each hold a handle + the original `email_type` binding = 4. 37 println!("shared FieldType strong_count = {}", Rc::strong_count(&email_type)); 38 } ``` **Output:** ```text 1 Primary email -> type 'email' (max 254 chars) 2 Billing email -> type 'email' (max 254 chars) 3 Backup email -> type 'email' (max 254 chars) 4 shared FieldType strong_count = 4 ``` **Why this is idiomatic:** * The `FieldType` is allocated **once**. Three `Field`s and the original binding share it; no string is deep-copied per field. * Each `Field` is a real, independent owner — a field can be moved into another collection, stored, or dropped without invalidating the others, because the `Rc` keeps the shared type alive until the last owner is gone. * `strong_count` is `4` because there are four live owners: the original `email_type` plus one inside each of the three fields. When `fields` and `email_type` go out of scope, the count walks down to `0` and the `FieldType` is freed — deterministically, no GC. * If this were a read-heavy server reused across requests/threads, you would change `Rc` to `Arc` and the rest of the code would stay the same. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — `Rc`, the Reference Counted Smart Pointer](https://doc.rust-lang.org/book/ch15-04-rc.html) * [The Rust Book — Reference Cycles Can Leak Memory](https://doc.rust-lang.org/book/ch15-06-reference-cycles.html) (`Weak`) * [The Rust Book — Shared-State Concurrency](https://doc.rust-lang.org/book/ch16-03-shared-state.html) (`Arc>`) * [`std::rc::Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html) and [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) — API reference. ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [The Three Ownership Rules](/05-ownership/01-ownership-rules/): single ownership, the rule `Rc`/`Arc` deliberately relaxes. * [Borrowing](/05-ownership/02-borrowing/) — try a borrow before reaching for shared ownership. * [Mutable References](/05-ownership/03-mutable-references/): the shared-XOR-mutable rule that makes `Rc` read-only. * [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/) — why a *deep* clone differs from an `Rc::clone` count bump. * [Lifetimes](/05-ownership/04-lifetimes/): the borrow-based alternative to shared ownership. * [The Drop Trait and RAII](/05-ownership/08-drop-trait/) — what runs when the last `Rc` count hits zero. * [Section 10: Smart Pointers](/10-smart-pointers/). The full treatment: `Box`, `RefCell`, `Weak`, breaking cycles, `Deref`. * [Section 06: Data Structures](/06-data-structures/) — building structs and enums that hold shared data. * [Variables and Mutability](/02-basics/00-variables/) — immutability by default, the backdrop to “shared = immutable”. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1 [Section titled “Exercise 1”](#exercise-1) **Difficulty:** Easy **Objective:** Create multiple owners of one value and observe the strong count. **Instructions:** Wrap a `Palette` (with a `name: String` and `colors: Vec`) in an `Rc`. Make two additional owners called `header` and `footer` with `Rc::clone`. Print the palette name from each handle, print the colors once, and finally print the strong count (it should be `3`). ```rust 1 use std::rc::Rc; 2 3 #[derive(Debug)] 4 struct Palette { 5 name: String, 6 colors: Vec, 7 } 8 9 fn main() { 10 let palette = Rc::new(Palette { 11 name: String::from("ocean"), 12 colors: vec![String::from("#012"), String::from("#089")], 13 }); 14 // TODO: make `header` and `footer` owners, print names, colors, and the count 15 } ``` Solution ```rust 1 use std::rc::Rc; 2 3 #[derive(Debug)] 4 struct Palette { 5 name: String, 6 colors: Vec, 7 } 8 9 fn main() { 10 let palette = Rc::new(Palette { 11 name: String::from("ocean"), 12 colors: vec![String::from("#012"), String::from("#089")], 13 }); 14 15 let header = Rc::clone(&palette); 16 let footer = Rc::clone(&palette); 17 18 println!("header uses palette '{}'", header.name); 19 println!("footer uses palette '{}'", footer.name); 20 println!("colors: {:?}", palette.colors); 21 println!("strong_count = {}", Rc::strong_count(&palette)); 22 } ``` Output: ```text 1 header uses palette 'ocean' 2 footer uses palette 'ocean' 3 colors: ["#012", "#089"] 4 strong_count = 3 ``` There are three owners: the original `palette` plus the two clones. None of them deep-copied the `Palette`; they all point at the same heap allocation. ### Exercise 2 [Section titled “Exercise 2”](#exercise-2) **Difficulty:** Medium **Objective:** Predict how the strong count changes as owners are dropped early with `drop`. **Instructions:** Create an `Rc>`, clone it twice into `a` and `b`, then `drop(a)` and `drop(b)` one at a time. **Before running it, write down** the count after each step. Then print the count at each step to confirm, and show the data is still usable after both clones are dropped. ```rust 1 use std::rc::Rc; 2 3 fn main() { 4 let data = Rc::new(vec![1, 2, 3]); 5 let a = Rc::clone(&data); 6 let b = Rc::clone(&data); 7 // TODO: print the count, drop(a), print, drop(b), print, then use `data` 8 } ``` Solution Predicted counts: `3` after two clones, `2` after `drop(a)`, `1` after `drop(b)`. ```rust 1 use std::rc::Rc; 2 3 fn main() { 4 let data = Rc::new(vec![1, 2, 3]); // count = 1 5 let a = Rc::clone(&data); // count = 2 6 let b = Rc::clone(&data); // count = 3 7 println!("after two clones: {}", Rc::strong_count(&data)); 8 9 drop(a); // count = 2 10 println!("after drop(a): {}", Rc::strong_count(&data)); 11 12 drop(b); // count = 1 13 println!("after drop(b): {}", Rc::strong_count(&data)); 14 15 println!("data still usable: {:?}", data); 16 } ``` Output: ```text 1 after two clones: 3 2 after drop(a): 2 3 after drop(b): 1 4 data still usable: [1, 2, 3] ``` `drop` (from [the Drop trait page](/05-ownership/08-drop-trait/)) decrements the strong count. Because `data` itself is still in scope, the count never reaches `0` and the `Vec` stays alive and usable. ### Exercise 3 [Section titled “Exercise 3”](#exercise-3) **Difficulty:** Medium/Hard **Objective:** Share a *mutable* counter across threads with `Arc>`. **Instructions:** Spawn 5 threads that each increment a shared `u32` counter by `1`. Use `Arc` to share ownership across threads and `Mutex` to make the mutation safe. `join` all threads, then print the final count (it must be `5`) and the strong count after the threads finish (it should be `1`). > **Hint:** Clone the `Arc` once per thread *before* the `move` closure captures it. Lock with `counter.lock().unwrap()` to get a mutable guard, then `*guard += 1`. Solution ```rust 1 use std::sync::{Arc, Mutex}; 2 use std::thread; 3 4 fn main() { 5 let counter = Arc::new(Mutex::new(0u32)); 6 let mut handles = Vec::new(); 7 8 for _ in 0..5 { 9 let counter = Arc::clone(&counter); // each thread gets its own owner 10 handles.push(thread::spawn(move || { 11 let mut guard = counter.lock().unwrap(); // lock for safe mutation 12 *guard += 1; 13 })); // guard dropped here -> lock released 14 } 15 16 for h in handles { 17 h.join().unwrap(); 18 } 19 20 println!("final count = {}", *counter.lock().unwrap()); 21 println!("strong_count = {}", Arc::strong_count(&counter)); 22 } ``` Output: ```text 1 final count = 5 2 strong_count = 1 ``` `Arc` provides shared *ownership* across threads; `Mutex` provides shared *mutability* by handing out one exclusive guard at a time. After all threads `join`, every per-thread `Arc` clone has been dropped, so the count is back to `1`. Swapping `Arc` for `Rc` here would not compile: `Rc` is not `Send`. The `Mutex`/`Arc` combination is explored further in [Section 26: Systems Programming](/26-systems-programming/). # The Drop Trait and RAII > Rust's Drop trait and RAII free resources deterministically at scope exit, replacing JavaScript's garbage collector and manual try/finally cleanup with a guarantee. Ownership Rule 3 says a value is dropped when its owner goes out of scope. The **`Drop` trait** is how you hook into that moment to run custom cleanup, and **RAII** (Resource Acquisition Is Initialization) is the pattern that falls out of it: tie a resource’s lifetime to a value’s scope and let the compiler free it for you, deterministically, at a point you can read off the source. *** ## Quick Overview [Section titled “Quick Overview”](#quick-overview) In Rust, cleanup is **deterministic and scope-bound**: the compiler inserts a call to a value’s destructor at the exact `}` where its owner goes out of scope, with no runtime tracking and no garbage collector. You implement the `Drop` trait to say *what* should happen at that moment (close a file, release a lock, roll back a transaction), and you can force cleanup *early* with `std::mem::drop`. For a TypeScript/JavaScript developer, this is the opposite of the garbage collector: instead of “freed eventually, at a time the engine chooses,” Rust gives you “freed *here*, every time.” > **Note:** This page is about the cleanup *mechanism*: the `Drop` trait, RAII, drop order, and `std::mem::drop`. The ownership Rule 3 that triggers drops lives in [The Three Ownership Rules](/05-ownership/01-ownership-rules/); the stack/heap split that explains *what* gets freed is in [Stack and Heap](/05-ownership/00-stack-heap/). *** ## TypeScript/JavaScript Example [Section titled “TypeScript/JavaScript Example”](#typescriptjavascript-example) In JavaScript and TypeScript, you never decide *when* an object’s memory is reclaimed. The **garbage collector** frees objects at some unspecified time after they become unreachable. For non-memory resources (files, sockets, locks), the convention is a manual `close()`/`dispose()` you must remember to call, usually wrapped in `try/finally`. ```typescript 1 // TypeScript/JavaScript: cleanup is either manual (try/finally) or non-deterministic (GC). 2 class DbConnection { 3 constructor(public readonly id: number) { 4 console.log(`[conn ${id}] opening`); 5 } 6 7 close(): void { 8 console.log(`[conn ${this.id}] closing`); 9 } 10 } 11 12 function handleRequest(): void { 13 const conn = new DbConnection(1); 14 try { 15 console.log("running query..."); 16 // ... work that might throw ... 17 } finally { 18 conn.close(); // YOU must remember this, in every exit path 19 } 20 } 21 22 handleRequest(); 23 24 // For pure-memory objects, there is no close() — the GC frees them "eventually". 25 // FinalizationRegistry can run a callback after collection, but the docs are explicit: 26 // it offers NO timing guarantee and the callback may never run at all. 27 const registry = new FinalizationRegistry((label: string) => { 28 console.log(`finalized: ${label}`); // may fire late, or never 29 }); 30 let cache: object | null = { big: "buffer" }; 31 registry.register(cache, "cache"); 32 cache = null; // now unreachable — but when is it collected? Unknown. ``` **Key points:** * Memory cleanup is **non-deterministic**: the GC runs on its own schedule. * Non-memory resources need a **manual** `close()`/`dispose()`, and you must call it on every exit path (hence `try/finally`). * `FinalizationRegistry` is explicitly documented as offering **no timing guarantees** and may never run; it is a last-ditch safety net, not a destructor. * Forgetting a `close()` leaks the resource (a file descriptor, a held lock) even though the memory is eventually GC’d. *** ## Rust Equivalent [Section titled “Rust Equivalent”](#rust-equivalent) Rust ties cleanup to scope. You implement `Drop::drop` once, and the compiler runs it automatically at the closing `}`, on *every* exit path, including early returns and panics. No `try/finally`, no `close()` to forget. ```rust 1 struct DbConnection { 2 id: u32, 3 } 4 5 impl DbConnection { 6 fn open(id: u32) -> DbConnection { 7 println!("[conn {id}] opening"); 8 DbConnection { id } 9 } 10 } 11 12 impl Drop for DbConnection { 13 fn drop(&mut self) { 14 println!("[conn {}] closing (Drop ran)", self.id); 15 } 16 } 17 18 fn main() { 19 println!("-- start of main --"); 20 let _primary = DbConnection::open(1); 21 22 { 23 let _scratch = DbConnection::open(2); 24 println!("-- inside inner block --"); 25 } // `_scratch` dropped HERE — exactly at this `}` 26 27 println!("-- back in main --"); 28 } // `_primary` dropped HERE — at the end of main ``` **Output:** ```plaintext 1 -- start of main -- 2 [conn 1] opening 3 [conn 2] opening 4 -- inside inner block -- 5 [conn 2] closing (Drop ran) 6 -- back in main -- 7 [conn 1] closing (Drop ran) ``` **Key points:** * `impl Drop for DbConnection` defines a destructor; its `drop(&mut self)` body is the cleanup logic. * `_scratch` is cleaned up at the end of its inner block, *before* `-- back in main --` prints. Cleanup is scope-bound, not deferred. * `_primary` is dropped at the end of `main`. You never call `.close()`; the compiler inserts the destructor call. * This is **RAII**: acquiring the `DbConnection` in `open` *is* the act that schedules its release. > **Note:** Most types you use (`String`, `Vec`, `Box`, `File`, `MutexGuard`) already implement `Drop` (or contain things that do). You only write your own `impl Drop` when you have a resource that needs custom teardown. Freeing a `String`’s heap buffer happens automatically; you don’t implement `Drop` for that. *** ## Detailed Explanation [Section titled “Detailed Explanation”](#detailed-explanation) ### The `Drop` trait [Section titled “The Drop trait”](#the-drop-trait) `Drop` is a trait from the standard library with a single method: ```rust 1 // (from std — shown for reference, not to be re-defined) 2 pub trait Drop { 3 fn drop(&mut self); 4 } ``` When a value that implements `Drop` goes out of scope, the compiler inserts a call to `drop(&mut self)`. A few things are worth nailing down: * `drop` takes `&mut self`, not `self`. The value is *already being destroyed*; you get a mutable borrow so you can inspect and tear down its fields, but you cannot move the value out of `self` inside `drop`. * You **cannot call `value.drop()` yourself** — the compiler forbids the explicit destructor call (more on that under Common Pitfalls). To trigger cleanup early, you use the free function `std::mem::drop`, covered below. * After your `drop` body runs, the compiler then automatically drops each *field* of the value, recursively. So a `String` field’s heap buffer is freed for you even though your `drop` body didn’t mention it. ### Cleanup runs on every exit path [Section titled “Cleanup runs on every exit path”](#cleanup-runs-on-every-exit-path) The compiler inserts the destructor call wherever the owner’s scope ends: at a literal closing brace you can see, but equally at early `return`s and even during a panic unwind. Here a value moved *into* a function is dropped inside that function, not back in the caller: ```rust 1 struct Loud(&'static str); 2 impl Drop for Loud { 3 fn drop(&mut self) { 4 println!("drop {}", self.0); 5 } 6 } 7 8 fn consume(item: Loud) { 9 println!("consume() received {}", item.0); 10 } // `item` dropped HERE, inside consume 11 12 fn main() { 13 let x = Loud("x"); 14 println!("before consume"); 15 consume(x); // `x` MOVED into consume; it is dropped there, not in main 16 println!("after consume (x already dropped)"); 17 } ``` **Output:** ```plaintext 1 before consume 2 consume() received x 3 drop x 4 after consume (x already dropped) ``` Because ownership moved into `consume`, that function’s scope now owns the value, so the drop happens at the end of `consume`, before `after consume` prints. (Moves are the subject of [The Three Ownership Rules](/05-ownership/01-ownership-rules/) and [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/); the takeaway here is that *the current owner’s scope* decides when the drop fires.) ### Drop order: bindings are LIFO [Section titled “Drop order: bindings are LIFO”](#drop-order-bindings-are-lifo) Within a single scope, local variables are dropped in **reverse order of declaration**: last declared, first dropped, like popping a stack: ```rust 1 struct Tracer(&'static str); 2 impl Drop for Tracer { 3 fn drop(&mut self) { 4 println!("dropping {}", self.0); 5 } 6 } 7 8 fn main() { 9 let _a = Tracer("a"); 10 let _b = Tracer("b"); 11 let _c = Tracer("c"); 12 println!("all three created"); 13 } // dropped in reverse: c, then b, then a ``` **Output:** ```plaintext 1 all three created 2 dropping c 3 dropping b 4 dropping a ``` This reverse order matters: if `_b` depends on `_a` (say `_a` is a connection and `_b` a transaction on it), declaring `_a` first guarantees the transaction is torn down *before* the connection it relies on. ### Drop order: a value’s own `drop`, then its fields — in declaration order [Section titled “Drop order: a value’s own drop, then its fields — in declaration order”](#drop-order-a-values-own-drop-then-its-fields--in-declaration-order) Nested values follow a different rule from local bindings. When a struct is dropped, **its own `drop` runs first**, and then **its fields are dropped in declaration order** (top to bottom): ```rust 1 struct Noisy(&'static str); 2 impl Drop for Noisy { 3 fn drop(&mut self) { 4 println!("drop {}", self.0); 5 } 6 } 7 8 #[allow(dead_code)] 9 struct Wrapper { 10 first: Noisy, 11 second: Noisy, 12 } 13 impl Drop for Wrapper { 14 fn drop(&mut self) { 15 println!("drop Wrapper"); 16 } 17 } 18 19 fn main() { 20 let _w = Wrapper { 21 first: Noisy("first"), 22 second: Noisy("second"), 23 }; 24 println!("created wrapper"); 25 } ``` **Output:** ```plaintext 1 created wrapper 2 drop Wrapper 3 drop first 4 drop second ``` So: the outer value’s `drop` body (`drop Wrapper`) runs first, then the fields drop **in declaration order** (`first` before `second`). Note this is *forward* order, the opposite of the LIFO order for separate `let` bindings. Elements of a `Vec` likewise drop in index order when the `Vec` is dropped. > **Tip:** Don’t memorize edge cases. Remember the principle: a composite is torn down *outermost-first* (its own `drop`, then its parts), and *separate local bindings* unwind like a stack (last-in, first-out). Code that relies on subtle drop-order details is usually fragile; prefer explicit ordering with `std::mem::drop` when order truly matters. ### `std::mem::drop`: releasing early [Section titled “std::mem::drop: releasing early”](#stdmemdrop-releasing-early) Sometimes scope is too coarse — you want a resource gone *before* the end of the block (release a lock before doing slow work, free a big buffer before a long phase). The standard library provides a free function, in the prelude, that does exactly this: ```rust 1 // This is essentially all std::mem::drop is: 2 fn drop(_value: T) {} // takes ownership by value, then its scope ends immediately ``` It takes the value **by value** (a move), so the value is now owned by `drop`’s parameter, whose scope ends instantly, running the destructor. It’s already in the prelude, so you write `drop(x)`, not `std::mem::drop(x)`: ```rust 1 struct Guard(&'static str); 2 impl Drop for Guard { 3 fn drop(&mut self) { 4 println!("releasing {}", self.0); 5 } 6 } 7 8 fn main() { 9 let lock = Guard("mutex"); 10 println!("got the lock, doing critical work"); 11 12 drop(lock); // explicitly release NOW, before the end of scope 13 println!("lock released early; doing non-critical work"); 14 } // nothing left to drop here — `lock` is already gone ``` **Output:** ```plaintext 1 got the lock, doing critical work 2 releasing mutex 3 lock released early; doing non-critical work ``` The magic is unremarkable: `drop` does nothing in its body. The cleanup happens because the value was *moved* into a scope that ends immediately. After `drop(lock)`, the binding `lock` is no longer usable; using it is a use-after-move error, which is exactly what you want. *** ## Key Differences [Section titled “Key Differences”](#key-differences) | Concept | TypeScript / JavaScript | Rust | | ---------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------- | | When memory is freed | Non-deterministically, by the GC | Deterministically, at the owner’s scope exit | | Destructor for resources | Manual `close()` / `dispose()`, often in `try/finally` | `Drop::drop`, run automatically by the compiler | | Runs on early return / throw | Only if you wrote `finally` | Always, including during panic unwind | | Forgetting cleanup | Leaks the resource (FD, lock) | Impossible to forget; it’s tied to scope | | Finalizers | `FinalizationRegistry`: no timing guarantee, may never run | `Drop`: precise, guaranteed, source-visible | | Force cleanup now | Call `close()` and null the reference | `std::mem::drop(value)` (a move) | | Cleanup order | Whatever order you write `close()` calls | Bindings LIFO; a value’s `drop` then its fields in declaration order | | Runtime cost | GC tracing, pauses, allocation headers | Zero — destructor calls are inserted at compile time | **The core mental shift:** JavaScript’s `FinalizationRegistry` looks like a destructor but is the opposite of one: it is a *best-effort, no-guarantee* callback. Rust’s `Drop` is a *hard guarantee* fired at a *known point*. RAII replaces the discipline of “remember to call `close()` on every path” with “the type system frees it for you, always.” > **Warning:** Do not reach for `Drop` as a place to run important *application* logic like flushing a network buffer where errors matter: `drop` cannot return a `Result` or fail gracefully, and during a panic it runs mid-unwind. Use it for *resource release* (free, close, unlock). For fallible teardown, expose an explicit method (e.g. `fn close(self) -> io::Result<()>`) and use `Drop` only as a backstop. *** ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### Pitfall 1: Calling `.drop()` directly [Section titled “Pitfall 1: Calling .drop() directly”](#pitfall-1-calling-drop-directly) Coming from a `dispose()`/`close()` mindset, the natural instinct is to call the destructor by name. Rust forbids it, because that would let the value be destroyed twice (once by you, once by the automatic end-of-scope drop): a double-free. ```rust 1 struct Guard(&'static str); 2 impl Drop for Guard { 3 fn drop(&mut self) { 4 println!("releasing {}", self.0); 5 } 6 } 7 8 fn main() { 9 let lock = Guard("mutex"); 10 lock.drop(); // does not compile (error[E0040]): explicit destructor call 11 } ``` Real compiler output: ```plaintext 1 error[E0040]: explicit use of destructor method 2 --> src/main.rs:10:10 3 | 4 10 | lock.drop(); // does not compile (error[E0040]): explicit destructor call 5 | ^^^^ explicit destructor calls not allowed 6 | 7 help: consider using `drop` function 8 | 9 10 - lock.drop(); // does not compile (error[E0040]): explicit destructor call 10 10 + drop(lock); // does not compile (error[E0040]): explicit destructor call 11 | ``` **Fix:** use the free function `drop(lock)` (the compiler even suggests it). It moves the value in and lets its scope end, running the destructor exactly once. ### Pitfall 2: Using a value after `drop(value)` [Section titled “Pitfall 2: Using a value after drop(value)”](#pitfall-2-using-a-value-after-dropvalue) `drop` takes ownership, so after `drop(x)` the binding `x` is moved-from and can no longer be used. This trips up developers expecting `x` to merely be “cleared” or “nulled” the way `x = null` works in JavaScript. ```rust 1 struct FileHandle { 2 path: String, 3 } 4 impl Drop for FileHandle { 5 fn drop(&mut self) { 6 println!("closing {}", self.path); 7 } 8 } 9 10 fn main() { 11 let f = FileHandle { path: String::from("/tmp/log.txt") }; 12 drop(f); // ownership moved into drop(); `f` is now gone 13 println!("path was {}", f.path); // does not compile (error[E0382]): use after move 14 } ``` Real compiler output (trimmed): ```plaintext 1 error[E0382]: borrow of moved value: `f` 2 --> src/main.rs:13:29 3 | 4 11 | let f = FileHandle { path: String::from("/tmp/log.txt") }; 5 | - move occurs because `f` has type `FileHandle`, which does not implement the `Copy` trait 6 12 | drop(f); // ownership moved into drop(); `f` is now gone 7 | - value moved here 8 13 | println!("path was {}", f.path); // does not compile (error[E0382]): use after move 9 | ^^^^^^ value borrowed here after move ``` **Fix:** read anything you need from the value *before* dropping it, or simply let it drop naturally at the end of scope. There is no valid “use after explicit drop.” ### Pitfall 3: Trying to make a `Drop` type `Copy` [Section titled “Pitfall 3: Trying to make a Drop type Copy”](#pitfall-3-trying-to-make-a-drop-type-copy) A `Copy` type is duplicated bitwise with no notion of a unique owner, but a destructor exists precisely to clean up a *unique* resource. The two are mutually exclusive, and the compiler says so. ```rust 1 #[derive(Copy, Clone)] // does not compile (error[E0184]) 2 struct Token { 3 value: u32, 4 } 5 6 impl Drop for Token { 7 fn drop(&mut self) { 8 println!("dropping token {}", self.value); 9 } 10 } 11 12 fn main() { 13 let _t = Token { value: 1 }; 14 } ``` Real compiler output: ```plaintext 1 error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructor 2 --> src/main.rs:1:10 3 | 4 1 | #[derive(Copy, Clone)] 5 | ^^^^ `Copy` not allowed on types with destructors ``` **Fix:** pick one model. If the type owns a resource that needs cleanup, it should *move* (not be `Copy`); drop the `Copy` derive. If it’s a plain value type with no resource, drop the `Drop` impl. (See [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/) for the `Copy` vs move distinction.) ### Pitfall 4: Expecting `Drop` to fire when a `panic!` aborts [Section titled “Pitfall 4: Expecting Drop to fire when a panic! aborts”](#pitfall-4-expecting-drop-to-fire-when-a-panic-aborts) During a normal panic, Rust *unwinds* the stack and runs destructors: `Drop` still fires, which is great for releasing locks safely. But if the project is built with `panic = "abort"` (in `Cargo.toml` or via a target that aborts), or if a panic occurs *while already unwinding from another panic*, the process aborts immediately and **destructors do not run**. **Fix:** don’t rely on `Drop` for correctness-critical cleanup that *must* happen even on abort (e.g. nothing should “leak” in a way that matters across process death). For in-process resources (locks, memory), the OS reclaims everything on process exit anyway. Just be aware that “drop always runs” assumes unwinding, not aborting. *** ## Best Practices [Section titled “Best Practices”](#best-practices) * **Let scope drive cleanup; reach for `drop()` only to release *early*.** The whole point of RAII is that you don’t manage lifetimes by hand. Restructure code (an inner `{ }` block, a helper function) so a value’s scope matches its useful lifetime, and only call `std::mem::drop` when you genuinely need a resource freed before the natural end of scope. * **Keep `drop` bodies short, infallible, and side-effect-light.** A destructor cannot return errors and may run during unwinding. Release the resource; don’t run business logic. If teardown can fail, expose an explicit `fn close(self) -> Result<...>` and treat `Drop` as the safety net. * **Use a guard with a “done” flag for commit/rollback patterns.** A value that auto-rolls-back on drop unless a `commit`/`finish` method flips a flag is the idiomatic Rust equivalent of `try/finally` for transactional resources (shown in the Real-World Example). * **Don’t implement `Drop` just to free memory.** `String`, `Vec`, `Box`, and friends already free their heap allocations. Write `impl Drop` only for *external* resources or *observable* teardown (closing a handle, logging a span end). * **Mind the difference between binding order and field order.** If teardown order is load-bearing, make it explicit (separate `drop()` calls, or deliberate field/declaration ordering) rather than relying on a reader to recall the LIFO-vs-declaration-order rules. *** ## Real-World Example [Section titled “Real-World Example”](#real-world-example) A classic production use of RAII: a **transaction guard** that automatically rolls back if it is dropped without being explicitly committed. This makes “every early-return path rolls back” a *compile-time guarantee* rather than something a reviewer has to verify by reading every branch — the JavaScript `try/finally` equivalent that you can never forget. ```rust 1 /// An RAII transaction guard. If it is dropped without `commit()`, it rolls back. 2 struct Transaction { 3 name: String, 4 committed: bool, 5 } 6 7 impl Transaction { 8 fn begin(name: &str) -> Transaction { 9 println!("BEGIN {name}"); 10 Transaction { name: name.to_string(), committed: false } 11 } 12 13 /// Consume the guard on success. Takes `self` by value so it can't be reused. 14 fn commit(mut self) { 15 println!("COMMIT {}", self.name); 16 self.committed = true; 17 // `self` is dropped at the end of commit(); the flag suppresses the rollback. 18 } 19 } 20 21 impl Drop for Transaction { 22 fn drop(&mut self) { 23 if !self.committed { 24 println!("ROLLBACK {} (guard cleanup)", self.name); 25 } 26 } 27 } 28 29 fn transfer(ok: bool) { 30 let tx = Transaction::begin("transfer-funds"); 31 println!(" ... debiting account A"); 32 if !ok { 33 println!(" ... validation failed, returning early"); 34 return; // `tx` dropped here -> automatic ROLLBACK, no manual cleanup needed 35 } 36 println!(" ... crediting account B"); 37 tx.commit(); // explicit success -> COMMIT, and the rollback is suppressed 38 } 39 40 fn main() { 41 println!("== happy path =="); 42 transfer(true); 43 println!("== error path =="); 44 transfer(false); 45 } ``` **Output:** ```plaintext 1 == happy path == 2 BEGIN transfer-funds 3 ... debiting account A 4 ... crediting account B 5 COMMIT transfer-funds 6 == error path == 7 BEGIN transfer-funds 8 ... debiting account A 9 ... validation failed, returning early 10 ROLLBACK transfer-funds (guard cleanup) ``` **Why this is idiomatic:** * The early `return` in `transfer(false)` triggers the rollback *automatically*: the compiler inserts the drop on that exit path. No `try/finally`, no rollback call to forget. * `commit(self)` takes ownership, so a committed transaction can’t be used again, and the `committed` flag stops `Drop` from also rolling back. * The same pattern underlies the standard library’s own RAII guards: `std::sync::MutexGuard` releases the lock on drop, `std::fs::File` closes the descriptor on drop, and `Box` frees its heap allocation on drop. You’re using RAII constantly even when you never write `impl Drop` yourself. * This is *deterministic* and *local*: you can read the source and know exactly when the rollback fires. In JavaScript the equivalent safety requires a correctly-written `finally` on every path, plus the resource itself surviving long enough, neither of which the type system enforces. *** ## Further Reading [Section titled “Further Reading”](#further-reading) ### Official Documentation [Section titled “Official Documentation”](#official-documentation) * [The Rust Book — Running Code on Cleanup with the `Drop` Trait](https://doc.rust-lang.org/book/ch15-03-drop.html) — the canonical introduction, including why you can’t call `drop` directly. * [`std::ops::Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) — the trait reference, with the rules on field drop order. * [`std::mem::drop`](https://doc.rust-lang.org/std/mem/fn.drop.html) — the free function for early, deterministic release. * [The Rustonomicon — Destructors](https://doc.rust-lang.org/nomicon/destructors.html) — the precise drop-order semantics for composite types. * [MDN — `FinalizationRegistry`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry) — note its explicit “no guarantee the callback runs” caveat, the contrast with Rust’s `Drop`. ### Related Sections in This Guide [Section titled “Related Sections in This Guide”](#related-sections-in-this-guide) * [The Three Ownership Rules](/05-ownership/01-ownership-rules/) — Rule 3 (drop at end of scope) is what `Drop` hooks into. * [Stack vs Heap](/05-ownership/00-stack-heap/) — *what* gets freed when a value is dropped. * [Move, Copy, and Clone](/05-ownership/06-move-copy-clone/) — why a `Drop` type can’t be `Copy`, and what a move does. * [Borrowing](/05-ownership/02-borrowing/) — references don’t drop the value they point to; only the owner does. * [Reference Counting (`Rc`/`Arc`)](/05-ownership/07-reference-counting/) — with shared ownership, the value drops when the *last* owner does. * [Variables and Mutability](/02-basics/00-variables/) — scopes and shadowing, the foundation for drop timing. * [Output and Formatting](/02-basics/04-output/) — the `println!` used throughout these examples. * [Data Structures](/06-data-structures/) — how `Drop` composes through struct and enum fields. *** ## Exercises [Section titled “Exercises”](#exercises) ### Exercise 1 [Section titled “Exercise 1”](#exercise-1) **Difficulty:** Easy **Objective:** Implement `Drop` and observe deterministic, reverse-order cleanup. **Instructions:** Define a `TempFile` struct with a `name: String` field and implement `Drop` so that dropping prints `deleting temp file `. In `main`, create two `TempFile`s (`"a.tmp"` then `"b.tmp"`), print `working with temp files`, and let them drop at the end of `main`. Before running, predict the order the two deletion messages print in. Solution ```rust 1 struct TempFile { 2 name: String, 3 } 4 5 impl Drop for TempFile { 6 fn drop(&mut self) { 7 println!("deleting temp file {}", self.name); 8 } 9 } 10 11 fn main() { 12 let _a = TempFile { name: String::from("a.tmp") }; 13 let _b = TempFile { name: String::from("b.tmp") }; 14 println!("working with temp files"); 15 } // dropped in reverse declaration order: b.tmp first, then a.tmp ``` Output: ```plaintext 1 working with temp files 2 deleting temp file b.tmp 3 deleting temp file a.tmp ``` Local bindings drop in **reverse** order of declaration (LIFO), so `b.tmp` — declared last — is deleted first. The leading underscores (`_a`, `_b`) keep the compiler from warning that the bindings are unused; they exist purely for their drop side effect. ### Exercise 2 [Section titled “Exercise 2”](#exercise-2) **Difficulty:** Medium **Objective:** Use `std::mem::drop` to release a resource *before* the end of its scope. **Instructions:** Define a `Buffer` struct with a `label: &'static str` field whose `Drop` prints `freeing buffer '