← Certified Rust Programming Expert · Lesson 5 of 7

Module Four

📖 Every lesson in this course is free to read right here, no account needed. Create a free account to track your progress, take the exam, and earn your certificate.
1

Course Outline

Certified Rust Programming Expert – Course Outline

Certified Rust Programming Expert

Course Outline

A complete path from beginner to professional Rust developer

This certification course teaches you everything you need to become a professional Rust programmer. You will start with the basics and grow into advanced systems programming, concurrency, and real-world project building. By the end, you will be ready for the Certified Rust Programming Expert exam.


Module One: Rust Foundations

Your first steps into Rust and systems programming.

  • What is systems programming?
  • Why Rust is safe, fast, and powerful
  • Installing Rust, Cargo, and the toolchain
  • Variables, data types, and mutability
  • Functions, comments, and printing
  • Reading user input
  • The Rust compiler and Cargo basics
  • Your first programs: Hello World and more

Module Two: Control Flow and Ownership

Making decisions, repeating actions, and understanding Rust’s memory model.

  • if, else if, and else
  • Boolean logic: &&, ||, and !
  • Pattern matching with match
  • Loops: loop, while, and for
  • Expressions vs statements
  • Ownership rules
  • Borrowing with & and &mut
  • Lifetimes: a simple introduction
  • Common mistakes and best practices

Module Three: Custom Types and Error Handling

Building your own data types and handling errors safely.

  • Structs: grouping related data
  • Methods with impl blocks
  • Enums: representing choices
  • Enums with data
  • Pattern matching with enums
  • Option for missing values
  • Result for error handling
  • unwrap, expect, and the ? operator
  • Deriving traits like Debug and Clone

Module Four: Collections, Generics, and Traits

Storing many values and writing flexible, reusable code.

  • Vectors: lists of values
  • Strings and text handling
  • Hash maps: key-value storage
  • Iterators and closures
  • Generics: code for many types
  • Traits: shared behaviour
  • Modules and project organisation
  • Testing with cargo test
  • Debugging techniques
  • Smart pointers: Box, Rc, and RefCell

Module Five: Concurrency and Systems Programming

Running many tasks at once and working close to the system.

  • Threads and spawn
  • Message passing with channels
  • Shared state with Mutex and Arc
  • Async programming basics
  • Working with files and the file system
  • Environment variables and command-line arguments
  • Unsafe Rust: when and why
  • Foreign Function Interface (FFI) basics
  • Writing command-line tools

Module Six: Professional Rust and Capstone Project

Building real-world software and preparing for certification.

  • Project structure and workspaces
  • Publishing crates to crates.io
  • Documentation with rustdoc
  • Benchmarking and performance tuning
  • Code review and style guidelines
  • Security best practices
  • Capstone project: build a complete Rust application
  • Exam preparation and practice questions

What You Will Build

Module Project
Module One Hello World and input/output programs
Module Two Guess the Number game
Module Three Student Report Card with structs and enums
Module Four Market Price Checker with hash maps
Module Five Multi-threaded file processor
Module Six Capstone: complete real-world Rust application

Certification Requirements

  • Complete all six modules
  • Pass all module quizzes and exercises
  • Submit all practical assignments
  • Complete the capstone project
  • Pass the final certification exam

Who Is This Course For?

  • Beginners who want to master Rust from scratch
  • Developers moving into systems programming
  • Students preparing for a career in software engineering
  • Professionals who want Rust certification
  • Anyone who wants to build fast, safe software

What You Need

  • A computer (Windows, Mac, or Linux)
  • Internet connection to install Rust and Cargo
  • A code editor (VS Code, RustRover, or similar)
  • Curiosity, patience, and practice time

How You Will Learn

  • Short, simple lessons
  • Fun stories and real-world examples
  • Nigerian and everyday examples
  • Step-by-step explanations
  • Hands-on activities and projects
  • Quizzes, matching exercises, and scenario questions
  • Group and individual activities
  • Capstone project for real experience

Career Opportunities After Certification

  • Systems Programmer
  • Rust Developer
  • Embedded Systems Engineer
  • Backend Developer
  • Blockchain Developer
  • Game Engine Developer
  • Cybersecurity Engineer
  • Cloud Infrastructure Engineer

Start your journey today. Master Rust, build safe systems, and become a Certified Rust Programming Expert!

2

Module One

Certified Rust Programming Expert – Module One

Module One: Rust Foundations – Your First Steps to Becoming a Certified Rust Expert

“Certified Rust Programming Expert” – A complete path from beginner to professional

Module Introduction

Hello, future Rust expert! Have you ever wondered how computers run games, send messages, and keep your money safe in banks? The answer is systems programming. And the best language for systems programming today is Rust.

Rust is a language used by big companies like Microsoft, Google, Amazon, and even space agencies. It is fast, safe, and helps you build programs that do not crash easily. But do not worry—Rust is also fun and friendly for beginners. This module will start from the very beginning.

In this first module, we will learn what Rust is, why it is special, how to install it, and how to write your very first programs. We will use simple words, fun stories, and lots of examples from Nigeria and around the world. By the end, you will understand the heart of Rust and be ready to continue your journey to becoming a Certified Rust Programming Expert.

No prior coding experience is needed. Just bring your curiosity and a willingness to learn!

Learning Objectives

After finishing this module, you will be able to:

  • Explain what systems programming is and why it matters.
  • Describe what Rust is and why it is special.
  • Install Rust and Cargo on your computer.
  • Write and run your first Rust program.
  • Use variables and understand data types.
  • Understand mutability and why Rust values safety.
  • Write functions and use comments.
  • Print output and read input from the user.
  • Recognize common mistakes and best practices.
  • Complete a mini project and practical assignment.

Warm-up Story: Ada and the Amazing Robot

Once upon a time in Lagos, Nigeria, there lived a clever girl named Ada. Ada loved building things. One day, she built a small robot named Chidi. Chidi could walk, talk, and even fetch water. But there was a big problem: Chidi kept crashing into walls! Every time Ada gave Chidi a command, Chidi would get confused and fall down.

Ada asked her friend, Tunde, who was a computer whiz. Tunde said, “Ada, the problem is not Chidi’s body. It is Chidi’s brain. The instructions inside Chidi are not safe. The computer language you used lets Chidi make mistakes that the computer cannot catch. You need a language that checks your work before the robot moves.”

Ada was curious. “Which language can do that?” she asked. Tunde smiled and said, “Rust! Rust is like a careful teacher. It checks every instruction before the robot runs. It stops mistakes that could make Chidi crash. With Rust, Chidi can walk safely and never bump into walls again.”

Ada decided to learn Rust. She read books, watched videos, and practised every day. Slowly, Chidi became the smartest robot in Lagos. Chidi could walk, stop, turn, and even greet people without falling. Ada was so proud!

Today, Ada is a Certified Rust Programming Expert. She builds robots, apps, and systems for companies all over the world. And it all started with one decision: to learn Rust.

Moral of the story: Rust helps you write code that is safe and correct, so your programs (or robots!) do not break easily. With practice, you can become an expert too.

Main Lessons

Lesson 1: What is Systems Programming?

Definition: Systems programming is writing software that controls the computer’s hardware and makes other programs run. It is like being the engineer who builds the roads and bridges that other cars (apps) drive on.

Why it is important: Without systems programming, your computer would not know how to turn on, save files, or connect to the internet.

Simple explanation: Imagine your computer is a big city. Systems programs are the roads, traffic lights, and water pipes. They keep everything moving. Apps like games and browsers are the cars and houses that use those roads.

Real-life example: The operating system (like Windows, macOS, or Linux) is a systems program. It manages your keyboard, screen, and memory.

School example: The school timetable is like a systems program. It tells each class when to go to which room. Without it, chaos!

Home example: Your home’s electrical wiring is a system. It delivers power to your TV, fridge, and lights safely.

Nigerian example: The traffic lights in Abuja are systems. They control when cars move and stop. If they fail, there is a big hold-up. Systems programming keeps such systems working.

Illustration:

  +-------------------+       +-------------------+
  |   HARDWARE        |       |   SYSTEMS         |
  | (CPU, memory,     |<----->|   PROGRAM         |
  |  disk, screen)    |       | (Operating System)|
  +-------------------+       +-------------------+
          ^                            ^
          |                            |
          |                            |
  +-------------------+       +-------------------+
  |   APPLICATIONS    |       |   RUST CODE       |
  | (Games, browsers) |       | (You write this!) |
  +-------------------+       +-------------------+
  

Mini summary: Systems programming builds the foundation that all other software uses. Rust is a language made for this job.

Lesson 2: Why Rust? The Superpowers of Rust

Definition: Rust is a programming language that focuses on speed, safety, and doing many things at once (concurrency).

Why it is important: Many programs crash because of memory errors. Rust stops those errors before the program even runs.

Simple explanation: Think of Rust as a strict but caring coach. It checks your code and says, “Wait, this part might cause a problem. Let’s fix it now.” Other languages might let you run and then crash later.

Real-life example: A bank uses Rust to handle millions of transactions. If the program crashes, people lose money. Rust prevents crashes.

School example: A teacher who marks your homework before you submit it is like Rust. A teacher who only marks after you fail is like some other languages.

Home example: Before you cook, your mum checks if the gas is off. Rust checks your code before it runs.

Nigerian example: In a busy market like Balogun in Lagos, a good trader counts money carefully before giving change. Rust counts and checks memory carefully to avoid “change” errors.

Illustration:

  Other languages:        Rust:
  Write code              Write code
       |                       |
       V                       V
  Run program             Compiler checks
       |                       |
       V                       V
  Maybe crash!            If safe, run
                          Else, fix first
  

Mini summary: Rust is safe, fast, and prevents many common bugs. That is why big companies love it.

Lesson 3: Installing Rust – Your Toolkit

Definition: Installing Rust means putting the Rust tools on your computer so you can write and run Rust programs.

Why it is important: You cannot build a house without tools. Rust has its own tools: rustc (the compiler) and cargo (the project manager).

Simple explanation: rustc turns your human-readable code into computer instructions. cargo helps you create projects, add libraries, and run tests.

Real-life example: A carpenter uses a hammer and saw. A Rust programmer uses rustc and cargo.

School example: Your school bag holds your books and pens. Cargo is like your school bag for Rust projects.

Home example: A toolbox in your house has a screwdriver and pliers. The Rust installation gives you a toolbox for coding.

Nigerian example: A tailor in Kano uses a sewing machine and scissors. A Rust programmer uses rustc and cargo to “stitch” code together.

Illustration:

  Your Computer
       |
       V
  Install Rust (from rustup.rs)
       |
       +--> rustc (compiler)
       |
       +--> cargo (project manager)
       |
       V
  Ready to write Rust!
  

Step-by-step installation:

  1. Go to rustup.rs in your browser.
  2. Follow the instructions for your operating system.
  3. Open a terminal or command prompt.
  4. Type rustc --version to check it works.
  5. Type cargo --version to check Cargo.

Mini summary: Install Rust using rustup. Then you get rustc and cargo to build programs.

Lesson 4: Your First Rust Program – Hello, World!

Definition: A “Hello, World!” program is the simplest program that prints a message to the screen.

Why it is important: It confirms that your tools work and you can run Rust code.

Simple explanation: You write a few lines. The computer reads them and shows “Hello, World!” on your screen.

Real-life example: When you say “Hello” to a friend, they say “Hello” back. Here, you tell the computer to say “Hello, World!”.

School example: The first day of school, you introduce yourself. This is your program’s introduction.

Home example: When you knock on a door, someone answers. This program knocks on the computer’s door.

Nigerian example: In Nigeria, we greet with “How are you?” The computer greets with “Hello, World!”.

Illustration:

  fn main() {
      println!("Hello, World!");
  }
  

Step-by-step:

  1. Open a terminal.
  2. Type cargo new hello_rust.
  3. Go into the folder: cd hello_rust.
  4. Open src/main.rs in a text editor.
  5. Replace the code with the example above.
  6. Save the file.
  7. Type cargo run.
  8. You will see Hello, World! on the screen.

Explanation:

  • fn main() { ... } – This is the starting point. Every Rust program begins here.
  • println!("Hello, World!"); – This prints the text inside the quotes.
  • The semicolon ; tells Rust that the instruction is finished.

Mini summary: “Hello, World!” is your first step. It shows you how to write and run a basic Rust program.

Lesson 5: Variables – Boxes for Keeping Things

Definition: A variable is a named box that stores a value. You can change what is inside (unless it is immutable).

Why it is important: Programs need to remember numbers, words, and other data. Variables hold that data.

Simple explanation: Imagine a kitchen cupboard. You put rice in one container, beans in another. You label them “rice” and “beans”. In Rust, you write let rice = 5;.

Real-life example: Your school bag has pockets for books, pens, and lunch. Each pocket is a variable.

School example: A register has columns for names, ages, and scores. Each column is like a variable.

Home example: Your phone’s contact list stores names and numbers. Each contact is a variable.

Nigerian example: A trader in Onitsha market has baskets for tomatoes, peppers, and onions. Each basket is a variable.

Illustration:

  let age = 13;
  let name = "Ada";
  let is_hungry = true;
  

Step-by-step:

  • Write let followed by the variable name.
  • Use = to assign a value.
  • End with a semicolon ;.
  • Use the variable name to read the value.

Mini summary: Variables store data. Use let to create them. You can change them later if you use mut.

Lesson 6: Data Types – Different Kinds of Boxes

Definition: A data type tells Rust what kind of value a variable holds: a number, a word, a true/false, etc.

Why it is important: Rust needs to know if you are adding numbers or joining words. Different types have different rules.

Simple explanation: You don’t put water in a paper bag. You use a bottle. Similarly, you use i32 for whole numbers, f64 for decimals, bool for true/false, and String for text.

Real-life example: A fuel gauge shows numbers. A name tag shows words. A light switch shows on/off (bool).

School example: Your report card has numbers for scores, letters for grades, and yes/no for attendance.

Home example: A TV remote has buttons for numbers (channels), on/off (bool), and volume (numbers).

Nigerian example: A market woman sells yams (counted in numbers), peppers (measured in cups), and “sold out” (true/false).

Table of common types:

TypeWhat it storesExample
i32Whole number (integer)42, -7, 0
f64Decimal number3.14, 2.5
boolTrue or falsetrue, false
StringText"Hello", "Lagos"
charSingle character'A', '7'

Illustration:

  let age: i32 = 13;
  let price: f64 = 500.50;
  let is_open: bool = true;
  let name: String = String::from("Ada");
  let grade: char = 'A';
  

Mini summary: Data types keep your data organised. Rust checks that you use the right type.

Lesson 7: Mutability – Change or Not to Change?

Definition: Mutability means you can change the value of a variable. In Rust, variables are immutable (cannot change) by default. You must add mut to make them mutable.

Why it is important: Immutability prevents accidental changes. It makes programs safer and easier to understand.

Simple explanation: Imagine a pencil writing on paper. You can erase and rewrite (mutable). But a pen writes permanently (immutable). Rust starts with pen by default; you choose pencil with mut.

Real-life example: Your birthday date never changes (immutable). Your age changes every year (mutable).

School example: Your name is fixed (immutable). Your class teacher can change (mutable).

Home example: The number of chairs in your house can change when guests come (mutable). The address of your house is fixed (immutable).

Nigerian example: The price of fuel can change (mutable). The location of your house is fixed (immutable).

Illustration:

  let x = 5;        // immutable, cannot change
  // x = 10;        // ERROR!
  let mut y = 5;    // mutable
  y = 10;           // OK!
  

Mini summary: Use let for unchangeable variables. Use let mut for changeable ones. Rust loves safety.

Lesson 8: Functions – Reusable Blocks of Code

Definition: A function is a named block of code that does a specific job. You can call it many times.

Why it is important: Functions avoid repeating code. They make programs shorter and easier to fix.

Simple explanation: Think of a function as a recipe. You write it once, then use it whenever you want to cook that dish.

Real-life example: A tailor has a pattern for a shirt. He uses the same pattern for many customers (function calls).

School example: The school bell rings at the same time every day. It is like a function called “ringBell()”.

Home example: Your blender has a “smoothie” button. You press it, and it does the same steps each time.

Nigerian example: A taxi driver has a route from Ikeja to Yaba. He repeats the same route (function) for many passengers.

Illustration:

  fn greet() {
      println!("Hello, friend!");
  }

  fn main() {
      greet();   // calls the function
      greet();   // calls it again
  }
  

Step-by-step:

  • Write fn followed by the function name.
  • Add parentheses ().
  • Open a block with { }.
  • Put the code inside.
  • Call the function by writing its name followed by ().

Mini summary: Functions are named blocks. Use fn to define them and () to call them.

Lesson 9: Comments – Notes for Humans

Definition: Comments are lines in your code that the computer ignores. They are for you and other humans to read.

Why it is important: Comments explain why you did something. They help you remember later and help others understand.

Simple explanation: Imagine writing notes in the margin of your school book. The teacher doesn’t mark them, but they help you study.

Real-life example: A recipe has notes like “bake for 20 minutes”. The oven ignores the note, but the cook reads it.

School example: Your class timetable might have small notes like “bring calculator”. They are comments for students.

Home example: A sticky note on the fridge says “buy milk”. It is a comment for the family.

Nigerian example: A signboard in traffic says “no parking”. It is a comment for drivers, not for the road.

Illustration:

  // This is a single-line comment
  let x = 5; // This comment explains the variable

  /*
     This is a multi-line comment.
     It can span many lines.
  */
  

Mini summary: Use // for short comments and /* ... */ for longer ones. They make code friendly.

Lesson 10: Printing and Input – Talking with the Computer

Definition: Printing means showing text on the screen. Input means the computer reads what you type.

Why it is important: Programs need to communicate with people. Printing shows results. Input lets users give data.

Simple explanation: Printing is like speaking. Input is like listening. A good program does both.

Real-life example: An ATM prints your balance. It also takes your PIN as input.

School example: A teacher asks a question (input) and writes the answer on the board (print).

Home example: A TV remote takes input (button press) and prints the channel on screen.

Nigerian example: A POS machine takes your card details (input) and prints a receipt (output).

Illustration:

  use std::io;

  fn main() {
      println!("What is your name?");
      let mut name = String::new();
      io::stdin().read_line(&mut name).expect("Failed");
      println!("Hello, {}!", name.trim());
  }
  

Step-by-step:

  • Use println! to print text.
  • Import std::io for input.
  • Create a String to hold input.
  • Use io::stdin().read_line() to read.
  • Use trim() to remove extra spaces.

Mini summary: Use println! to print. Use stdin().read_line to read input. Always trim extra spaces.

Lesson 11: The Rust Compiler – Your Strict Friend

Definition: The compiler is a program that turns your Rust code into a program the computer can run. It also checks for mistakes.

Why it is important: The compiler catches many errors before you run the program. This saves time and prevents crashes.

Simple explanation: The compiler is like a teacher who checks your homework before you submit it. If there is a mistake, the teacher tells you to fix it.

Real-life example: A building inspector checks a house before people move in. The Rust compiler checks your code before it runs.

School example: Before a debate, your teacher checks your speech for errors. That is the compiler.

Home example: Before cooking, your mum checks if all ingredients are available. That is the compiler.

Nigerian example: Before a bus leaves the park, the conductor checks that all passengers have paid. The compiler checks your code.

Illustration:

  Your code (hello.rs)
       |
       V
  rustc hello.rs   <-- compiler
       |
       V
  If no errors: hello executable
  If errors: messages to fix
  

Mini summary: The Rust compiler is strict but helpful. It catches mistakes early. Always fix compiler errors.

Lesson 12: Cargo – Your Project Manager

Definition: Cargo is Rust’s tool for creating projects, building code, running tests, and adding libraries.

Why it is important: Without Cargo, managing big projects is hard. Cargo makes everything organised.

Simple explanation: Cargo is like a project folder with extra powers. It creates a structure, downloads dependencies, and runs your program.

Real-life example: A wedding planner organises the venue, food, and music. Cargo organises your Rust project.

School example: A class monitor collects homework and keeps things in order. Cargo is the monitor for your code.

Home example: A toolbox keeps all tools in one place. Cargo is your coding toolbox.

Nigerian example: An event planner for a Nigerian wedding handles aso-ebi, food, and canopies. Cargo handles your project’s files and libraries.

Illustration:

  cargo new my_project
       |
       V
  my_project/
    ├── Cargo.toml
    └── src/
         └── main.rs
  

Common Cargo commands:

CommandWhat it does
cargo new nameCreates a new project
cargo buildBuilds the project
cargo runBuilds and runs the project
cargo checkChecks for errors without building
cargo testRuns tests

Mini summary: Use cargo new to start a project. Use cargo run to run it. Cargo keeps everything tidy.

Lesson 13: Common Mistakes for Beginners

Definition: Mistakes (bugs) are errors in code. Rust catches many before running.

Why it is important: Knowing common mistakes helps you avoid them.

Simple explanation: Rust is like a spell-checker. It underlines errors and suggests fixes.

Real-life example: Typing a wrong phone number. Rust checks if the number is valid.

School example: Forgetting to carry your pencil. Rust checks if you have all variables.

Home example: Forgetting to turn off the tap. Rust checks if memory is freed.

Nigerian example: Forgetting your change in a taxi. Rust checks that you don’t lose data.

Table of common mistakes:

MistakeWhat happensRust’s help
Using a variable without mutCannot change valueCompiler error
Forgetting semicolonUnexpected codeCompiler error
Wrong data typeType mismatchCompiler error
Reading input without trimExtra newlineYou must trim
Typo in variable nameCannot find valueCompiler error

Mini summary: Rust catches many mistakes early. Read compiler messages and fix them.

Lesson 14: Best Practices for Beginners

Definition: Best practices are good habits that make your code clean and safe.

Why it is important: Good habits make coding easier and more fun.

Simple explanation: Like brushing your teeth daily, coding habits keep your programs healthy.

Real-life example: Keeping your room tidy. Best practices keep code tidy.

School example: Writing neatly in your notebook. Best practices make code readable.

Home example: Washing plates after eating. Best practices prevent problems later.

Nigerian example: Keeping your market stall organised. Best practices keep code organised.

List of best practices:

  • Use meaningful variable names (e.g., score not s).
  • Write comments to explain why, not what.
  • Use cargo to manage projects.
  • Run your code often to catch errors early.
  • Read compiler errors carefully.
  • Keep functions small and focused.
  • Use consistent indentation.
  • Practice every day.

Mini summary: Good habits make you a better programmer. Start them now.

Lesson 15: Putting It All Together – Your First Complete Program

Let’s write a program that uses variables, functions, input, and printing.

Program: Greeting Card

  use std::io;

  fn greet(name: &str) {
      println!("Hello, {}! Welcome to Rust.", name);
  }

  fn main() {
      println!("What is your name?");
      let mut name = String::new();
      io::stdin().read_line(&mut name).expect("Failed to read");
      let name = name.trim();
      greet(name);
      println!("You are on your way to becoming a Rust Expert!");
  }
  

Explanation:

  • greet is a function that takes a name and prints a greeting.
  • main asks for input, reads it, trims it, and calls greet.
  • trim() removes the extra newline from input.
  • &str is a string slice, a borrowed string.

Mini summary: This program combines everything you learned in Module One. You can build it and run it!

Key Vocabulary

WordSimple Definition
Systems programmingWriting software that controls hardware and supports other programs.
RustA safe, fast programming language for systems.
CompilerA program that turns your code into a runnable program and checks for errors.
CargoRust’s tool for managing projects.
VariableA named box that stores a value.
Data typeThe kind of value a variable holds (number, text, true/false).
ImmutableCannot be changed.
MutableCan be changed.
FunctionA reusable block of code that does a job.
CommentA note in code that the computer ignores.
PrintShow text on the screen.
InputData typed by the user.
BugA mistake in code.
ConcurrencyDoing many things at the same time.

Important Concepts

  • Safety first: Rust prevents memory errors and data races.
  • Speed: Rust programs run as fast as C or C++.
  • Immutability by default: Variables cannot change unless you say mut.
  • Ownership and borrowing: (We will learn later, but it is Rust’s way of managing memory without a garbage collector.)
  • Cargo and crates: Cargo manages projects; crates are libraries you can use.
  • Compiler as teacher: The compiler catches many errors before running.

Step-by-step Explanations

How to write and run your first Rust program

  1. Install Rust from rustup.rs.
  2. Open a terminal or command prompt.
  3. Type cargo new hello_rust to create a project.
  4. Go into the folder: cd hello_rust.
  5. Open src/main.rs in a text editor.
  6. Replace the code with:
    fn main() {
        println!("Hello, World!");
    }
          
  7. Save the file.
  8. In the terminal, type cargo run.
  9. You will see Hello, World! on the screen.

Explanation: cargo new creates the project structure. cargo run compiles and runs your code. The main function is the starting point. println! prints text.

How to read input step by step

  1. Import std::io at the top.
  2. Create a mutable String: let mut name = String::new();.
  3. Call io::stdin().read_line(&mut name).
  4. Handle the result with .expect("Failed").
  5. Use name.trim() to remove extra spaces.
  6. Use the value with println!.

Real-life Examples

  • Banking: Rust is used in some banks to handle transactions safely.
  • Operating systems: Parts of Windows and Linux are being rewritten in Rust for safety.
  • Web browsers: Firefox uses Rust for its rendering engine.
  • Games: Some game engines use Rust for speed and safety.
  • Embedded devices: Rust runs on small chips in cars, robots, and IoT devices.

Nigerian Examples

  • POS machines: Many POS terminals in Nigeria need safe, fast software. Rust can power them.
  • Banking apps: Nigerian banks use secure systems. Rust helps prevent crashes and fraud.
  • Traffic lights in Lagos: Systems programming controls traffic lights. Rust can make them reliable.
  • Nollywood streaming: Video apps need to handle many users. Rust’s concurrency helps.
  • Agricultural sensors: Farmers use sensors to check soil. Rust can run on small devices.

Fun Examples Children Can Relate To

  • Video games: Rust makes games run smoothly without crashing.
  • Robots: Like Ada’s robot Chidi, Rust keeps robots from bumping into walls.
  • Chat apps: Sending messages without them getting lost.
  • Music players: Playing songs without skipping.
  • School quiz apps: Keeping score correctly.

Everyday Examples

  • Alarm clock: A program that checks the time and rings.
  • Microwave: A program that counts down and stops.
  • Traffic light: A program that changes lights on a timer.
  • ATM: A program that checks your PIN and gives cash.
  • Phone keyboard: A program that shows letters you tap.

Parent Tips

  • Encourage your child to ask questions. Curiosity is key in programming.
  • Install Rust together. It is free and safe.
  • Let them make mistakes. Errors are learning opportunities.
  • Praise effort, not just correct answers.
  • Use everyday examples (like traffic lights) to explain concepts.
  • Set a small daily coding time. Consistency helps.
  • Celebrate small wins, like printing “Hello, World!”.
  • Be patient. Learning to code takes time.
  • Ask them to teach you what they learned. Teaching is the best way to learn.
  • Keep it fun. Use games and stories.

Interesting Facts

  • Rust was voted the “most loved programming language” on Stack Overflow for many years.
  • Rust’s mascot is a crab named Ferris.
  • Rust is named after a fungus that is tough and resilient.
  • Rust has no garbage collector, yet it manages memory safely.
  • Rust can run on tiny computers and huge servers.
  • Many companies pay high salaries for Rust developers.
  • Rust is used in space satellites.
  • The Linux kernel now supports Rust.

Did You Know?

  • Did you know that Rust is used in space? Some satellites use Rust.
  • Did you know that the Linux kernel now supports Rust?
  • Did you know that Rust can help prevent hacking?
  • Did you know that cargo can run tests for your code automatically?
  • Did you know that Rust’s compiler gives very helpful error messages?
  • Did you know that Rust has a mascot called Ferris the crab?
  • Did you know that Rust is free and open-source?
  • Did you know that many big companies are rewriting software in Rust?

Remember This

  • Rust is safe, fast, and great for systems programming.
  • Variables store data. Use let to create them.
  • Use mut to make a variable changeable.
  • Functions are reusable blocks of code.
  • Comments help humans understand code.
  • The compiler checks your code before it runs.
  • Cargo manages your projects.
  • Practice every day to get better.
  • Rust prevents many common bugs.
  • You can become a Certified Rust Expert with practice.

Common Mistakes

  • Forgetting semicolons at the end of statements.
  • Trying to change an immutable variable without mut.
  • Using the wrong data type (e.g., putting text in a number variable).
  • Forgetting to call main() correctly.
  • Not trimming input when reading from the user.
  • Ignoring compiler error messages.
  • Using a variable before creating it.
  • Misspelling variable or function names.

Best Practices

  • Use meaningful names for variables and functions.
  • Write comments for tricky parts.
  • Keep functions short and focused.
  • Run cargo check often to catch errors.
  • Read the compiler messages carefully.
  • Format your code with cargo fmt.
  • Test your code with cargo test.
  • Practice every day.
  • Learn from mistakes.
  • Ask for help when stuck.

Illustrations and Diagrams

How Rust Works

  Your Code (main.rs)
        |
        V
  Rust Compiler (rustc)
        |
        +--> Errors? --> Fix and recompile
        |
        V
  Executable Program
        |
        V
  Runs on your computer
  

Rust Project Structure with Cargo

  my_project/
  ├── Cargo.toml    (project settings)
  └── src/
       └── main.rs  (your code)
  

Decision Flowchart: Should I use mut?

  Start
    |
    V
  Need to change the value later?
    |
    +-- Yes --> Use let mut x = ...
    |
    +-- No  --> Use let x = ...
    |
    V
  End
  

Your Learning Journey

  Module One: Rust Foundations
        |
        V
  Module Two: Control Flow and Ownership
        |
        V
  Module Three: Custom Types and Error Handling
        |
        V
  Module Four: Collections, Generics, and Traits
        |
        V
  Module Five: Concurrency and Systems Programming
        |
        V
  Module Six: Professional Rust and Capstone
        |
        V
  Certified Rust Programming Expert 🎉
  

Comparison Tables

Rust vs Other Languages

FeatureRustPythonC++
SpeedVery fastSlowerVery fast
Memory safetyYes, compiler checksAutomatic, but slowerManual, error-prone
Learning curveModerateEasyHard
ConcurrencySafe and easyLimitedHard
Use caseSystems, embedded, webScripts, data scienceGames, systems

Immutable vs Mutable

PropertyImmutableMutable
Can change value?NoYes
Keywordletlet mut
SafetyHigherLower, but flexible
Examplelet x = 5;let mut y = 5; y = 10;

Data Types Overview

TypeCategoryExample
i32Integer42
f64Floating point3.14
boolBooleantrue
StringText"Hello"
charCharacter'A'

Lesson Summaries

Lesson 1: Systems programming controls hardware and supports apps. Rust is a great language for it.

Lesson 2: Rust is safe, fast, and concurrent. It prevents many crashes.

Lesson 3: Install Rust with rustup. You get rustc and cargo.

Lesson 4: “Hello, World!” prints a message. Every Rust program starts with fn main().

Lesson 5: Variables store data. Use let.

Lesson 6: Data types tell Rust what kind of value you have.

Lesson 7: Immutable by default. Use mut to change.

Lesson 8: Functions are reusable blocks of code.

Lesson 9: Comments are notes for humans.

Lesson 10: Printing shows text. Input reads what you type.

Lesson 11: The compiler checks your code and finds errors.

Lesson 12: Cargo manages projects and libraries.

Lesson 13: Common mistakes include missing semicolons and wrong types.

Lesson 14: Best practices make code clean and safe.

Lesson 15: Combine variables, functions, and input in a greeting program.

End-of-Module Summary

Congratulations! You have finished Module One of the Certified Rust Programming Expert course. You learned that systems programming is the foundation of all software. Rust is a modern language that is safe, fast, and perfect for systems. You installed Rust, wrote your first program, and learned about variables, data types, mutability, functions, comments, the compiler, Cargo, and input/output. You also saw many examples from Nigeria and everyday life. You now know common mistakes and best practices. Most importantly, you are ready to write simple Rust programs and understand how they work. Keep practising, and you will become a Certified Rust Programming Expert!

Frequently Asked Questions

  1. What is Rust? Rust is a programming language for building fast and safe software.
  2. Is Rust hard to learn? It is a bit stricter than some languages, but very beginner-friendly with practice.
  3. Do I need to know another language first? No. You can start with Rust.
  4. What is Cargo? Cargo is Rust’s tool for managing projects.
  5. Why do I need to use mut? To allow a variable to change. Rust defaults to safety.
  6. What is a compiler error? A message from Rust saying your code has a problem.
  7. Can Rust run on my phone? Yes, Rust can run on many devices.
  8. Is Rust free? Yes, completely free and open-source.
  9. What can I build with Rust? Games, operating systems, web servers, robots, and more.
  10. How do I get help? Read the compiler messages, ask a teacher, or search online.

Matching Exercises

Match the word to its definition.

WordDefinition
1. VariableA. A reusable block of code.
2. FunctionB. A named box that stores a value.
3. CompilerC. A note in code that the computer ignores.
4. CommentD. Turns code into a runnable program.
5. CargoE. Rust’s project manager.

Answers: 1-B, 2-A, 3-D, 4-C, 5-E

Scenario-based Exercises

  1. Scenario: You want to store your age in a program. Which data type should you use?
    Answer: i32 (whole number).
  2. Scenario: You need a variable that will change when you score a goal. Should it be mutable?
    Answer: Yes, use let mut score = 0;.
  3. Scenario: Your program asks for your name and prints it. What function do you use to read input?
    Answer: std::io::stdin().read_line().
  4. Scenario: You write let x = 5; x = 10; and get an error. Why?
    Answer: x is immutable. Use let mut x = 5;.
  5. Scenario: You want to reuse a block of code that greets users. What should you create?
    Answer: A function.

Group Activity

Title: “Build a Greeting Robot”

Instructions: In groups of 3–4, write a Rust program that asks for each person’s name and then prints a greeting for each. Use a function for the greeting. One person types, one person reads the compiler errors, one person explains the code. Share your program with the class.

Goal: Practice variables, input, functions, and printing.

Individual Activity

Task: Write a Rust program that stores your name, age, and favourite food in variables. Then print them in a sentence like: “My name is Ada, I am 13 years old, and I love jollof rice.”

Hint: Use let and println!.

Mini Project

Project: “My Daily Routine”

Write a Rust program that prints your daily routine. Use at least three functions: one for morning, one for afternoon, and one for evening. Call them in main(). Add comments to explain each part.

Example output:

  Morning: Wake up, brush teeth, eat breakfast.
  Afternoon: Go to school, study, play.
  Evening: Do homework, eat dinner, sleep.
  

Practical Assignment

Assignment: Create a new Cargo project called about_me. In main.rs, write a program that:

  1. Prints “What is your name?”
  2. Reads the user’s name.
  3. Prints “How old are you?”
  4. Reads the age.
  5. Prints a sentence: “Hello [name], you are [age] years old!”
  6. Uses a function to print the final greeting.

Submit: Your main.rs file and a screenshot of the program running.

Key Takeaways

  • Rust is a safe, fast language for systems programming.
  • Install Rust with rustup and use cargo for projects.
  • Variables store data. Use let and mut.
  • Data types: i32, f64, bool, String, char.
  • Functions are reusable blocks.
  • Comments explain code to humans.
  • The compiler catches errors before running.
  • Print with println!, read input with stdin().read_line().
  • Practice and read error messages carefully.
  • You are on your way to becoming a Certified Rust Expert.

Classroom Discussion Questions

  1. Why is safety important in systems programming?
  2. How is Rust’s compiler like a teacher?
  3. What would happen if traffic lights had bugs?
  4. Why does Rust make variables immutable by default?
  5. How can functions make code shorter?
  6. What is the difference between a variable and a constant?
  7. How does Cargo help in big projects?
  8. Why do we write comments?
  9. What did Ada learn from the robot story?
  10. Where do you see systems programming in Nigeria?

Preparation for Module Two

In Module Two, we will dive deeper into Rust. We will learn about:

  • Control flow: if, else, and match.
  • Loops: loop, while, and for.
  • Expressions and statements.
  • Ownership and borrowing (Rust’s superpower!).
  • Writing bigger programs.
  • Debugging and testing.

To prepare, make sure you have completed the practical assignment and can run simple Rust programs. Review your notes on variables and functions. Bring your curiosity!

See you in Module Two!


End of Module One – Certified Rust Programming Expert

3

Module Two

Certified Rust Programming Expert – Module Two

Module Two: Control Flow and Ownership – Making Rust Think and Remember

“Certified Rust Programming Expert” – A complete path from beginner to professional

Module Introduction

Welcome back, future Rust expert! In Module One, you learned how to install Rust, write your first program, and use variables and functions. You made the computer talk with println!. You also learned how to read input from the user.

But a program that only runs straight from top to bottom is boring. Real programs need to make decisions and repeat things. Imagine a traffic light that never changes, or a game that never checks if you won. That would be useless!

In this module, we will learn two superpowers: decision making (using if, else, and match) and repeating actions (using loops). We will also learn about Rust’s most special idea: ownership. This is what makes Rust safe and fast. Don’t worry—we will use simple stories, examples from Nigeria, and lots of pictures made with text.

By the end, you will be able to write programs that think and act, just like a smart robot. And you will understand how Rust manages memory without a garbage collector. Let’s begin!

Learning Objectives

After finishing this module, you will be able to:

  • Use if, else if, and else to make decisions in Rust.
  • Use match to compare a value against many patterns.
  • Use loop, while, and for to repeat actions.
  • Understand the difference between expressions and statements.
  • Explain the basics of ownership and borrowing in simple words.
  • Use borrowing with & and mutable borrowing with &mut.
  • Recognize common mistakes with control flow and ownership.
  • Complete a mini project and practical assignment.

Warm-up Story: Chidi the Robot Learns to Decide

Remember Ada and her robot Chidi from Module One? After Ada learned Rust, she rebuilt Chidi’s brain. Now Chidi could walk without crashing. But Chidi still had a problem: Chidi did not know when to stop walking!

One day, Ada took Chidi to the market in Lagos. Chidi was supposed to follow Ada and stop when she stopped. But Chidi just kept walking. Ada shouted, “Chidi, stop!” But Chidi did not understand.

Ada realized that Chidi needed to make decisions. Chidi needed to check: “Is Ada in front of me? If yes, keep walking. If no, stop.” Chidi also needed to repeat this check many times, like a loop.

So Ada wrote a new program using if and while. She taught Chidi to check Ada’s position again and again. Now Chidi could decide when to walk and when to stop.

But there was another problem. Chidi kept losing track of Ada’s bag. When Ada gave the bag to Chidi to carry, Chidi would drop it because Chidi thought Ada still had it. Ada explained, “Chidi, when you take the bag, you own it. I don’t own it anymore. That is called ownership.”

Chidi learned about ownership. Now Chidi could carry the bag safely without dropping it. Chidi became the smartest robot in Lagos!

Moral of the story: Programs need to make decisions and repeat actions to be useful. And they need ownership to manage things safely. Rust gives us tools like if, match, loops, and ownership to do this.

Main Lessons

Lesson 1: Making Decisions with if

Definition: if is a keyword that runs a block of code only when a condition is true.

Why it is important: Programs need to choose different actions in different situations. if lets them do that.

Simple explanation: Imagine you are hungry. You check: “If I am hungry, I will eat.” The action (eating) only happens if the condition (hungry) is true.

Real-life example: A traffic light: if the light is red, cars stop. If green, cars go.

School example: If you score above 50, you pass the exam. If not, you fail.

Home example: If the pot is hot, use a cloth to hold it. If not, hold it directly.

Nigerian example: If it rains in Lagos, take an umbrella. If not, leave it at home.

Illustration:

  let hungry = true;

  if hungry {
      println!("Time to eat!");
  }
  

Step-by-step:

  • Write if followed by a condition.
  • The condition must be a bool (true or false).
  • Open a block with { and close with }.
  • Put the code to run inside the block.

Mini summary: if runs code only when a condition is true. It is the first step to making your program smart.

Lesson 2: Adding else and else if

Definition: else runs when the if condition is false. else if checks another condition if the first one is false.

Why it is important: Sometimes you need more than two choices. else if lets you check many conditions in order.

Simple explanation: If you are hungry, eat. Else if you are thirsty, drink. Else, rest.

Real-life example: A grading system: if score >= 70, grade A; else if score >= 60, grade B; else, grade C.

School example: If it is Monday, wear uniform. Else if it is Friday, wear sports wear. Else, wear casual.

Home example: If there is rice, cook rice. Else if there is beans, cook beans. Else, order food.

Nigerian example: If there is fuel, drive the car. Else if there is diesel, use the generator. Else, take a bus.

Illustration:

  let score = 75;

  if score >= 70 {
      println!("Grade A");
  } else if score >= 60 {
      println!("Grade B");
  } else {
      println!("Grade C");
  }
  

Mini summary: else if lets you check many conditions. else catches everything else. Use them together for smart decisions.

Lesson 3: Conditions and Boolean Logic

Definition: A condition is an expression that is either true or false. Boolean logic uses && (AND), || (OR), and ! (NOT).

Why it is important: You often need to check more than one thing at once.

Simple explanation: && means both must be true. || means at least one must be true. ! flips true to false and false to true.

Real-life example: You can ride a bike if you have a helmet AND you know how to ride.

School example: You can go to the library if you have a library card OR a teacher’s note.

Home example: You can watch TV if you have finished homework AND it is not too late.

Nigerian example: You can travel if you have your ticket AND your luggage is ready.

Illustration:

  let has_helmet = true;
  let knows_riding = false;

  if has_helmet && knows_riding {
      println!("You can ride!");
  } else {
      println!("Not yet safe to ride.");
  }

  if !knows_riding {
      println!("You need to learn riding first.");
  }
  

Mini summary: Use &&, ||, and ! to combine or flip conditions. They make your decisions more powerful.

Lesson 4: The match Expression – A Better Way to Choose

Definition: match compares a value against many patterns and runs the code for the matching pattern.

Why it is important: It is cleaner and safer than many if else blocks. Rust checks that you cover all possibilities.

Simple explanation: Imagine a vending machine. You press a button (A1, B2, C3). The machine matches your button to a snack. match does the same with values.

Real-life example: A traffic light: match the colour to an action. Red -> stop, Yellow -> ready, Green -> go.

School example: Match the day of the week to the subject: Monday -> Maths, Tuesday -> English, etc.

Home example: Match the weather to what you wear: Sunny -> T-shirt, Rainy -> raincoat.

Nigerian example: Match the market day to the market you visit: Monday -> Oshodi, Wednesday -> Mile 12, etc.

Illustration:

  let light = "red";

  match light {
      "red" => println!("Stop!"),
      "yellow" => println!("Get ready..."),
      "green" => println!("Go!"),
      _ => println!("Unknown light"),
  }
  

Step-by-step:

  • Write match followed by the value.
  • Inside { }, write patterns with => and code.
  • Separate each arm with a comma.
  • Use _ as the “catch-all” pattern for anything else.

Mini summary: match is a clean way to choose between many options. It forces you to think of all cases.

Lesson 5: Repeating with loop

Definition: loop repeats a block of code forever until you tell it to stop with break.

Why it is important: Some tasks need to repeat many times, like checking for input or counting.

Simple explanation: Imagine a fan that keeps spinning until you press the off button. loop is like that fan. break is the off button.

Real-life example: A washing machine repeats the wash cycle until the timer ends.

School example: The school bell rings every day at the same time. It is a loop.

Home example: Your alarm clock rings every morning until you turn it off.

Nigerian example: A generator keeps running until you switch it off. loop is the generator, break is the switch.

Illustration:

  let mut count = 0;

  loop {
      println!("Count is {}", count);
      count += 1;

      if count == 5 {
          break;
      }
  }
  

Mini summary: loop repeats forever. Use break to stop. Be careful not to create an infinite loop by mistake!

Lesson 6: while Loops – Repeat While True

Definition: A while loop repeats as long as a condition is true.

Why it is important: It is perfect when you don’t know how many times to repeat, but you know when to stop.

Simple explanation: While you are hungry, keep eating. When you are full, stop.

Real-life example: While the traffic light is red, cars wait. When it turns green, they go.

School example: While the teacher is talking, students listen. When the bell rings, they go out.

Home example: While the water is boiling, watch the pot. When it boils, turn off the stove.

Nigerian example: While there is fuel in the generator, it runs. When the fuel finishes, it stops.

Illustration:

  let mut number = 3;

  while number != 0 {
      println!("{}!", number);
      number -= 1;
  }
  println!("Liftoff!");
  

Mini summary: while checks a condition before each repeat. When the condition becomes false, the loop stops.

Lesson 7: for Loops – Repeat a Known Number of Times

Definition: A for loop repeats once for each item in a collection (like a range of numbers).

Why it is important: It is the safest and most common loop in Rust. It avoids mistakes with counters.

Simple explanation: For each day of the week, go to school. For each item in your bag, take it out.

Real-life example: For each passenger on a bus, collect the fare.

School example: For each student in the class, mark attendance.

Home example: For each plate in the sink, wash it.

Nigerian example: For each customer in the market, greet them “Welcome o!”.

Illustration:

  for number in 1..5 {
      println!("Number is {}", number);
  }

  let names = ["Ada", "Tunde", "Chidi"];
  for name in names.iter() {
      println!("Hello, {}!", name);
  }
  

Mini summary: for loops go through each item. They are clean and safe. Use them when you know the collection.

Lesson 8: Expressions vs Statements

Definition: An expression produces a value. A statement performs an action but produces no value.

Why it is important: Rust is an expression-based language. Many things are expressions, like if and match.

Simple explanation: 5 + 3 is an expression (it gives 8). let x = 5; is a statement (it does something but gives nothing).

Real-life example: “What is 2+2?” is an expression (answer 4). “Close the door” is a statement (action, no value).

School example: “What is your name?” (expression, answer). “Raise your hand” (statement, action).

Home example: “How many eggs?” (expression). “Put the eggs in the basket” (statement).

Nigerian example: “How much is the yam?” (expression). “Buy the yam” (statement).

Illustration:

  let x = 5;          // statement
  let y = {           // block expression
      let a = 3;
      a + 1           // no semicolon, so it is the value
  };
  println!("y is {}", y); // prints 4
  

Mini summary: Expressions give values. Statements do actions. In Rust, if and match can be expressions if you use them that way.

Lesson 9: Using if in a let – Expressions in Action

Definition: You can assign the result of an if or match expression to a variable.

Why it is important: It makes code shorter and cleaner.

Simple explanation: Instead of writing a long if else to set a variable, you can do it in one line.

Real-life example: “If it is sunny, wear sunglasses; else, wear a raincoat.” Assign that choice to “what to wear”.

School example: “If score is above 50, result is Pass; else, Fail.” Assign to “result”.

Home example: “If there is milk, breakfast is cereal; else, breakfast is bread.” Assign to “breakfast”.

Nigerian example: “If there is petrol, drive the car; else, take a bus.” Assign to “transport”.

Illustration:

  let score = 75;
  let result = if score >= 50 { "Pass" } else { "Fail" };
  println!("Result: {}", result);
  

Mini summary: if and match can give values. Assign them to variables for cleaner code. Remember: no semicolon inside the block if you want the value.

Lesson 10: Introduction to Ownership – Rust’s Superpower

Definition: Ownership is Rust’s system for managing memory. Each value has a single owner. When the owner goes out of scope, the value is dropped (freed).

Why it is important: It prevents memory bugs and makes Rust safe without a garbage collector.

Simple explanation: Imagine you have a toy. Only one person can own it at a time. If you give it to a friend, you no longer own it. Rust does the same with data.

Real-life example: A book in the library. Only one person can borrow it at a time. When returned, someone else can borrow it.

School example: A class monitor holds the attendance book. If they give it to another student, they no longer have it.

Home example: The TV remote. If you pass it to your sister, you cannot change the channel anymore.

Nigerian example: A single mobile phone. If you give it to your friend to make a call, you cannot use it until they return it.

Illustration:

  let s1 = String::from("hello");
  let s2 = s1;   // s1 is moved to s2. s1 is no longer valid.
  // println!("{}", s1); // ERROR: s1 no longer owns the data
  println!("{}", s2);   // OK
  

Step-by-step:

  • Each value has one owner.
  • When you assign to another variable, ownership moves.
  • The original variable cannot be used after the move.
  • When the owner goes out of scope, the value is freed.

Mini summary: Ownership means one owner at a time. When you assign a String to another variable, the original is moved. This prevents double-free errors.

Lesson 11: Borrowing – Using Without Taking

Definition: Borrowing lets you use a value without taking ownership. You use & to create a reference.

Why it is important: It lets functions use data without stealing it from the owner.

Simple explanation: You can let a friend look at your book without giving it away. They borrow it and give it back.

Real-life example: A library book you read in the library. You don’t own it, but you can read it.

School example: You borrow a pencil from a friend. You use it and return it.

Home example: You borrow your brother’s charger. You use it and give it back.

Nigerian example: You borrow a pot from your neighbour to cook. You return it after.

Illustration:

  fn main() {
      let s = String::from("hello");
      let len = calculate_length(&s); // borrow s
      println!("The length of '{}' is {}.", s, len);
  }

  fn calculate_length(s: &String) -> usize {
      s.len()
  }
  

Step-by-step:

  • Use & before a variable to borrow it.
  • The function takes &Type as a parameter.
  • The owner keeps ownership.
  • The borrower can read the value but not change it (unless mutable).

Mini summary: Borrowing uses & to reference a value without moving it. The owner keeps ownership. This is safe and efficient.

Lesson 12: Mutable Borrowing – Changing Borrowed Data

Definition: You can borrow a value mutably with &mut if you want to change it.

Why it is important: Sometimes you need to modify data without taking ownership.

Simple explanation: You borrow a friend’s notebook and write in it with their permission.

Real-life example: A teacher borrows your homework to correct it. They can write on it (with permission).

School example: You borrow the class register to add your name. You need permission.

Home example: You borrow your mum’s phone to add a contact. She must allow it.

Nigerian example: You borrow a trader’s ledger to record a sale. The trader must agree.

Illustration:

  fn main() {
      let mut s = String::from("hello");
      change(&mut s);
      println!("{}", s); // prints "hello, world"
  }

  fn change(s: &mut String) {
      s.push_str(", world");
  }
  

Step-by-step:

  • Make the variable mutable with let mut.
  • Pass &mut variable to the function.
  • The function takes &mut Type.
  • Only one mutable borrow at a time is allowed.

Mini summary: Use &mut to borrow and change. You can only have one mutable borrow at a time. This prevents data races.

Lesson 13: Lifetimes – A Simple Introduction

Definition: A lifetime is how long a reference is valid. Rust uses lifetimes to prevent dangling references.

Why it is important: Lifetimes ensure that references are always safe to use.

Simple explanation: Imagine borrowing a book from a friend. You can only use it while your friend is still around. If your friend leaves, you cannot use the book. Lifetimes track this.

Real-life example: A library book has a due date. After that, you must return it.

School example: You can only use the school library while the library is open.

Home example: You can only use your brother’s charger while he is at home.

Nigerian example: You can only use a borrowed generator while the owner is still in town.

Illustration:

  fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
      if x.len() > y.len() {
          x
      } else {
          y
      }
  }

  fn main() {
      let s1 = String::from("hello");
      let s2 = String::from("hi");
      let result = longest(&s1, &s2);
      println!("Longest: {}", result);
  }
  

Step-by-step:

  • Use 'a to name a lifetime.
  • Add it to references: &'a str.
  • Rust checks that references live long enough.
  • Most of the time, you don’t need to write lifetimes. Rust figures it out.

Mini summary: Lifetimes track how long references are valid. They prevent dangling references. Rust often infers them automatically.

Lesson 14: Common Mistakes with Control Flow and Ownership

Definition: Mistakes (bugs) happen. Knowing them helps you avoid them.

Why it is important: Rust’s compiler catches many mistakes. Learn from the messages.

Simple explanation: Like learning to ride a bike, you may wobble. Rust helps you stay balanced.

Real-life example: Forgetting to turn off the tap. Rust checks that you don’t leave memory leaks.

School example: Forgetting to carry your homework. Rust checks that you don’t forget variables.

Home example: Forgetting to lock the door. Rust checks that your data is safe.

Nigerian example: Forgetting your change in a taxi. Rust checks that you don’t lose data.

Table of common mistakes:

MistakeWhat happensRust’s help
Using a moved valueValue no longer validCompiler error: “value borrowed after move”
Multiple mutable borrowsData race possibleCompiler error: “cannot borrow as mutable more than once”
Missing _ in matchNot all cases coveredCompiler error: “non-exhaustive patterns”
Infinite loop without breakProgram never stopsYou must add a break condition
Using == with different typesType mismatchCompiler error
Forgetting mut for mutable borrowCannot borrow as mutableCompiler error

Mini summary: Rust catches many mistakes early. Read the error messages. They tell you exactly what to fix.

Lesson 15: Putting It All Together – A Small Program

Let’s write a program that uses everything we learned: if, match, loops, and borrowing.

Program: Guess the Number (simplified)

  use std::io;

  fn main() {
      let secret = 7;
      let mut attempts = 0;

      loop {
          println!("Guess a number between 1 and 10:");
          let mut guess = String::new();
          io::stdin().read_line(&mut guess).expect("Failed to read");
          let guess: i32 = guess.trim().parse().expect("Not a number");

          attempts += 1;

          if guess == secret {
              println!("You win! You took {} attempts.", attempts);
              break;
          } else if guess < secret {
              println!("Too low!");
          } else {
              println!("Too high!");
          }
      }
  }
  

Explanation:

  • loop repeats until the correct guess.
  • if else if else checks the guess.
  • break exits the loop when correct.
  • String::new() creates a new string.
  • parse() converts text to a number.
  • trim() removes extra spaces.

Mini summary: This program combines decisions, loops, and input. You can build it and play!

Key Vocabulary

WordSimple Definition
Control flowThe order in which code runs. Decisions and loops change it.
ifRuns code when a condition is true.
elseRuns code when the if condition is false.
else ifChecks another condition if the first is false.
matchCompares a value to many patterns.
loopRepeats forever until break.
whileRepeats while a condition is true.
forRepeats for each item in a collection.
ExpressionProduces a value.
StatementPerforms an action, no value.
OwnershipEach value has one owner. When owner goes away, value is dropped.
BorrowingUsing a value without taking ownership (&).
Mutable borrowBorrowing to change (&mut).
MoveTransferring ownership from one variable to another.
LifetimeHow long a reference is valid.
ScopeThe part of code where a variable is valid.

Important Concepts

  • Conditions: if and else use bool values.
  • Pattern matching: match is powerful and safe.
  • Loops: loop, while, and for repeat actions.
  • Expressions: Rust is expression-based. if and match can give values.
  • Ownership: One owner at a time. Prevents memory bugs.
  • Borrowing: Use & to reference without moving. Use &mut to change.
  • Lifetimes: Track reference validity. Often inferred automatically.
  • Compiler as guard: Rust checks ownership and borrowing rules at compile time.

Step-by-step Explanations

How to use match step by step

  1. Identify the value you want to check.
  2. Write match value {.
  3. List each possible pattern with => and the code to run.
  4. Separate arms with commas.
  5. Add a catch-all _ if needed.
  6. Close with }.

Example:

  let day = "Monday";
  match day {
      "Monday" => println!("Start of week"),
      "Friday" => println!("Almost weekend"),
      _ => println!("Just another day"),
  }
  

How to borrow step by step

  1. Create a variable that owns a value.
  2. Write a function that takes a reference: fn my_func(s: &String).
  3. Call the function with &my_variable.
  4. Inside the function, use the reference without taking ownership.
  5. The owner keeps ownership and can use the value after.

Example:

  fn main() {
      let s = String::from("hello");
      print_length(&s);
      println!("Still own: {}", s);
  }
  fn print_length(s: &String) {
      println!("Length: {}", s.len());
  }
  

How to use a mutable borrow step by step

  1. Make the variable mutable: let mut s = String::from("hello");.
  2. Write a function that takes &mut String.
  3. Call the function with &mut s.
  4. The function can change the value.
  5. After the borrow ends, the owner can use the value again.

Real-life Examples

  • Traffic lights: Use match to decide stop, ready, or go.
  • Bank ATMs: Use if to check if PIN is correct.
  • Game loops: Use loop to keep the game running until you quit.
  • Music players: Use for to play each song in a playlist.
  • Online forms: Use while to keep asking until valid input.

Nigerian Examples

  • POS machine: Uses if to check if card is valid.
  • Traffic in Lagos: Uses match to decide when to stop and go.
  • Market trading: Uses while to keep selling while there is stock.
  • Nollywood streaming: Uses for to play each episode.
  • Generator: Uses loop to keep running until fuel finishes.

Fun Examples Children Can Relate To

  • Video games: if checks if you won or lost.
  • Robot: while keeps the robot moving while battery is full.
  • Chat apps: loop keeps the app running until you close it.
  • Music: for plays each song in your playlist.
  • School quiz: match checks your answer against the correct one.

Everyday Examples

  • Alarm clock: while time is not up, keep ringing.
  • Microwave: for each second, count down.
  • Traffic light: match colour to action.
  • ATM: if PIN is correct, allow withdrawal.
  • Phone keyboard: for each tap, show letter.

Parent Tips

  • Encourage your child to explain their code out loud. It helps them learn.
  • Let them make mistakes. Errors are learning opportunities.
  • Use everyday examples (traffic lights, cooking) to explain control flow.
  • Set a small daily coding time. Consistency helps.
  • Celebrate small wins, like making a working loop.
  • Be patient. Ownership takes time to understand.
  • Ask them to teach you what they learned. Teaching is the best way to learn.
  • Keep it fun. Use games and stories.
  • Remind them that even expert programmers make mistakes.
  • Encourage them to read error messages carefully.

Interesting Facts

  • Rust’s ownership system was inspired by research from the 1990s.
  • Rust prevents data races at compile time. No other language does this as well.
  • The match keyword is one of Rust’s most loved features.
  • Rust’s for loop is safer than C’s for loop.
  • Rust’s compiler is often called “the best teacher” because of its clear error messages.
  • Many companies rewrite their software in Rust to stop crashes.
  • Lifetimes are checked at compile time, so there is no runtime cost.
  • Borrowing is one of Rust’s most unique features.

Did You Know?

  • Did you know that Rust can prevent entire classes of bugs before your program runs?
  • Did you know that the Linux kernel now uses Rust for some drivers?
  • Did you know that Rust’s match must cover every possible case?
  • Did you know that loop can return a value with break?
  • Did you know that borrowing is checked at compile time, so there is no runtime cost?
  • Did you know that you can have many immutable borrows at the same time?
  • Did you know that you can only have one mutable borrow at a time?
  • Did you know that lifetimes help Rust prevent dangling references?

Remember This

  • if, else if, and else make decisions.
  • match compares against many patterns.
  • loop repeats forever; use break to stop.
  • while repeats while a condition is true.
  • for repeats for each item in a collection.
  • Expressions give values; statements do actions.
  • Ownership: one owner at a time.
  • Borrowing: use & to reference, &mut to change.
  • Lifetimes track reference validity.
  • Rust’s compiler catches many mistakes before running.

Common Mistakes

  • Forgetting to add break in a loop.
  • Using a moved value after moving it.
  • Trying to have two mutable borrows at the same time.
  • Forgetting the catch-all _ in match.
  • Using = instead of == in conditions.
  • Forgetting that if conditions must be bool.
  • Not trimming input before parsing.
  • Ignoring compiler error messages.

Best Practices

  • Prefer for loops over while when possible.
  • Use match instead of long if else chains.
  • Borrow instead of moving when you don’t need ownership.
  • Keep functions small and focused.
  • Use meaningful names.
  • Write comments for tricky parts.
  • Run cargo check often.
  • Read compiler messages carefully.
  • Format your code with cargo fmt.
  • Test your code with cargo test.

Illustrations and Diagrams

Control Flow Decision Tree

  Start
    |
    V
  Is condition true? --Yes--> Run if block
    |
    No
    |
    V
  Is else if condition true? --Yes--> Run else if block
    |
    No
    |
    V
  Run else block
    |
    V
  End
  

Loop Flowchart

  Start
    |
    V
  Check condition (while) or enter loop
    |
    +--> Condition false? --> Exit loop
    |
    +--> Condition true? --> Run loop body
              |
              V
         Go back to check
  

Ownership and Borrowing

  let s = String::from("hello");   // s owns the String
        |
        V
  let r = &s;                      // r borrows s
        |
        V
  println!("{}", r);               // OK
  println!("{}", s);               // OK, s still owns
  

Move Semantics

  let s1 = String::from("hello");
        |
        V
  let s2 = s1;   // ownership moves to s2
        |
        V
  s1 is no longer valid
        |
        V
  s2 is the new owner
  

Mutable Borrow Flow

  let mut s = String::from("hello");
        |
        V
  change(&mut s);
        |
        V
  fn change(s: &mut String) {
      s.push_str(", world");
  }
        |
        V
  s is now "hello, world"
  

Your Learning Journey

  Module One: Rust Foundations
        |
        V
  Module Two: Control Flow and Ownership
        |
        V
  Module Three: Custom Types and Error Handling
        |
        V
  Module Four: Collections, Generics, and Traits
        |
        V
  Module Five: Concurrency and Systems Programming
        |
        V
  Module Six: Professional Rust and Capstone
        |
        V
  Certified Rust Programming Expert 🎉
  

Comparison Tables

Loop Comparison

Loop TypeWhen to UseExample
loopRepeat forever until breakGame loop
whileRepeat while condition is trueWait for input
forRepeat for each itemGo through a list

Ownership vs Borrowing

FeatureOwnershipBorrowing
Who has the value?One ownerOwner keeps it
Can you change?Yes, if mutableOnly with &mut
CostMove transfers ownershipNo move, just reference
SafetyPrevents double-freePrevents data races

Expression vs Statement

ExpressionStatement
Produces a valuePerforms an action
5 + 3let x = 5;
if true { 1 } else { 2 }println!("hi");

Immutable vs Mutable Borrow

FeatureImmutable BorrowMutable Borrow
Syntax&value&mut value
Can read?YesYes
Can change?NoYes
How many at once?ManyOnly one

Lesson Summaries

Lesson 1: if runs code when a condition is true.

Lesson 2: else if and else add more choices.

Lesson 3: Use &&, ||, and ! to combine conditions.

Lesson 4: match compares a value to many patterns.

Lesson 5: loop repeats forever until break.

Lesson 6: while repeats while a condition is true.

Lesson 7: for repeats for each item in a collection.

Lesson 8: Expressions give values; statements do actions.

Lesson 9: if and match can be assigned to variables.

Lesson 10: Ownership: one owner at a time. Moving transfers ownership.

Lesson 11: Borrowing uses & to reference without moving.

Lesson 12: Mutable borrowing uses &mut to change borrowed data.

Lesson 13: Lifetimes track reference validity. Rust often infers them.

Lesson 14: Common mistakes include moved values and missing _ in match.

Lesson 15: Combine decisions, loops, and borrowing in a guessing game.

End-of-Module Summary

Congratulations! You have finished Module Two of the Certified Rust Programming Expert course. You learned how to make programs think with if, else, and match. You learned how to repeat actions with loop, while, and for. You also learned Rust’s superpower: ownership and borrowing. These ideas make Rust safe and fast. You learned about lifetimes and how Rust prevents dangling references. You saw many examples from Nigeria and everyday life. You now know common mistakes and best practices. Most importantly, you can write programs that make decisions and repeat tasks, and you understand how Rust manages memory safely. Keep practising, and you will become a Certified Rust Programming Expert!

Frequently Asked Questions

  1. What is control flow? The order in which code runs. Decisions and loops change it.
  2. What is the difference between if and match? if checks one condition. match checks many patterns.
  3. When should I use loop vs while? Use loop when you want to repeat forever until a break. Use while when you have a condition.
  4. What is ownership? Rust’s system where each value has one owner. When the owner goes away, the value is dropped.
  5. What is borrowing? Using a value without taking ownership. You use &.
  6. What is a mutable borrow? Borrowing to change the value. You use &mut.
  7. Why can’t I have two mutable borrows? It could cause data races. Rust prevents this at compile time.
  8. What is an expression? Code that produces a value.
  9. What is a statement? Code that performs an action but produces no value.
  10. How do I stop an infinite loop? Use break.

Matching Exercises

Match the word to its definition.

WordDefinition
1. ifA. Repeats for each item in a collection.
2. matchB. Runs code when a condition is true.
3. whileC. Compares a value to many patterns.
4. forD. Repeats while a condition is true.
5. OwnershipE. Each value has one owner.

Answers: 1-B, 2-C, 3-D, 4-A, 5-E

Scenario-based Exercises

  1. Scenario: You want to check if a student passed or failed. Which control flow do you use?
    Answer: if else.
  2. Scenario: You want to check the day of the week and print a message for each day. Which control flow is best?
    Answer: match.
  3. Scenario: You want to keep asking for a password until it is correct. Which loop?
    Answer: loop or while.
  4. Scenario: You have a list of names and want to greet each one. Which loop?
    Answer: for.
  5. Scenario: You want to use a String in a function but keep ownership. What do you use?
    Answer: Borrow with &.

Group Activity

Title: “Build a Traffic Light Simulator”

Instructions: In groups of 3–4, write a Rust program that simulates a traffic light. Use match to choose the action for “red”, “yellow”, and “green”. Use a loop to repeat the cycle. Print the light colour and the action. Share your program with the class.

Goal: Practice match, loops, and printing.

Individual Activity

Task: Write a Rust program that asks the user for a number. Use if else to print whether the number is positive, negative, or zero. Use a loop to let the user try again until they type “quit”.

Hint: Use parse() to convert input to a number. Use break to exit.

Mini Project

Project: “My School Timetable”

Write a Rust program that stores your school timetable for the week. Use match to print the subject for each day. Use a for loop to go through the days. Add comments to explain each part.

Example output:

  Monday: Maths
  Tuesday: English
  Wednesday: Science
  Thursday: History
  Friday: Sports
  

Practical Assignment

Assignment: Create a new Cargo project called number_guess. In main.rs, write a program that:

  1. Stores a secret number (e.g., 7).
  2. Uses a loop to ask the user to guess.
  3. Reads the guess and converts it to a number.
  4. Uses if else if else to say “Too low!”, “Too high!”, or “You win!”.
  5. Breaks the loop when the guess is correct.
  6. Prints the number of attempts.
  7. Uses a function to check the guess and return a message.

Submit: Your main.rs file and a screenshot of the program running.

Key Takeaways

  • Use if, else if, and else for decisions.
  • Use match for many patterns.
  • Use loop, while, and for for repetition.
  • Expressions give values; statements do actions.
  • Ownership: one owner at a time.
  • Borrowing: use & to reference, &mut to change.
  • Lifetimes track reference validity.
  • Rust’s compiler catches many mistakes early.
  • Practice and read error messages carefully.
  • You are on your way to becoming a Certified Rust Expert.

Classroom Discussion Questions

  1. Why do programs need to make decisions?
  2. How is match different from if else?
  3. What happens if a loop has no break?
  4. Why is for often safer than while?
  5. What is the difference between an expression and a statement?
  6. Why does Rust have ownership?
  7. How does borrowing help us use data without moving it?
  8. Why can’t we have two mutable borrows at the same time?
  9. What did Chidi the robot learn in the warm-up story?
  10. Where do you see control flow in Nigeria?

Preparation for Module Three

In Module Three, we will dive deeper into Rust. We will learn about:

  • Structs and enums (building your own data types).
  • Pattern matching with enums.
  • Error handling with Result and Option.
  • Methods and impl blocks.
  • Deriving traits like Debug and Clone.
  • Writing bigger programs.

To prepare, make sure you have completed the practical assignment and can write programs with decisions and loops. Review your notes on ownership and borrowing. Bring your curiosity!

See you in Module Three!


End of Module Two – Certified Rust Programming Expert

4

Module Three

Certified Rust Programming Expert – Module Three

Module Three: Custom Types and Error Handling – Building Your Own Data Safely

“Certified Rust Programming Expert” – A complete path from beginner to professional

Module Introduction

Welcome back, future Rust expert! In Module One, you learned how to store single values in variables. In Module Two, you learned how to make decisions and repeat actions. Now it is time to build your own data types.

Imagine you are building a school management program. You need to store information about students: their names, ages, classes, and scores. If you use separate variables for each piece of information, your code will become a big, messy pile. Rust gives you a better way: structs and enums.

A struct groups related data together. An enum lets you choose one value from a list of possibilities. Together, they help you build clear, safe programs. We will also learn about error handling with Result and Option. These are Rust’s tools for handling things that might go wrong, without crashing.

By the end of this module, you will be able to design your own data types and write programs that handle errors gracefully. Let’s get started!

Learning Objectives

After finishing this module, you will be able to:

  • Define and use structs to group related data.
  • Add methods to structs using impl blocks.
  • Define and use enums to represent choices.
  • Use match with enums for pattern matching.
  • Understand Option and Result for error handling.
  • Use unwrap, expect, and the ? operator safely.
  • Write programs that handle errors without crashing.
  • Complete a mini project and practical assignment.

Warm-up Story: Tunde Builds a School Register

Tunde is a bright boy in Abuja. His teacher asked him to keep a register of all the students in his class. At first, Tunde used a separate notebook for each student. He wrote name in one book, age in another, and score in another. Soon, the books were everywhere. He mixed up names and scores. It was a mess!

Tunde’s friend, Ada, said, “Tunde, why don’t you use one card for each student? Write the name, age, and score on the same card. Then you can keep all cards in one box.”

Tunde tried it. It was much better! Each card held all the information about one student. He could look at a card and see everything at once. He could also write notes on the card, like “Good in Maths” or “Needs help in English.”

In Rust, that card is called a struct. It groups related data together. And the notes are like methods—actions you can do with the card.

But then Tunde faced another problem. Some students had not taken the exam yet. Their score was missing. Tunde could not write “0” because that would mean they failed. He needed a way to say “no score yet.” Ada said, “Use an Option. It can be Some(score) if there is a score, or None if there is no score.”

Tunde also needed to handle errors. What if a student’s name was written wrongly? What if the score was negative? Ada said, “Use a Result. It can be Ok(value) if everything is fine, or Err(error) if there is a problem.”

Tunde’s new register was clean, safe, and easy to use. Just like Rust!

Moral of the story: Grouping related data together makes programs clean and safe. Structs help us do that. And when data is missing or something goes wrong, Option and Result help us handle it without crashing.

Main Lessons

Lesson 1: What is a Struct?

Definition: A struct is a custom data type that groups related values together. Each value is called a field.

Why it is important: It keeps related data together, making code clean and easy to understand.

Simple explanation: Think of a student ID card. It has a name, age, class, and photo. All that information belongs to one student. A struct is like that card.

Real-life example: A bank account has a name, account number, and balance. All in one place.

School example: A student record with name, age, class, and score.

Home example: A recipe card with ingredients, steps, and cooking time.

Nigerian example: A market stall record with trader name, goods, price, and quantity.

Illustration:

  struct Student {
      name: String,
      age: u32,
      score: f64,
  }
  

Step-by-step:

  • Write struct followed by the name (use PascalCase: Student).
  • Open a block with { }.
  • List each field as name: Type.
  • Separate fields with commas.

Mini summary: A struct groups related data. It is like a card that holds many pieces of information about one thing.

Lesson 2: Creating and Using Struct Instances

Definition: An instance is a specific value created from a struct. You fill in the fields with actual data.

Why it is important: You need to create instances to use your structs in programs.

Simple explanation: The struct is the blank card. The instance is the card filled with a student’s details.

Real-life example: A blank form becomes an instance when you fill it with your name and address.

School example: A blank report card becomes an instance when it has your scores.

Home example: A shopping list becomes an instance when you write specific items.

Nigerian example: A blank POS receipt becomes an instance when it shows a transaction.

Illustration:

  let student1 = Student {
      name: String::from("Ada"),
      age: 13,
      score: 85.5,
  };

  println!("Name: {}", student1.name);
  println!("Age: {}", student1.age);
  println!("Score: {}", student1.score);
  

Mini summary: Create an instance by filling in the fields. Use dot notation to access fields.

Lesson 3: Methods – Actions on Structs

Definition: A method is a function that belongs to a struct. It is defined inside an impl block.

Why it is important: Methods let you do things with your data in a clean way.

Simple explanation: If the struct is a card, methods are actions you can do with the card, like “show details” or “update score.”

Real-life example: A phone has methods: call, send message, take photo.

School example: A student card has a method: “calculate grade.”

Home example: A TV remote has methods: turn on, change channel, volume up.

Nigerian example: A POS machine has methods: accept payment, print receipt.

Illustration:

  impl Student {
      fn show_details(&self) {
          println!("Name: {}, Age: {}, Score: {}", self.name, self.age, self.score);
      }

      fn is_pass(&self) -> bool {
          self.score >= 50.0
      }
  }
  

Step-by-step:

  • Write impl StructName { }.
  • Inside, write fn method_name(&self) { }.
  • &self means the method borrows the instance.
  • Call it with instance.method_name().

Mini summary: Methods are functions inside impl. They make structs powerful and clean.

Lesson 4: What is an Enum?

Definition: An enum (enumeration) is a type that can be one of several variants.

Why it is important: It represents choices clearly and safely.

Simple explanation: Think of a traffic light. It can be Red, Yellow, or Green. Those are the only choices. An enum lists all possible choices.

Real-life example: Days of the week: Monday, Tuesday, Wednesday, etc.

School example: Grades: A, B, C, D, F.

Home example: Light switch: On or Off.

Nigerian example: Payment methods: Cash, Transfer, Card, USSD.

Illustration:

  enum TrafficLight {
      Red,
      Yellow,
      Green,
  }

  enum PaymentMethod {
      Cash,
      Transfer,
      Card,
      USSD,
  }
  

Mini summary: Enums list possible choices. They make your code safe because you can only use the listed variants.

Lesson 5: Enums with Data

Definition: Enum variants can hold data. This makes enums very powerful.

Why it is important: Sometimes a choice comes with extra information.

Simple explanation: A payment can be Cash (with amount) or Card (with card number). The enum variant holds the data.

Real-life example: A message can be Text (with words), Image (with pixels), or Video (with frames).

School example: A result can be Pass (with score) or Fail (with reason).

Home example: A delivery can be Delivered (with time) or Pending (with expected date).

Nigerian example: A transfer can be Successful (with reference) or Failed (with error).

Illustration:

  enum Payment {
      Cash(f64),
      Transfer { bank: String, amount: f64 },
      Card(String),
  }
  

Mini summary: Enum variants can carry data. This lets you attach extra information to each choice.

Lesson 6: Pattern Matching with Enums

Definition: match works with enums to handle each variant.

Why it is important: It ensures you handle all possible variants safely.

Simple explanation: If the light is Red, stop. If Yellow, ready. If Green, go. match checks each variant.

Real-life example: A restaurant order: match the order to the kitchen action.

School example: Match the grade to a message: A -> Excellent, B -> Good, etc.

Home example: Match the weather to what to wear.

Nigerian example: Match the market day to the market location.

Illustration:

  let light = TrafficLight::Red;

  match light {
      TrafficLight::Red => println!("Stop!"),
      TrafficLight::Yellow => println!("Get ready..."),
      TrafficLight::Green => println!("Go!"),
  }
  

Mini summary: match with enums is safe and clean. Rust forces you to handle every variant.

Lesson 7: Option – Handling Missing Values

Definition: Option is an enum that can be Some(value) or None.

Why it is important: It handles cases where a value might be missing, without using null.

Simple explanation: Imagine a box that may or may not have a gift. Some(gift) means there is a gift. None means empty.

Real-life example: A phone contact that may or may not have an email address.

School example: A student may or may not have a middle name.

Home example: A fridge may or may not have milk.

Nigerian example: A market stall may or may not have tomatoes today.

Illustration:

  let some_number = Some(5);
  let no_number: Option = None;

  match some_number {
      Some(n) => println!("Number is {}", n),
      None => println!("No number"),
  }
  

Mini summary: Option handles missing values. Use Some and None. Always match both.

Lesson 8: Result – Handling Errors

Definition: Result is an enum that can be Ok(value) or Err(error).

Why it is important: It handles operations that might fail, like reading a file or parsing a number.

Simple explanation: Imagine you are baking. The cake might come out well (Ok) or burn (Err). Result tells you which happened.

Real-life example: An ATM withdrawal: Ok(cash) or Err("Insufficient funds").

School example: An exam: Ok(score) or Err("Absent").

Home example: A phone call: Ok("Connected") or Err("No network").

Nigerian example: A transfer: Ok("Successful") or Err("Network failure").

Illustration:

  fn divide(a: f64, b: f64) -> Result {
      if b == 0.0 {
          Err(String::from("Cannot divide by zero"))
      } else {
          Ok(a / b)
      }
  }

  match divide(10.0, 2.0) {
      Ok(result) => println!("Result: {}", result),
      Err(e) => println!("Error: {}", e),
  }
  

Mini summary: Result handles success and failure. Use Ok for success and Err for errors. Always handle both.

Lesson 9: unwrap and expect – Quick but Risky

Definition: unwrap() and expect() extract the value from Option or Result. If there is no value, the program panics (crashes).

Why it is important: They are quick for testing, but risky for real programs.

Simple explanation: Imagine opening a box. If there is a gift, you take it. If empty, you scream and fall down. That is unwrap.

Real-life example: Pressing an elevator button. If it works, fine. If not, you are stuck.

School example: Assuming you will pass without studying. If you fail, you panic.

Home example: Assuming there is fuel in the generator. If not, it stops.

Nigerian example: Assuming there is network before a transfer. If not, it fails.

Illustration:

  let x = Some(5);
  let y = x.unwrap(); // y = 5

  let z: Option = None;
  // let w = z.unwrap(); // PANIC! Program crashes
  

Mini summary: unwrap and expect are quick but dangerous. Use them only when you are sure there is a value. In real programs, use match or ?.

Lesson 10: The ? Operator – Safe Error Propagation

Definition: The ? operator returns the value if Ok, or returns the error if Err.

Why it is important: It makes error handling clean without match every time.

Simple explanation: If something works, continue. If it fails, pass the problem up to the caller.

Real-life example: A relay race. If a runner falls, the next runner takes over the problem.

School example: If you don’t understand a topic, ask the teacher. The teacher handles it.

Home example: If the remote is not working, give it to your dad. He fixes it.

Nigerian example: If the POS fails, the cashier calls the bank. The bank handles it.

Illustration:

  fn read_number() -> Result {
      let s = "42";
      let n: i32 = s.parse().map_err(|_| "Not a number".to_string())?;
      Ok(n)
  }
  

Mini summary: The ? operator propagates errors cleanly. Use it in functions that return Result.

Lesson 11: Structs with Methods and Enums Together

Definition: You can use structs and enums together to build powerful data models.

Why it is important: Real programs need both grouping and choices.

Simple explanation: A student struct with a field that is an enum for grade.

Real-life example: A car with a field for fuel type: Petrol, Diesel, Electric.

School example: A student with a field for result: Pass, Fail, Pending.

Home example: A delivery with a field for status: Ordered, Shipped, Delivered.

Nigerian example: A transaction with a field for payment method: Cash, Transfer, Card, USSD.

Illustration:

  enum Grade {
      A,
      B,
      C,
      F,
  }

  struct Student {
      name: String,
      age: u32,
      grade: Grade,
  }

  impl Student {
      fn show(&self) {
          println!("{} is {} years old", self.name, self.age);
          match self.grade {
              Grade::A => println!("Excellent!"),
              Grade::B => println!("Good job!"),
              Grade::C => println!("Keep trying!"),
              Grade::F => println!("Needs improvement."),
          }
      }
  }
  

Mini summary: Combine structs and enums for rich data. Use match to handle enum fields.

Lesson 12: Deriving Traits – Making Structs Useful

Definition: Traits are like abilities. Rust can automatically add some traits to your structs with #[derive(...)].

Why it is important: It saves you time and makes your structs work with printing, comparing, and cloning.

Simple explanation: Like a student automatically getting a library card when they join school. derive gives your struct automatic abilities.

Real-life example: A new phone automatically has calling and texting. You don’t build it from scratch.

School example: A new student automatically gets a uniform and timetable.

Home example: A new TV automatically has a remote.

Nigerian example: A new POS machine automatically accepts cards and prints receipts.

Illustration:

  #[derive(Debug, Clone, PartialEq)]
  struct Point {
      x: i32,
      y: i32,
  }

  let p1 = Point { x: 1, y: 2 };
  let p2 = p1.clone();
  println!("{:?}", p1); // Debug printing
  

Mini summary: #[derive(...)] adds traits like Debug, Clone, and PartialEq. It makes structs easier to use.

Lesson 13: Common Mistakes with Structs and Enums

Definition: Mistakes happen. Knowing them helps you avoid them.

Why it is important: Rust’s compiler catches many mistakes. Learn from the messages.

Simple explanation: Like forgetting to fill a form correctly. Rust tells you what is missing.

Real-life example: Forgetting to sign a cheque. The bank rejects it.

School example: Forgetting to write your name on an exam. The teacher cannot mark it.

Home example: Forgetting to close the fridge. Food spoils.

Nigerian example: Forgetting to add the recipient’s name in a transfer. The money returns.

Table of common mistakes:

MistakeWhat happensRust’s help
Forgetting to handle NoneCompiler error“non-exhaustive patterns”
Using unwrap on NoneProgram panicsRuntime crash
Missing a field in structCompiler error“missing field”
Wrong type in enum variantCompiler error“mismatched types”
Forgetting &self in methodCompiler error“expected &self”
Forgetting to derive DebugCannot print struct“doesn’t implement Debug”

Mini summary: Rust catches many mistakes early. Read the error messages. They tell you exactly what to fix.

Lesson 14: Best Practices for Structs, Enums, and Errors

Definition: Best practices are good habits that make your code clean and safe.

Why it is important: Good habits make coding easier and more fun.

Simple explanation: Like keeping your room tidy, best practices keep your code tidy.

Real-life example: A chef keeps the kitchen clean. A programmer keeps code clean.

School example: Writing neatly in your notebook. Best practices make code readable.

Home example: Washing plates after eating. Best practices prevent problems later.

Nigerian example: Keeping your market stall organised. Best practices keep code organised.

List of best practices:

  • Use structs to group related data.
  • Use enums for choices.
  • Use match to handle all variants.
  • Use Option for missing values, not null.
  • Use Result for operations that can fail.
  • Avoid unwrap in real programs.
  • Use ? to propagate errors cleanly.
  • Derive Debug for easy printing.
  • Keep methods small and focused.
  • Write comments to explain why.

Mini summary: Good habits make you a better programmer. Start them now.

Lesson 15: Putting It All Together – A Small Program

Let’s write a program that uses structs, enums, methods, and error handling.

Program: Student Report Card

  #[derive(Debug)]
  enum Grade {
      A,
      B,
      C,
      F,
  }

  struct Student {
      name: String,
      age: u32,
      score: f64,
      grade: Grade,
  }

  impl Student {
      fn new(name: &str, age: u32, score: f64) -> Student {
          let grade = if score >= 70.0 {
              Grade::A
          } else if score >= 60.0 {
              Grade::B
          } else if score >= 50.0 {
              Grade::C
          } else {
              Grade::F
          };

          Student {
              name: String::from(name),
              age,
              score,
              grade,
          }
      }

      fn show(&self) {
          println!("Name: {}", self.name);
          println!("Age: {}", self.age);
          println!("Score: {}", self.score);
          match self.grade {
              Grade::A => println!("Grade: A (Excellent)"),
              Grade::B => println!("Grade: B (Good)"),
              Grade::C => println!("Grade: C (Average)"),
              Grade::F => println!("Grade: F (Fail)"),
          }
      }
  }

  fn main() {
      let s1 = Student::new("Ada", 13, 85.0);
      let s2 = Student::new("Tunde", 14, 45.0);

      s1.show();
      println!("---");
      s2.show();
  }
  

Explanation:

  • Grade is an enum with four variants.
  • Student is a struct with name, age, score, and grade.
  • new is a method that creates a Student and calculates the grade.
  • show is a method that prints the details and matches the grade.
  • main creates two students and shows their reports.

Mini summary: This program combines structs, enums, methods, and match. It is a complete example of what you can build.

Key Vocabulary

WordSimple Definition
StructA custom type that groups related data.
FieldA piece of data inside a struct.
InstanceA specific value created from a struct.
MethodA function that belongs to a struct.
implA block where you define methods for a struct.
EnumA type that can be one of several variants.
VariantOne of the possible choices in an enum.
OptionAn enum for values that may be missing: Some or None.
ResultAn enum for success or failure: Ok or Err.
unwrapExtracts value or panics if empty.
expectLike unwrap but with a custom message.
? operatorPropagates errors cleanly.
TraitAn ability or behaviour that types can have.
deriveAutomatically adds traits to a struct or enum.
PanicWhen a program crashes due to an unrecoverable error.

Important Concepts

  • Structs: Group related data into one type.
  • Methods: Functions that belong to a struct, defined in impl.
  • Enums: Represent choices with variants.
  • Pattern matching: match handles all enum variants safely.
  • Option: Handles missing values without null.
  • Result: Handles operations that can fail.
  • ? operator: Propagates errors cleanly.
  • Deriving traits: Adds abilities like Debug and Clone automatically.
  • Compiler as guard: Rust checks that you handle all cases.

Step-by-step Explanations

How to define and use a struct step by step

  1. Write struct Name { }.
  2. List fields as field: Type.
  3. Create an instance with Name { field: value, ... }.
  4. Access fields with instance.field.
  5. Add methods in impl Name { }.
  6. Call methods with instance.method().

Example:

  struct Book {
      title: String,
      author: String,
      pages: u32,
  }

  impl Book {
      fn summary(&self) -> String {
          format!("{} by {} ({} pages)", self.title, self.author, self.pages)
      }
  }

  fn main() {
      let b = Book {
          title: String::from("Things Fall Apart"),
          author: String::from("Chinua Achebe"),
          pages: 209,
      };
      println!("{}", b.summary());
  }
  

How to use Result step by step

  1. Write a function that returns Result<T, E>.
  2. Use Ok(value) for success.
  3. Use Err(error) for failure.
  4. Call the function and use match to handle both cases.
  5. Or use ? to propagate the error.

Example:

  fn parse_age(s: &str) -> Result {
      match s.parse::() {
          Ok(age) => Ok(age),
          Err(_) => Err(String::from("Invalid age")),
      }
  }
  

How to use Option step by step

  1. Use Some(value) when there is a value.
  2. Use None when there is no value.
  3. Use match to handle both cases.
  4. Or use if let Some(x) = ... for short handling.
  5. Avoid unwrap unless you are sure.

Example:

  fn find_student(name: &str) -> Option {
      if name == "Ada" {
          Some(13)
      } else {
          None
      }
  }

  match find_student("Ada") {
      Some(age) => println!("Age: {}", age),
      None => println!("Student not found"),
  }
  

Real-life Examples

  • Bank accounts: Struct with name, number, balance.
  • Traffic lights: Enum with Red, Yellow, Green.
  • Online forms: Option for optional fields.
  • File reading: Result for success or error.
  • Payment systems: Enum with Cash, Card, Transfer.

Nigerian Examples

  • POS transaction: Struct with amount, recipient, status.
  • Market stall: Struct with trader name, goods, prices.
  • Payment method: Enum with Cash, Transfer, Card, USSD.
  • Network status: Option for signal strength.
  • Bank transfer: Result for success or failure.

Fun Examples Children Can Relate To

  • Video game character: Struct with name, health, score.
  • Superpower: Enum with Flying, Invisibility, Strength.
  • Treasure box: Option for gold or empty.
  • Quest result: Result for success or failure.
  • Pet: Struct with name, type, age.

Everyday Examples

  • Contact list: Struct with name, phone, email.
  • Weather: Enum with Sunny, Rainy, Cloudy.
  • Fridge: Option for milk or no milk.
  • Cooking: Result for success or burnt.
  • Shopping list: Struct with item, quantity, price.

Parent Tips

  • Encourage your child to explain their structs out loud.
  • Let them make mistakes. Errors are learning opportunities.
  • Use everyday examples (contact list, weather) to explain structs and enums.
  • Set a small daily coding time. Consistency helps.
  • Celebrate small wins, like creating a working struct.
  • Be patient. Error handling takes time to understand.
  • Ask them to teach you what they learned. Teaching is the best way to learn.
  • Keep it fun. Use games and stories.
  • Remind them that even expert programmers make mistakes.
  • Encourage them to read error messages carefully.

Interesting Facts

  • Rust’s Option and Result are inspired by functional programming languages.
  • Rust has no null. Instead, it uses Option.
  • The ? operator is one of Rust’s most loved features.
  • Structs in Rust can have methods, unlike C structs.
  • Enums in Rust can hold data, unlike enums in many other languages.
  • Rust’s compiler forces you to handle every possible error.
  • Deriving Debug is one of the most common things Rust programmers do.
  • The ? operator can only be used in functions that return Result or Option.

Did You Know?

  • Did you know that Rust’s Result is used everywhere in real programs?
  • Did you know that Option prevents the famous “billion-dollar mistake” of null pointers?
  • Did you know that match can destructure structs and enums?
  • Did you know that #[derive(Debug)] lets you print your structs easily?
  • Did you know that the ? operator can be used in functions that return Result?
  • Did you know that enums can have methods too, just like structs?
  • Did you know that Option and Result are just enums?
  • Did you know that Rust’s compiler will not let you forget to handle an error?

Remember This

  • Structs group related data.
  • Methods are functions inside impl.
  • Enums represent choices.
  • match handles all enum variants.
  • Option handles missing values.
  • Result handles errors.
  • unwrap is quick but risky.
  • ? propagates errors cleanly.
  • derive adds traits automatically.
  • Rust’s compiler catches many mistakes before running.

Common Mistakes

  • Forgetting to handle None or Err.
  • Using unwrap on a value that might be missing.
  • Forgetting to derive Debug before printing.
  • Missing a field when creating a struct.
  • Using the wrong type in a struct field.
  • Forgetting &self in methods.
  • Not handling all enum variants in match.
  • Ignoring compiler error messages.

Best Practices

  • Use structs for related data.
  • Use enums for choices.
  • Use Option instead of null.
  • Use Result for fallible operations.
  • Avoid unwrap in real programs.
  • Use ? to propagate errors.
  • Derive Debug for easy printing.
  • Keep methods small and focused.
  • Write comments to explain why.
  • Run cargo check often.

Illustrations and Diagrams

Struct and Methods

  struct Student {
      name: String,
      age: u32,
      score: f64,
  }
        |
        V
  impl Student {
      fn show(&self) { ... }
      fn is_pass(&self) -> bool { ... }
  }
        |
        V
  let s = Student { ... };
  s.show();
  s.is_pass();
  

Option Flowchart

  Start
    |
    V
  Value exists?
    |
    +-- Yes --> Some(value)
    |
    +-- No  --> None
    |
    V
  Match on Option
    |
    +-- Some(v) --> Use v
    |
    +-- None    --> Handle missing
  

Result Flowchart

  Start
    |
    V
  Operation succeeded?
    |
    +-- Yes --> Ok(value)
    |
    +-- No  --> Err(error)
    |
    V
  Match on Result
    |
    +-- Ok(v)  --> Use v
    |
    +-- Err(e) --> Handle error
  

Error Propagation with ?

  Function A returns Result
        |
        V
  Calls Function B with ?
        |
        V
  If B returns Ok --> continue
        |
        V
  If B returns Err --> return Err to caller
        |
        V
  Caller handles error
  

Your Learning Journey

  Module One: Rust Foundations
        |
        V
  Module Two: Control Flow and Ownership
        |
        V
  Module Three: Custom Types and Error Handling
        |
        V
  Module Four: Collections, Generics, and Traits
        |
        V
  Module Five: Concurrency and Systems Programming
        |
        V
  Module Six: Professional Rust and Capstone
        |
        V
  Certified Rust Programming Expert 🎉
  

Comparison Tables

Struct vs Enum

FeatureStructEnum
PurposeGroup related dataRepresent choices
ExampleStudent { name, age }Grade { A, B, C, F }
FieldsAll fields exist togetherOnly one variant at a time
Use caseData recordsState machines, options

Option vs Result

FeatureOptionResult
PurposeValue may be missingOperation may fail
VariantsSome, NoneOk, Err
Use caseOptional fieldsFile reading, parsing
Error infoNo error detailsContains error details

unwrap vs match vs ?

MethodSafetyWhen to use
unwrap()Risky (panics)Quick tests, prototypes
matchSafeWhen you need custom handling
?SafeWhen you want to propagate errors

Struct vs Method

FeatureStructMethod
What is it?A data typeA function
Where defined?struct Name { }impl Name { }
PurposeHold dataDo something with data
ExampleStudent { name, age }fn show(&self)

Lesson Summaries

Lesson 1: Structs group related data.

Lesson 2: Create instances by filling fields.

Lesson 3: Methods are functions inside impl.

Lesson 4: Enums represent choices.

Lesson 5: Enum variants can hold data.

Lesson 6: match handles all enum variants.

Lesson 7: Option handles missing values.

Lesson 8: Result handles success and failure.

Lesson 9: unwrap and expect are quick but risky.

Lesson 10: The ? operator propagates errors cleanly.

Lesson 11: Combine structs and enums for rich data.

Lesson 12: derive adds traits automatically.

Lesson 13: Common mistakes include unhandled None and missing fields.

Lesson 14: Best practices: use Option, Result, and avoid unwrap.

Lesson 15: Combine structs, enums, methods, and error handling in a student report program.

End-of-Module Summary

Congratulations! You have finished Module Three of the Certified Rust Programming Expert course. You learned how to build your own data types with structs and enums. You learned how to add methods to structs. You learned how to handle missing values with Option and errors with Result. You also learned the ? operator and how to derive traits like Debug. You saw many examples from Nigeria and everyday life. You now know common mistakes and best practices. Most importantly, you can design clean, safe programs that handle errors gracefully. Keep practising, and you will become a Certified Rust Programming Expert!

Frequently Asked Questions

  1. What is a struct? A custom type that groups related data.
  2. What is an enum? A type that can be one of several variants.
  3. What is a method? A function that belongs to a struct.
  4. What is Option? An enum for values that may be missing: Some or None.
  5. What is Result? An enum for success or failure: Ok or Err.
  6. What is unwrap? A method that extracts a value or panics.
  7. What is the ? operator? It propagates errors cleanly.
  8. What is derive? It automatically adds traits to a struct or enum.
  9. Why does Rust have no null? Because Option handles missing values safely.
  10. How do I handle all enum variants? Use match.

Matching Exercises

Match the word to its definition.

WordDefinition
1. StructA. A type that can be one of several variants.
2. EnumB. Groups related data.
3. MethodC. Handles missing values.
4. OptionD. Handles success or failure.
5. ResultE. A function that belongs to a struct.

Answers: 1-B, 2-A, 3-E, 4-C, 5-D

Scenario-based Exercises

  1. Scenario: You want to store a student’s name, age, and score. What do you use?
    Answer: A struct.
  2. Scenario: You want to represent the days of the week. What do you use?
    Answer: An enum.
  3. Scenario: A user may or may not enter their middle name. What do you use?
    Answer: Option.
  4. Scenario: You are reading a file that might fail. What do you use?
    Answer: Result.
  5. Scenario: You want to print a struct for debugging. What do you add?
    Answer: #[derive(Debug)].

Group Activity

Title: “Build a School Management System”

Instructions: In groups of 3–4, design a Rust program that stores student information. Create a Student struct with name, age, and score. Create a Grade enum with A, B, C, F. Add a method to calculate the grade. Add a method to print the student details. Share your program with the class.

Goal: Practice structs, enums, methods, and match.

Individual Activity

Task: Write a Rust program that defines a Book struct with title, author, and pages. Add a method summary() that returns a string. Create two books and print their summaries.

Hint: Use format! to build the string.

Mini Project

Project: “My Contact List”

Write a Rust program that defines a Contact struct with name, phone, and email (email is Option<String>). Create a few contacts. Add a method to print the contact details. If email is None, print “No email”. Use match to handle the Option.

Example output:

  Name: Ada
  Phone: 08012345678
  Email: ada@example.com
  ---
  Name: Tunde
  Phone: 08087654321
  Email: No email
  

Practical Assignment

Assignment: Create a new Cargo project called bank_account. In main.rs, write a program that:

  1. Defines a BankAccount struct with owner (String) and balance (f64).
  2. Adds methods: deposit(amount) and withdraw(amount) -> Result<(), String>.
  3. withdraw returns Err if there are insufficient funds.
  4. Creates an account, deposits some money, tries to withdraw too much, and prints the result.
  5. Uses match to handle the Result from withdraw.
  6. Derives Debug for the struct.

Submit: Your main.rs file and a screenshot of the program running.

Key Takeaways

  • Structs group related data.
  • Enums represent choices.
  • Methods add behaviour to structs.
  • Option handles missing values.
  • Result handles errors.
  • unwrap is quick but risky.
  • ? propagates errors cleanly.
  • derive adds traits automatically.
  • Rust’s compiler catches many mistakes early.
  • Practice and read error messages carefully.

Classroom Discussion Questions

  1. Why do we need structs?
  2. How are enums different from structs?
  3. Why does Rust have Option instead of null?
  4. What happens if you use unwrap on None?
  5. How does the ? operator help with error handling?
  6. Why is match important with enums?
  7. What traits can you derive for a struct?
  8. How do methods make structs more powerful?
  9. What did Tunde learn from the school register story?
  10. Where do you see structs and enums in Nigeria?

Preparation for Module Four

In Module Four, we will dive deeper into Rust. We will learn about:

  • Collections: vectors, strings, and hash maps.
  • Iterators and closures.
  • Generics and traits.
  • Writing bigger, modular programs.
  • Testing and debugging.
  • Building a complete project.

To prepare, make sure you have completed the practical assignment and can write programs with structs, enums, and error handling. Review your notes on Option and Result. Bring your curiosity!

See you in Module Four!


End of Module Three – Certified Rust Programming Expert

5

Module Four

Certified Rust Programming Expert – Module Four

Module Four: Collections, Generics, and Traits – Storing Many Things and Writing Flexible Code

“Certified Rust Programming Expert” – A complete path from beginner to professional

Module Introduction

Welcome back, future Rust expert! In Module One, you learned how to store single values in variables. In Module Two, you learned how to make decisions and repeat actions. In Module Three, you learned how to build your own data types with structs and enums.

But what if you need to store many values at once? Imagine a school with 500 students. You cannot create 500 separate variables. That would be crazy! Rust gives you collections to store many values in one place.

In this module, we will learn about vectors (lists of values), strings (text), and hash maps (key-value storage). We will also learn about iterators and closures to work with collections. Then we will explore generics and traits to write flexible code. Finally, we will learn about modules, testing, and debugging to build bigger, safer programs.

By the end, you will be able to store and manage many values, write reusable code, and build complete projects. Let’s begin!

Learning Objectives

After finishing this module, you will be able to:

  • Use vectors to store lists of values.
  • Use strings to store and manipulate text.
  • Use hash maps to store key-value pairs.
  • Use iterators to go through collections.
  • Use closures to write short, inline functions.
  • Write generic functions and structs.
  • Define and implement traits.
  • Organise code with modules.
  • Write and run tests.
  • Debug programs effectively.
  • Complete a mini project and practical assignment.

Warm-up Story: Ada’s Big Market List

Ada is going to the market in Lagos. Her mother gives her a long list of things to buy: rice, beans, tomatoes, peppers, onions, oil, salt, and maggi. Ada writes the list on a piece of paper.

At the market, Ada needs to check each item. She also needs to remember the price of each item. And she needs to know how much money she has spent. If she uses separate papers for each item, she will get confused. Instead, she uses one notebook with columns for item, price, and quantity.

In Rust, that notebook is like a vector (a list of items) and a hash map (item to price). Ada can add items, remove items, and look up prices quickly. Just like Ada’s notebook, Rust collections help you store and manage many things without confusion.

But Ada also wants to write a small helper that can calculate the total price of any basket of goods, whether it is rice and beans or tomatoes and peppers. She does not want to write a new helper for every possible combination. She uses a generic function that works with any list of items. She also uses a trait to make sure each item has a price.

Finally, Ada wants to keep her notebook safe. She writes a small note to herself: “Always check the price before buying.” That note is like a test. And when something goes wrong, she checks her notebook carefully. That is debugging.

Moral of the story: Collections help you store many values. Generics and traits help you write flexible code. Tests and debugging help you keep your programs safe.

Main Lessons

Lesson 1: Vectors – Lists of Values

Definition: A vector is a list that can grow or shrink. It stores many values of the same type.

Why it is important: You often need to store many items, like names, scores, or prices.

Simple explanation: Think of a shopping list. You can add items, remove items, and read them one by one. A vector is like that list.

Real-life example: A playlist of songs. You can add songs, remove songs, and play them in order.

School example: A list of students in a class.

Home example: A list of chores to do.

Nigerian example: A list of goods in a market stall.

Illustration:

  let mut scores = vec![85, 90, 78, 92];

  scores.push(88);        // add a score
  scores.remove(0);       // remove first score

  for score in &scores {
      println!("Score: {}", score);
  }
  

Step-by-step:

  • Create with vec![] or Vec::new().
  • Use push to add items.
  • Use remove to delete items.
  • Use for to go through items.
  • Use & to borrow the vector without moving it.

Mini summary: Vectors store lists of values. They can grow and shrink. Use vec! to create them.

Lesson 2: Strings – Working with Text

Definition: A String is a growable, mutable text type. It stores words, sentences, and paragraphs.

Why it is important: Programs need to handle names, messages, and user input.

Simple explanation: A String is like a notebook where you can write, erase, and add more words.

Real-life example: A text message you type on your phone.

School example: Your name written on your exercise book.

Home example: A shopping list written on paper.

Nigerian example: A broadcast message sent to customers: “Buy 1 get 1 free!”

Illustration:

  let mut greeting = String::from("Hello");
  greeting.push_str(", world!");
  greeting.push('!');

  println!("{}", greeting);       // Hello, world!!
  println!("Length: {}", greeting.len());
  

Step-by-step:

  • Create with String::from("text") or "text".to_string().
  • Use push_str to add a string slice.
  • Use push to add a single character.
  • Use len() to get the length.
  • Use & to borrow as a string slice (&str).

Mini summary: Strings store text. They can grow and change. Use String::from to create them.

Lesson 3: Hash Maps – Key-Value Storage

Definition: A hash map stores pairs of keys and values. You use the key to find the value.

Why it is important: You often need to look up information quickly by a name or ID.

Simple explanation: Think of a phone book. You look up a name (key) to find a phone number (value).

Real-life example: A dictionary. You look up a word to find its meaning.

School example: A class register. You look up a student’s name to find their score.

Home example: A recipe book. You look up a dish to find ingredients.

Nigerian example: A market price list. You look up “rice” to find the price.

Illustration:

  use std::collections::HashMap;

  let mut prices = HashMap::new();
  prices.insert("rice", 500);
  prices.insert("beans", 300);

  match prices.get("rice") {
      Some(price) => println!("Rice costs {}", price),
      None => println!("Rice not found"),
  }
  

Step-by-step:

  • Import with use std::collections::HashMap;.
  • Create with HashMap::new().
  • Use insert(key, value) to add pairs.
  • Use get(key) to find a value. It returns Option.
  • Use for (key, value) in &map to go through all pairs.

Mini summary: Hash maps store key-value pairs. Use them for fast lookups. get returns Option.

Lesson 4: Iterators – Going Through Collections

Definition: An iterator is an object that goes through items in a collection, one at a time.

Why it is important: Iterators make it easy to process lists without writing manual loops.

Simple explanation: Think of a bus conductor. He goes to each passenger, one by one, to collect fares. An iterator does the same with items.

Real-life example: A teacher marking attendance, calling each student’s name.

School example: Going through each question in an exam.

Home example: Checking each item on a shopping list.

Nigerian example: A trader counting each item in a basket.

Illustration:

  let numbers = vec![1, 2, 3, 4, 5];

  let doubled: Vec = numbers.iter().map(|x| x * 2).collect();
  println!("{:?}", doubled); // [2, 4, 6, 8, 10]

  let evens: Vec<&i32> = numbers.iter().filter(|x| *x % 2 == 0).collect();
  println!("{:?}", evens); // [2, 4]
  

Step-by-step:

  • Call .iter() to get an iterator.
  • Use .map() to transform each item.
  • Use .filter() to keep only some items.
  • Use .collect() to turn the result back into a collection.

Mini summary: Iterators go through items one by one. Use map and filter to process them.

Lesson 5: Closures – Short Inline Functions

Definition: A closure is a small function you can write inline. It can capture values from around it.

Why it is important: Closures make code short and easy to read, especially with iterators.

Simple explanation: Think of a quick note you write on a sticky paper. It does one small job.

Real-life example: A recipe note: “Add salt to taste.”

School example: A short reminder: “Bring calculator.”

Home example: A sticky note: “Buy milk.”

Nigerian example: A short instruction: “Add pepper small.”

Illustration:

  let add = |a, b| a + b;
  println!("{}", add(3, 4)); // 7

  let numbers = vec![1, 2, 3];
  let doubled: Vec = numbers.iter().map(|x| x * 2).collect();
  println!("{:?}", doubled); // [2, 4, 6]
  

Step-by-step:

  • Write |parameters| expression.
  • Use { } for multiple lines.
  • Closures can capture variables from outside.
  • Use them with map, filter, and other iterator methods.

Mini summary: Closures are short functions. They are perfect for iterators. Use |x| to write them.

Lesson 6: Generics – Writing Flexible Code

Definition: Generics let you write code that works with many types, without repeating yourself.

Why it is important: You can write one function that works for numbers, text, and more.

Simple explanation: Think of a water bottle. It can hold water, juice, or milk. The bottle is generic. The contents can be different types.

Real-life example: A delivery box. It can carry books, clothes, or food.

School example: A school bag. It can carry maths books, English books, or science books.

Home example: A plate. You can put rice, beans, or salad on it.

Nigerian example: A market basket. It can carry yams, tomatoes, or peppers.

Illustration:

  fn largest(list: &[T]) -> &T {
      let mut largest = &list[0];
      for item in list {
          if item > largest {
              largest = item;
          }
      }
      largest
  }

  let numbers = vec![34, 50, 25, 100, 65];
  println!("Largest: {}", largest(&numbers));

  let chars = vec!['y', 'm', 'a', 'q'];
  println!("Largest: {}", largest(&chars));
  

Step-by-step:

  • Write <T> after the function name.
  • Use T as a type in parameters and return.
  • Add trait bounds like T: PartialOrd if needed.
  • Call the function with any type that meets the bounds.

Mini summary: Generics let you write code for many types. Use <T> to define them. Add traits to set rules.

Lesson 7: Traits – Shared Behaviour

Definition: A trait defines behaviour that types can share. It is like a contract.

Why it is important: Traits let you write code that works with many types, as long as they have the required behaviour.

Simple explanation: Think of a driving licence. Any person with a licence can drive. The licence is a trait. Different people (types) can have it.

Real-life example: A “can swim” trait. Fish, humans, and boats can all swim.

School example: A “can read” trait. Students, teachers, and librarians can all read.

Home example: A “can cook” trait. Mum, dad, and auntie can all cook.

Nigerian example: A “can trade” trait. Market men, market women, and online sellers can all trade.

Illustration:

  trait Greet {
      fn greet(&self) -> String;
  }

  struct Person {
      name: String,
  }

  impl Greet for Person {
      fn greet(&self) -> String {
          format!("Hello, my name is {}", self.name)
      }
  }

  let p = Person { name: String::from("Ada") };
  println!("{}", p.greet());
  

Step-by-step:

  • Define with trait Name { }.
  • List method signatures inside.
  • Implement with impl Trait for Type { }.
  • Call the methods on instances.

Mini summary: Traits define shared behaviour. Types implement traits. Use them to write flexible code.

Lesson 8: Modules – Organising Your Code

Definition: A module is a way to group related code together. It keeps your project tidy.

Why it is important: As programs grow, you need to organise them. Modules prevent confusion.

Simple explanation: Think of a school. It has different classrooms for different subjects. Each classroom is a module.

Real-life example: A hospital has different departments: emergency, pharmacy, laboratory.

School example: Different subjects in different exercise books.

Home example: Different drawers for clothes, utensils, and tools.

Nigerian example: A market has different sections: food, clothes, electronics.

Illustration:

  mod greetings {
      pub fn hello() {
          println!("Hello from the greetings module!");
      }

      pub mod farewells {
          pub fn bye() {
              println!("Goodbye from the farewells module!");
          }
      }
  }

  fn main() {
      greetings::hello();
      greetings::farewells::bye();
  }
  

Step-by-step:

  • Write mod name { } to create a module.
  • Use pub to make items public.
  • Access with module::item.
  • Nest modules for deeper organisation.

Mini summary: Modules group related code. Use mod and pub. They keep projects tidy.

Lesson 9: Testing – Checking Your Work

Definition: A test is code that checks if your program works correctly.

Why it is important: Tests catch mistakes early. They give you confidence.

Simple explanation: Like checking your homework before submitting. Tests check your code before running it for real.

Real-life example: A doctor checks your temperature before giving medicine.

School example: A teacher marks your test and gives feedback.

Home example: Tasting food before serving guests.

Nigerian example: Checking the generator fuel before starting it.

Illustration:

  fn add(a: i32, b: i32) -> i32 {
      a + b
  }

  #[cfg(test)]
  mod tests {
      use super::*;

      #[test]
      fn test_add() {
          assert_eq!(add(2, 3), 5);
          assert_eq!(add(-1, 1), 0);
      }
  }
  

Step-by-step:

  • Add #[cfg(test)] to a module.
  • Write test functions with #[test].
  • Use assert_eq! to check values.
  • Run with cargo test.

Mini summary: Tests check your code. Use #[test] and assert_eq!. Run with cargo test.

Lesson 10: Debugging – Finding and Fixing Mistakes

Definition: Debugging is the process of finding and fixing mistakes (bugs) in your code.

Why it is important: All programmers make mistakes. Debugging helps you fix them.

Simple explanation: Like finding a typo in your homework and correcting it.

Real-life example: A mechanic checking a car engine to find a problem.

School example: Checking your maths work to find an error.

Home example: Finding why the TV remote is not working.

Nigerian example: A trader checking why the POS is not printing receipts.

Illustration:

  Debugging steps:
  1. Read the error message.
  2. Find the line of code.
  3. Check the logic.
  4. Fix the mistake.
  5. Run again.

  Use println! to print values.
  Use dbg! to print with file and line.
  Use cargo check to catch errors early.
  

Step-by-step:

  • Read compiler error messages carefully.
  • Use println! to print variables.
  • Use dbg! to print with file and line info.
  • Simplify the code to isolate the problem.
  • Ask for help if stuck.

Mini summary: Debugging finds and fixes mistakes. Read error messages. Use println! and dbg!. Be patient.

Lesson 11: Lifetimes – A Simple Introduction

Definition: A lifetime is how long a reference is valid. Rust uses lifetimes to prevent dangling references.

Why it is important: Lifetimes ensure that references are always safe to use.

Simple explanation: Imagine borrowing a book from a friend. You can only use it while your friend is still around. If your friend leaves, you cannot use the book. Lifetimes track this.

Real-life example: A library book has a due date. After that, you must return it.

School example: You can only use the school library while the library is open.

Home example: You can only use your brother’s charger while he is at home.

Nigerian example: You can only use a borrowed generator while the owner is still in town.

Illustration:

  fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
      if x.len() > y.len() {
          x
      } else {
          y
      }
  }

  fn main() {
      let s1 = String::from("hello");
      let s2 = String::from("hi");
      let result = longest(&s1, &s2);
      println!("Longest: {}", result);
  }
  

Step-by-step:

  • Use 'a to name a lifetime.
  • Add it to references: &'a str.
  • Rust checks that references live long enough.
  • Most of the time, you don’t need to write lifetimes. Rust figures it out.

Mini summary: Lifetimes track how long references are valid. They prevent dangling references. Rust often infers them automatically.

Lesson 12: Smart Pointers – Box, Rc, and RefCell

Definition: Smart pointers are types that act like pointers but have extra abilities. Rust has Box, Rc, and RefCell.

Why it is important: They help you manage memory in advanced situations.

Simple explanation: A normal pointer is like a finger pointing at something. A smart pointer is a finger with a brain. It can do extra things.

Real-life example: A TV remote is a smart pointer. It points to the TV but also controls it.

School example: A class monitor is a smart pointer. They represent the class but also do tasks.

Home example: A universal remote controls many devices.

Nigerian example: A POS machine is a smart pointer. It handles payment and printing.

Illustration:

  // Box: stores data on the heap
  let b = Box::new(5);
  println!("b = {}", b);

  // Rc: reference counted, shared ownership
  use std::rc::Rc;
  let a = Rc::new(String::from("hello"));
  let b = Rc::clone(&a);
  println!("Count: {}", Rc::strong_count(&a)); // 2
  

Mini summary: Smart pointers add abilities. Box stores on heap. Rc allows shared ownership. RefCell allows inner mutability.

Lesson 13: Common Mistakes with Collections and Generics

Definition: Mistakes happen. Knowing them helps you avoid them.

Why it is important: Rust’s compiler catches many mistakes. Learn from the messages.

Simple explanation: Like forgetting to add an item to your market list. Rust reminds you.

Real-life example: Forgetting to save a file before closing.

School example: Forgetting to write your name on an exam.

Home example: Forgetting to lock the door.

Nigerian example: Forgetting to collect your change in a taxi.

Table of common mistakes:

MistakeWhat happensRust’s help
Using vector after movingCompiler error“value borrowed after move”
Getting from hash map without checkingPanic if missingget returns Option
Missing trait bound in genericCompiler error“trait not implemented”
Forgetting pub in moduleCannot access“private module”
Test not marked with #[test]Test not runNo output
Forgetting & in iteratorMoves collectionCompiler error

Mini summary: Rust catches many mistakes early. Read error messages. They tell you exactly what to fix.

Lesson 14: Best Practices for Collections, Generics, and Modules

Definition: Best practices are good habits that make your code clean and safe.

Why it is important: Good habits make coding easier and more fun.

Simple explanation: Like keeping your room tidy, best practices keep your code tidy.

Real-life example: A chef keeps the kitchen clean. A programmer keeps code clean.

School example: Writing neatly in your notebook. Best practices make code readable.

Home example: Washing plates after eating. Best practices prevent problems later.

Nigerian example: Keeping your market stall organised. Best practices keep code organised.

List of best practices:

  • Use vectors for lists, hash maps for lookups.
  • Use & to borrow collections instead of moving them.
  • Use iterators and closures for clean code.
  • Use generics to avoid repeating code.
  • Use traits to define shared behaviour.
  • Organise code with modules.
  • Write tests for important functions.
  • Use dbg! for debugging.
  • Keep functions small and focused.
  • Write comments to explain why.

Mini summary: Good habits make you a better programmer. Start them now.

Lesson 15: Putting It All Together – A Small Program

Let’s write a program that uses collections, iterators, closures, and modules.

Program: Market Price Checker

  use std::collections::HashMap;

  mod market {
      use std::collections::HashMap;

      pub fn create_prices() -> HashMap {
          let mut prices = HashMap::new();
          prices.insert(String::from("rice"), 500.0);
          prices.insert(String::from("beans"), 300.0);
          prices.insert(String::from("tomatoes"), 150.0);
          prices
      }

      pub fn total_cost(prices: &HashMap, items: &[&str]) -> f64 {
          items.iter()
              .filter_map(|item| prices.get(*item))
              .sum()
      }
  }

  fn main() {
      let prices = market::create_prices();
      let basket = vec!["rice", "beans", "tomatoes"];

      let total = market::total_cost(&prices, &basket);
      println!("Total cost: ₦{}", total);

      for (item, price) in &prices {
          println!("{}: ₦{}", item, price);
      }
  }
  

Explanation:

  • market is a module that groups related functions.
  • create_prices returns a hash map of items and prices.
  • total_cost uses an iterator, filter, and sum to calculate total.
  • main calls the functions and prints the result.

Mini summary: This program combines modules, hash maps, vectors, iterators, and closures. It is a complete example of what you can build.

Key Vocabulary

WordSimple Definition
VectorA growable list of values.
StringA growable text type.
Hash MapA collection of key-value pairs.
IteratorAn object that goes through items one by one.
ClosureA short inline function.
GenericCode that works with many types.
TraitShared behaviour that types can implement.
ModuleA group of related code.
TestCode that checks if your program works.
DebuggingFinding and fixing mistakes.
LifetimeHow long a reference is valid.
Smart PointerA pointer with extra abilities.
BoxStores data on the heap.
RcReference counted, shared ownership.
RefCellAllows inner mutability.

Important Concepts

  • Vectors: Store lists of values. Use vec!.
  • Strings: Store text. Use String::from.
  • Hash Maps: Store key-value pairs. Use HashMap::new.
  • Iterators: Go through items. Use map, filter, collect.
  • Closures: Short functions. Use |x|.
  • Generics: Write code for many types. Use <T>.
  • Traits: Define shared behaviour. Use trait and impl.
  • Modules: Organise code. Use mod and pub.
  • Testing: Check your code. Use #[test].
  • Debugging: Find and fix mistakes. Use dbg!.
  • Lifetimes: Track reference validity.
  • Smart Pointers: Box, Rc, RefCell for advanced memory management.

Step-by-step Explanations

How to use a vector step by step

  1. Create with let mut v = vec![1, 2, 3];.
  2. Add with v.push(4);.
  3. Remove with v.remove(0);.
  4. Access with v[0] or v.get(0).
  5. Loop with for item in &v { }.

Example:

  let mut scores = vec![85, 90, 78];
  scores.push(92);
  for score in &scores {
      println!("Score: {}", score);
  }
  

How to use a hash map step by step

  1. Import with use std::collections::HashMap;.
  2. Create with let mut map = HashMap::new();.
  3. Insert with map.insert("key", value);.
  4. Get with map.get("key"); returns Option.
  5. Loop with for (k, v) in &map { }.

Example:

  use std::collections::HashMap;

  let mut prices = HashMap::new();
  prices.insert("rice", 500);
  prices.insert("beans", 300);

  for (item, price) in &prices {
      println!("{}: {}", item, price);
  }
  

How to write a test step by step

  1. Add #[cfg(test)] before a module.
  2. Write mod tests { }.
  3. Add use super::*; to access functions.
  4. Write #[test] before each test function.
  5. Use assert_eq! to check values.
  6. Run with cargo test.

Example:

  fn add(a: i32, b: i32) -> i32 {
      a + b
  }

  #[cfg(test)]
  mod tests {
      use super::*;

      #[test]
      fn test_add() {
          assert_eq!(add(2, 3), 5);
      }
  }
  

How to define a trait step by step

  1. Write trait Name { }.
  2. Add method signatures inside.
  3. Write impl Name for Type { }.
  4. Implement the methods.
  5. Call them on instances.

Example:

  trait Greet {
      fn greet(&self) -> String;
  }

  struct Person { name: String }

  impl Greet for Person {
      fn greet(&self) -> String {
          format!("Hello, {}", self.name)
      }
  }
  

Real-life Examples

  • Playlists: Vectors store songs.
  • Phone books: Hash maps store names and numbers.
  • Chat apps: Strings store messages.
  • Online stores: Hash maps store product prices.
  • School records: Vectors store student names.

Nigerian Examples

  • Market list: Vector of items to buy.
  • Price list: Hash map of item to price.
  • POS transactions: Vector of transactions.
  • Bank accounts: Hash map of account number to balance.
  • Customer messages: String of text.

Fun Examples Children Can Relate To

  • Video game inventory: Vector of items.
  • High scores: Vector of numbers.
  • Pet names: Vector of strings.
  • Superpowers: Enum with Flying, Strength, Invisibility.
  • Treasure map: Hash map of location to treasure.

Everyday Examples

  • Shopping list: Vector of items.
  • Contact list: Hash map of name to phone.
  • To-do list: Vector of tasks.
  • Recipe: Vector of steps.
  • Weather forecast: Hash map of day to weather.

Parent Tips

  • Encourage your child to explain their collections out loud.
  • Let them make mistakes. Errors are learning opportunities.
  • Use everyday examples (shopping list, contact list) to explain collections.
  • Set a small daily coding time. Consistency helps.
  • Celebrate small wins, like creating a working vector.
  • Be patient. Generics and traits take time to understand.
  • Ask them to teach you what they learned. Teaching is the best way to learn.
  • Keep it fun. Use games and stories.
  • Remind them that even expert programmers make mistakes.
  • Encourage them to read error messages carefully.

Interesting Facts

  • Rust’s Vec is similar to Python’s list and Java’s ArrayList.
  • Rust’s HashMap uses a fast hashing algorithm.
  • Iterators in Rust are lazy: they only do work when needed.
  • Closures can capture variables by reference, by mutable reference, or by value.
  • Generics in Rust have zero runtime cost.
  • Traits are similar to interfaces in other languages.
  • Rust’s module system is inspired by ML and Haskell.
  • cargo test runs all tests in your project.
  • dbg! prints the file name and line number.
  • Lifetimes are checked at compile time, so there is no runtime cost.

Did You Know?

  • Did you know that Rust’s Vec can grow automatically?
  • Did you know that HashMap does not keep items in order?
  • Did you know that iter() borrows items, while into_iter() takes ownership?
  • Did you know that closures can be stored in variables?
  • Did you know that generics can be used with structs and enums too?
  • Did you know that traits can have default methods?
  • Did you know that modules can be nested inside other modules?
  • Did you know that tests can be run in parallel?
  • Did you know that dbg! returns the value, so you can use it inline?
  • Did you know that Box is often used for recursive types?

Remember This

  • Vectors store lists of values.
  • Strings store text.
  • Hash maps store key-value pairs.
  • Iterators go through items one by one.
  • Closures are short functions.
  • Generics let you write code for many types.
  • Traits define shared behaviour.
  • Modules organise code.
  • Tests check your code.
  • Debugging finds and fixes mistakes.
  • Lifetimes track reference validity.
  • Smart pointers add abilities.

Common Mistakes

  • Forgetting to import HashMap.
  • Using get without handling None.
  • Forgetting & when iterating over a vector.
  • Using unwrap on a hash map lookup.
  • Forgetting trait bounds in generics.
  • Forgetting pub in modules.
  • Forgetting #[test] before test functions.
  • Ignoring compiler error messages.

Best Practices

  • Use vectors for lists, hash maps for lookups.
  • Use & to borrow collections.
  • Use iterators and closures for clean code.
  • Use generics to avoid repeating code.
  • Use traits to define shared behaviour.
  • Organise code with modules.
  • Write tests for important functions.
  • Use dbg! for debugging.
  • Keep functions small and focused.
  • Write comments to explain why.

Illustrations and Diagrams

Vector Operations

  let mut v = vec![1, 2, 3];
        |
        V
  v.push(4);        // [1, 2, 3, 4]
        |
        V
  v.remove(0);      // [2, 3, 4]
        |
        V
  for x in &v { }   // go through items
  

Hash Map Lookup

  HashMap
  +--------+--------+
  |  Key   | Value  |
  +--------+--------+
  | "rice" |  500   |
  | "beans"|  300   |
  +--------+--------+
        |
        V
  map.get("rice") --> Some(500)
  map.get("yam")  --> None
  

Iterator Pipeline

  Vector: [1, 2, 3, 4, 5]
        |
        V
  .iter()        --> 1, 2, 3, 4, 5
        |
        V
  .map(|x| x*2)  --> 2, 4, 6, 8, 10
        |
        V
  .filter(|x| x>5) --> 6, 8, 10
        |
        V
  .collect()     --> Vec [6, 8, 10]
  

Module Structure

  project/
  ├── src/
  │   ├── main.rs
  │   ├── market.rs      <-- module
  │   └── utils.rs       <-- module
  └── Cargo.toml
  

Test Flow

  Write code
      |
      V
  Write tests with #[test]
      |
      V
  Run cargo test
      |
      V
  Tests pass? --Yes--> Done
      |
      No
      |
      V
  Fix code and run again
  

Your Learning Journey

  Module One: Rust Foundations
        |
        V
  Module Two: Control Flow and Ownership
        |
        V
  Module Three: Custom Types and Error Handling
        |
        V
  Module Four: Collections, Generics, and Traits
        |
        V
  Module Five: Concurrency and Systems Programming
        |
        V
  Module Six: Professional Rust and Capstone
        |
        V
  Certified Rust Programming Expert 🎉
  

Comparison Tables

Collection Comparison

CollectionUse CaseExample
VectorOrdered list of itemsShopping list
StringTextMessage
Hash MapKey-value lookupPhone book

Generic vs Trait

FeatureGenericTrait
PurposeWork with many typesDefine shared behaviour
Syntax<T>trait Name { }
Examplefn largest<T>()impl Greet for Person

Test vs Debug

FeatureTestDebug
PurposeCheck correctnessFind mistakes
WhenAfter writing codeWhen something fails
Tools#[test], assert_eq!println!, dbg!

Vector vs Hash Map

FeatureVectorHash Map
OrderKeeps insertion orderNo guaranteed order
AccessBy indexBy key
Use caseListsLookups

Lesson Summaries

Lesson 1: Vectors store lists of values.

Lesson 2: Strings store and manipulate text.

Lesson 3: Hash maps store key-value pairs.

Lesson 4: Iterators go through collections.

Lesson 5: Closures are short inline functions.

Lesson 6: Generics let you write flexible code.

Lesson 7: Traits define shared behaviour.

Lesson 8: Modules organise code.

Lesson 9: Tests check your code.

Lesson 10: Debugging finds and fixes mistakes.

Lesson 11: Lifetimes track reference validity.

Lesson 12: Smart pointers add abilities.

Lesson 13: Common mistakes include unhandled None and missing trait bounds.

Lesson 14: Best practices: use iterators, generics, traits, and modules.

Lesson 15: Combine collections, modules, and iterators in a market price checker.

End-of-Module Summary

Congratulations! You have finished Module Four of the Certified Rust Programming Expert course. You learned how to store many values with vectors, strings, and hash maps. You learned how to process them with iterators and closures. You learned how to write flexible code with generics and traits. You learned how to organise code with modules, check it with tests, and fix it with debugging. You also learned about lifetimes and smart pointers. You saw many examples from Nigeria and everyday life. You now know common mistakes and best practices. Most importantly, you can build complete programs that manage many values safely and efficiently. Keep practising, and you will become a Certified Rust Programming Expert!

Frequently Asked Questions

  1. What is a vector? A growable list of values of the same type.
  2. What is a hash map? A collection of key-value pairs.
  3. What is an iterator? An object that goes through items one by one.
  4. What is a closure? A short inline function.
  5. What is a generic? Code that works with many types.
  6. What is a trait? Shared behaviour that types can implement.
  7. What is a module? A group of related code.
  8. How do I write a test? Use #[test] and assert_eq!.
  9. How do I debug? Use println! and dbg!.
  10. What is a lifetime? How long a reference is valid.

Matching Exercises

Match the word to its definition.

WordDefinition
1. VectorA. Key-value storage.
2. Hash MapB. Short inline function.
3. IteratorC. Growable list.
4. ClosureD. Shared behaviour.
5. TraitE. Goes through items one by one.

Answers: 1-C, 2-A, 3-E, 4-B, 5-D

Scenario-based Exercises

  1. Scenario: You want to store a list of student names. What do you use?
    Answer: A vector.
  2. Scenario: You want to look up a student’s score by name. What do you use?
    Answer: A hash map.
  3. Scenario: You want to double every number in a list. What do you use?
    Answer: An iterator with map.
  4. Scenario: You want to write one function that works for both numbers and text. What do you use?
    Answer: Generics.
  5. Scenario: You want to check if your function works correctly. What do you write?
    Answer: A test with #[test].

Group Activity

Title: “Build a Market Price Checker”

Instructions: In groups of 3–4, write a Rust program that stores market items and prices in a hash map. Let the user type an item name, and the program prints the price. Use a loop to let the user check many items. Use a module to organise the code. Share your program with the class.

Goal: Practice hash maps, modules, loops, and input.

Individual Activity

Task: Write a Rust program that creates a vector of your five favourite foods. Use a for loop to print each food. Then use an iterator to print only the foods with more than 5 letters.

Hint: Use filter and collect.

Mini Project

Project: “My To-Do List”

Write a Rust program that stores tasks in a vector. Add a function to add a task. Add a function to remove a task by index. Add a function to print all tasks. Use a module to organise the code. Add a test to check that adding a task works.

Example output:

  1. Do homework
  2. Buy milk
  3. Call grandma
  

Practical Assignment

Assignment: Create a new Cargo project called student_scores. In main.rs, write a program that:

  1. Creates a hash map of student names to scores.
  2. Adds at least five students.
  3. Prints each student and their score.
  4. Calculates and prints the average score.
  5. Finds and prints the highest score.
  6. Uses a module to organise the code.
  7. Writes a test to check the average calculation.

Submit: Your main.rs file and a screenshot of the program running.

Key Takeaways

  • Vectors store lists. Hash maps store key-value pairs.
  • Strings store text.
  • Iterators and closures process collections cleanly.
  • Generics let you write flexible code.
  • Traits define shared behaviour.
  • Modules organise code.
  • Tests check your code.
  • Debugging finds and fixes mistakes.
  • Lifetimes track reference validity.
  • Smart pointers add abilities.
  • Practice and read error messages carefully.

Classroom Discussion Questions

  1. Why do we need collections?
  2. When would you use a vector instead of a hash map?
  3. How do iterators make code cleaner?
  4. Why are closures useful with iterators?
  5. How do generics help avoid repeating code?
  6. Why are traits important for shared behaviour?
  7. How do modules keep projects tidy?
  8. Why should you write tests?
  9. How do you debug a program effectively?
  10. What did Ada learn from the market list story?

Preparation for Module Five

In Module Five, we will dive deeper into Rust. We will learn about:

  • Concurrency: running many tasks at once.
  • Threads and message passing.
  • Async programming basics.
  • Working with files and the file system.
  • Building command-line tools.
  • Building a complete real-world project.

To prepare, make sure you have completed the practical assignment and can write programs with collections, modules, and tests. Review your notes on vectors, hash maps, and iterators. Bring your curiosity!

See you in Module Five!


End of Module Four – Certified Rust Programming Expert

6

Module Five

Certified Rust Programming Expert – Module Five

Module Five: Concurrency and Systems Programming – Doing Many Things at Once and Talking to the System

“Certified Rust Programming Expert” – A complete path from beginner to professional

Module Introduction

Welcome back, future Rust expert! In Module One, you learned the basics of Rust. In Module Two, you learned control flow and ownership. In Module Three, you learned structs, enums, and error handling. In Module Four, you learned collections, generics, traits, modules, testing, and debugging.

Now it is time to unlock one of Rust’s greatest strengths: concurrency. Concurrency means doing many things at the same time. Imagine a bank with many cashiers serving many customers at once. Or a market with many traders selling at the same time. That is concurrency.

In this module, we will learn how to run many tasks at once using threads. We will learn how to send messages between threads. We will learn how to share data safely with Mutex and Arc. We will also learn how to work with files, read command-line arguments, use environment variables, and build command-line tools.

By the end, you will be able to write programs that do many things at once, work with the file system, and behave like real systems tools. Let’s begin!

Learning Objectives

After finishing this module, you will be able to:

  • Explain what concurrency is and why it matters.
  • Create and join threads in Rust.
  • Use message passing with channels.
  • Share data safely with Mutex and Arc.
  • Read and write files using std::fs.
  • Read command-line arguments.
  • Use environment variables.
  • Build simple command-line tools.
  • Understand the basics of unsafe Rust.
  • Recognize common mistakes and best practices.
  • Complete a mini project and practical assignment.

Warm-up Story: Ada’s Busy Market Stall

Ada now runs a busy market stall in Lagos. She sells rice, beans, tomatoes, and peppers. Customers come from everywhere. Sometimes ten customers arrive at the same time. Ada cannot serve them all alone. She is too slow.

So Ada hires three helpers: Tunde, Chidi, and Ngozi. Now Ada can serve one customer, Tunde serves another, Chidi serves another, and Ngozi serves another. Four customers are served at the same time. The market stall becomes fast and efficient. That is concurrency.

But there is a problem. All four of them share one cash box. If two helpers try to take money from the box at the same time, they will get confused. Money might go missing. Ada needs a way to make sure only one person uses the cash box at a time. She puts a lock on the box. Only the person with the key can open it. That is a Mutex.

Ada also needs to keep records of every sale. She writes each sale in a big notebook. That notebook is a file. She writes to it and reads from it every day.

Finally, Ada wants to set up her stall automatically. She writes a small set of instructions for her helpers: “Open the stall, arrange the goods, count the money.” That is like a command-line tool. Ada gives the instructions as arguments.

Moral of the story: Concurrency lets you do many things at once. Locks keep shared things safe. Files store data. Command-line tools automate tasks.

Main Lessons

Lesson 1: What is Concurrency?

Definition: Concurrency means doing many tasks at the same time or almost the same time.

Why it is important: Modern computers have many cores. Concurrency lets your program use them all and finish faster.

Simple explanation: Imagine a kitchen with one cook. The cook makes one dish at a time. Now imagine four cooks. They can make four dishes at once. That is concurrency.

Real-life example: A bank with many cashiers serving many customers at once.

School example: Many teachers marking different subjects at the same time.

Home example: One person washing plates while another cooks and another sweeps.

Nigerian example: A market with many traders selling at the same time. If only one trader sold, the market would be slow.

Illustration:

  Without concurrency:
  Task A ---> Task B ---> Task C ---> Done
  (one after another, slow)

  With concurrency:
  Task A ---> Done
  Task B ---> Done
  Task C ---> Done
  (all at once, fast)
  

Mini summary: Concurrency means doing many things at once. It makes programs faster on modern computers.

Lesson 2: Threads – Workers in Your Program

Definition: A thread is a small worker inside your program. Each thread runs its own code at the same time as other threads.

Why it is important: Threads let your program do many things at once.

Simple explanation: Think of a restaurant. The main thread is the manager. Worker threads are the cooks, waiters, and cleaners. They all work at the same time.

Real-life example: A hospital with many doctors and nurses, each helping a different patient.

School example: Many students working on different group projects at the same time.

Home example: One family member cooks, another washes, another sets the table.

Nigerian example: A bus park with many conductors loading different buses at the same time.

Illustration:

  use std::thread;
  use std::time::Duration;

  fn main() {
      let handle = thread::spawn(|| {
          for i in 1..5 {
              println!("Worker thread: {}", i);
              thread::sleep(Duration::from_millis(500));
          }
      });

      for i in 1..3 {
          println!("Main thread: {}", i);
          thread::sleep(Duration::from_millis(500));
      }

      handle.join().unwrap();
  }
  

Step-by-step:

  • Use thread::spawn to create a new thread.
  • Pass a closure with the code the thread should run.
  • Use handle.join() to wait for the thread to finish.
  • Use thread::sleep to pause for a while.

Mini summary: Threads are workers in your program. Use thread::spawn to create them. Use join to wait for them.

Lesson 3: Message Passing with Channels

Definition: A channel is a way for threads to send messages to each other safely.

Why it is important: Threads should not share memory carelessly. Sending messages is safer.

Simple explanation: Imagine a post office. One thread writes a letter and sends it. Another thread receives the letter. The channel is the post office.

Real-life example: A walkie-talkie. One person speaks, another listens.

School example: Passing notes between students in class.

Home example: Sending a text message to a family member.

Nigerian example: A market woman sends a message to her supplier: “Bring more tomatoes.” The supplier receives it.

Illustration:

  use std::sync::mpsc;
  use std::thread;

  fn main() {
      let (tx, rx) = mpsc::channel();

      thread::spawn(move || {
          let msg = String::from("Hello from the worker!");
          tx.send(msg).unwrap();
      });

      let received = rx.recv().unwrap();
      println!("Got: {}", received);
  }
  

Step-by-step:

  • Create a channel with mpsc::channel().
  • tx is the sender. rx is the receiver.
  • Use tx.send(message) to send.
  • Use rx.recv() to receive.
  • Use move to give the sender to the thread.

Mini summary: Channels let threads send messages safely. Use mpsc::channel and send/recv.

Lesson 4: Sharing Data with Mutex

Definition: A Mutex is a lock that allows only one thread to access data at a time.

Why it is important: It prevents data races when many threads need the same data.

Simple explanation: Think of a bathroom with one key. Only the person with the key can enter. Others wait. Mutex is the key.

Real-life example: A single toilet in a house. Only one person uses it at a time.

School example: One whiteboard marker. Only one student writes at a time.

Home example: One TV remote. Only one person changes the channel at a time.

Nigerian example: One POS machine. Only one customer uses it at a time.

Illustration:

  use std::sync::Mutex;

  fn main() {
      let m = Mutex::new(5);

      {
          let mut num = m.lock().unwrap();
          *num = 6;
      }

      println!("m = {:?}", m);
  }
  

Step-by-step:

  • Create with Mutex::new(value).
  • Use lock() to get access.
  • lock() returns a Result.
  • When the lock goes out of scope, it is released automatically.

Mini summary: Mutex allows only one thread to access data at a time. Use lock() to get access.

Lesson 5: Shared Ownership with Arc

Definition: Arc (Atomic Reference Counted) is a smart pointer that lets many threads share ownership of the same data.

Why it is important: Mutex alone does not let many threads own the same value. Arc adds that ability.

Simple explanation: Imagine a library book that many people can read. The book is the data. Arc is the library card that lets each reader access it.

Real-life example: A shared calendar in an office. Many people can view it.

School example: A class textbook that all students can read.

Home example: A family photo album that everyone can look at.

Nigerian example: A community notice board that everyone can read.

Illustration:

  use std::sync::{Arc, Mutex};
  use std::thread;

  fn main() {
      let counter = Arc::new(Mutex::new(0));
      let mut handles = vec![];

      for _ in 0..5 {
          let counter = Arc::clone(&counter);
          let handle = thread::spawn(move || {
              let mut num = counter.lock().unwrap();
              *num += 1;
          });
          handles.push(handle);
      }

      for handle in handles {
          handle.join().unwrap();
      }

      println!("Result: {}", *counter.lock().unwrap());
  }
  

Step-by-step:

  • Wrap the value: Arc::new(Mutex::new(value)).
  • Clone the Arc for each thread with Arc::clone.
  • Lock inside the thread with lock().
  • Join all threads.

Mini summary: Arc allows many threads to share ownership. Combine with Mutex for safe sharing. Use Arc::clone.

Lesson 6: Working with Files – Reading and Writing

Definition: Files store data on your computer even after the program stops. Rust can read and write files.

Why it is important: Programs need to save data, load settings, and store logs.

Simple explanation: A file is like a notebook. You can write in it and read from it later.

Real-life example: Saving a document on your computer.

School example: Writing homework in an exercise book and reading it later.

Home example: A shopping list on the fridge.

Nigerian example: A trader’s ledger book where all sales are recorded.

Illustration:

  use std::fs;
  use std::io::Write;

  fn main() -> std::io::Result<()> {
      // Write to a file
      let mut file = fs::File::create("sales.txt")?;
      writeln!(file, "Rice: 500")?;
      writeln!(file, "Beans: 300")?;

      // Read from a file
      let content = fs::read_to_string("sales.txt")?;
      println!("File contents:\n{}", content);

      Ok(())
  }
  

Step-by-step:

  • Use fs::File::create to make a new file.
  • Use writeln! to write lines.
  • Use fs::read_to_string to read the whole file.
  • Return Result to handle errors with ?.

Mini summary: Files store data. Use std::fs to read and write. Always handle errors with Result.

Lesson 7: Command-Line Arguments

Definition: Command-line arguments are extra words you type after the program name to give it information.

Why it is important: They let users control what the program does without changing the code.

Simple explanation: Like telling a taxi driver where to go: “Take me to Yaba.” The destination is the argument.

Real-life example: Ordering food: “Give me two plates of rice.” The number is the argument.

School example: A teacher saying: “Open your book to page 42.” Page 42 is the argument.

Home example: Telling the generator: “Run for 2 hours.” 2 hours is the argument.

Nigerian example: Telling a bus conductor: “I am going to Ikeja.” Ikeja is the argument.

Illustration:

  use std::env;

  fn main() {
      let args: Vec = env::args().collect();

      if args.len() < 2 {
          println!("Please provide a name.");
          return;
      }

      println!("Hello, {}!", args[1]);
  }

  // Run with: cargo run Ada
  // Output: Hello, Ada!
  

Step-by-step:

  • Import std::env.
  • Use env::args() to get the arguments.
  • Collect them into a vector with .collect().
  • Check the length with .len().
  • Access the first real argument with args[1].

Mini summary: Command-line arguments let users pass information to your program. Use std::env::args().

Lesson 8: Environment Variables

Definition: Environment variables are settings stored by the operating system that programs can read.

Why it is important: They let you change program behaviour without changing code. They also keep secrets safe.

Simple explanation: Like a school’s rule book. Every teacher reads the same rules.

Real-life example: The temperature setting on an air conditioner.

School example: The school’s daily schedule posted on the wall.

Home example: The family rules on the fridge.

Nigerian example: A market association’s rules that all traders follow.

Illustration:

  use std::env;

  fn main() {
      match env::var("HOME") {
          Ok(home) => println!("Your home folder is: {}", home),
          Err(_) => println!("HOME not set"),
      }
  }
  

Step-by-step:

  • Import std::env.
  • Use env::var("NAME") to read.
  • It returns Result.
  • Handle Ok and Err with match.

Mini summary: Environment variables are system settings. Use env::var to read them. Always handle errors.

Lesson 9: Building a Simple Command-Line Tool

Definition: A command-line tool is a program you run from the terminal to do a specific job.

Why it is important: They are fast, simple, and powerful. Many professional tools are command-line tools.

Simple explanation: A tool like a hammer. You pick it up and use it for a job.

Real-life example: ls lists files. cp copies files.

School example: A calculator for maths.

Home example: A blender for smoothies.

Nigerian example: A POS machine for payments.

Illustration:

  use std::env;
  use std::fs;

  fn main() {
      let args: Vec = env::args().collect();

      if args.len() < 3 {
          println!("Usage: {}  ", args[0]);
          return;
      }

      match args[1].as_str() {
          "read" => {
              match fs::read_to_string(&args[2]) {
                  Ok(content) => println!("{}", content),
                  Err(e) => println!("Error: {}", e),
              }
          }
          "write" => {
              match fs::write(&args[2], "Hello, file!") {
                  Ok(_) => println!("Written to {}", args[2]),
                  Err(e) => println!("Error: {}", e),
              }
          }
          _ => println!("Unknown command: {}", args[1]),
      }
  }
  

Step-by-step:

  • Read arguments with env::args().
  • Check that enough arguments are given.
  • Use match on the first argument.
  • Do the action (read or write).
  • Handle errors with match.

Mini summary: Command-line tools are powerful. Use arguments, match, and file functions. Handle errors carefully.

Lesson 10: Introduction to unsafe Rust

Definition: unsafe is a keyword that lets you do things Rust normally prevents, like raw pointer access.

Why it is important: Sometimes you need to talk directly to hardware or use C libraries.

Simple explanation: Think of a locked room. Normally you cannot enter. With unsafe, you can, but you must be very careful.

Real-life example: A doctor entering a restricted hospital area. Only trained people should go in.

School example: Only the head teacher can enter the staff room.

Home example: Only adults can use the gas cooker.

Nigerian example: Only authorised traders can enter the central bank vault.

Illustration:

  fn main() {
      let mut num = 5;

      let r1 = &num as *const i32;

      unsafe {
          println!("r1 points to: {}", *r1);
      }
  }
  

Step-by-step:

  • Use as *const T to make a raw pointer.
  • Wrap the dangerous code in unsafe { }.
  • Dereference with *r1.
  • Use unsafe only when necessary.

Mini summary: unsafe lets you do dangerous things. Use it only when needed. Document why.

Lesson 11: Async Programming – A Simple Introduction

Definition: Async programming lets your program wait for slow tasks (like network or files) without blocking other work.

Why it is important: It makes programs that handle many users fast and efficient.

Simple explanation: Imagine a waiter taking orders from many tables. While the kitchen cooks one order, the waiter serves another table. No time is wasted.

Real-life example: A restaurant with one waiter serving many tables.

School example: One teacher helping many students at the same time.

Home example: Cooking rice while watching the stew.

Nigerian example: One POS operator serving many customers, one at a time, without waiting idly.

Illustration:

  async fn fetch_data() -> String {
      String::from("Data loaded")
  }

  fn main() {
      // Async needs a runtime, e.g. tokio.
      // This is a simple introduction.
  }
  

Step-by-step:

  • Write async fn for slow tasks.
  • Use .await to wait for the result.
  • You need an async runtime like tokio.
  • Start simple. Async is advanced.

Mini summary: Async lets you handle slow tasks without blocking. Use async and .await. You need a runtime.

Lesson 12: Common Mistakes with Concurrency

Definition: Mistakes happen. Knowing them helps you avoid them.

Why it is important: Concurrency is powerful but tricky. Rust helps, but you must be careful.

Simple explanation: Like driving a car. It is fast, but you must follow rules.

Real-life example: Two people trying to use the same ATM at the same time.

School example: Two students writing on the same whiteboard at the same time.

Home example: Two people trying to enter the same door at the same time.

Nigerian example: Two traders selling the same item and confusing the customer.

Table of common mistakes:

MistakeWhat happensRust’s help
Forgetting to join threadsProgram may exit earlyhandle.join()
Forgetting to lock before using shared dataData raceCompiler error
Using Arc without MutexCannot mutateCompiler error
Deadlock (two locks waiting on each other)Program hangsCareful design
Ignoring file errorsPanic or wrong resultUse Result and ?
Using unsafe carelesslyUndefined behaviourYou must be careful

Mini summary: Concurrency has pitfalls. Rust catches many. Read error messages. Be careful with locks and threads.

Lesson 13: Best Practices for Concurrency and Systems Programming

Definition: Best practices are good habits that make your code clean and safe.

Why it is important: Concurrency and systems code must be reliable.

Simple explanation: Like a pilot following a checklist before takeoff.

Real-life example: A surgeon washing hands before an operation.

School example: Writing your name on every exam paper.

Home example: Turning off the gas after cooking.

Nigerian example: A trader counting money twice before giving change.

List of best practices:

  • Prefer message passing (channels) over shared memory when possible.
  • Use Arc<Mutex<T>> for shared mutable data.
  • Keep locks short. Do not hold locks while doing slow work.
  • Always join threads or use scoped threads.
  • Handle all file and IO errors with Result.
  • Use ? to propagate errors cleanly.
  • Document why you use unsafe.
  • Test concurrent code carefully.
  • Use cargo clippy to catch common mistakes.
  • Read the Rust concurrency documentation.

Mini summary: Follow best practices. They keep your concurrent code safe and reliable.

Lesson 14: Putting It All Together – A Multi-Threaded File Processor

Let’s write a program that uses threads, channels, files, and error handling.

Program: Count words in many files using threads

  use std::fs;
  use std::sync::mpsc;
  use std::thread;

  fn main() {
      let files = vec!["file1.txt", "file2.txt", "file3.txt"];
      let (tx, rx) = mpsc::channel();

      for file in files {
          let tx = tx.clone();
          thread::spawn(move || {
              match fs::read_to_string(file) {
                  Ok(content) => {
                      let count = content.split_whitespace().count();
                      tx.send((file, count)).unwrap();
                  }
                  Err(e) => {
                      tx.send((file, 0)).unwrap();
                      eprintln!("Error reading {}: {}", file, e);
                  }
              }
          });
      }

      drop(tx); // close original sender

      for (file, count) in rx {
          println!("{}: {} words", file, count);
      }
  }
  

Explanation:

  • We create a channel for messages.
  • We spawn one thread per file.
  • Each thread reads its file and sends the word count.
  • The main thread receives and prints results.
  • We drop the original sender to end the loop.

Mini summary: This program combines threads, channels, files, and error handling. It is a real-world example of concurrency.

Lesson 15: Building a Simple Command-Line Tool with Concurrency

Let’s build a small tool that counts words in files given on the command line.

  use std::env;
  use std::fs;
  use std::sync::mpsc;
  use std::thread;

  fn main() {
      let args: Vec = env::args().skip(1).collect();

      if args.is_empty() {
          println!("Usage: wordcount   ...");
          return;
      }

      let (tx, rx) = mpsc::channel();

      for file in args {
          let tx = tx.clone();
          thread::spawn(move || {
              match fs::read_to_string(&file) {
                  Ok(content) => {
                      let count = content.split_whitespace().count();
                      tx.send((file, count)).unwrap();
                  }
                  Err(_) => {
                      tx.send((file, 0)).unwrap();
                  }
              }
          });
      }

      drop(tx);

      let mut total = 0;
      for (file, count) in rx {
          println!("{}: {} words", file, count);
          total += count;
      }
      println!("Total: {} words", total);
  }
  

Explanation:

  • env::args().skip(1) gets all file names after the program name.
  • Each file is processed in its own thread.
  • Results are sent back through a channel.
  • The main thread sums the counts.

Mini summary: This tool combines command-line arguments, threads, channels, and files. It is a real systems programming example.

Key Vocabulary

WordSimple Definition
ConcurrencyDoing many tasks at the same time.
ThreadA worker inside your program that runs code.
ChannelA safe way for threads to send messages.
MutexA lock that allows only one thread to access data at a time.
ArcA smart pointer for shared ownership across threads.
FileStored data on your computer.
Command-line argumentExtra words typed after a program name.
Environment variableA system setting programs can read.
Command-line toolA program run from the terminal.
unsafeA keyword that lets you do dangerous things.
AsyncDoing slow tasks without blocking others.
DeadlockTwo threads waiting on each other forever.
Data raceTwo threads accessing the same data carelessly.
joinWait for a thread to finish.
spawnCreate a new thread.

Important Concepts

  • Concurrency: Doing many tasks at once.
  • Threads: Workers inside your program.
  • Message passing: Threads send messages safely.
  • Shared state: Use Arc<Mutex<T>>.
  • Files: Store and load data with std::fs.
  • Arguments: Read with env::args().
  • Environment: Read with env::var().
  • Command-line tools: Combine arguments, files, and logic.
  • unsafe: Use only when necessary. Document why.
  • Async: Handle slow tasks without blocking.
  • Error handling: Always use Result for IO.

Step-by-step Explanations

How to spawn and join a thread step by step

  1. Import std::thread.
  2. Call thread::spawn(|| { ... }).
  3. Store the handle in a variable.
  4. Use handle.join().unwrap() to wait.

Example:

  use std::thread;

  let handle = thread::spawn(|| {
      println!("Hello from a thread!");
  });

  handle.join().unwrap();
  

How to use a channel step by step

  1. Import std::sync::mpsc.
  2. Create with let (tx, rx) = mpsc::channel();.
  3. Clone tx for each thread.
  4. Use tx.send(value) inside the thread.
  5. Use rx.recv() or for msg in rx in the main thread.

Example:

  use std::sync::mpsc;
  use std::thread;

  let (tx, rx) = mpsc::channel();

  thread::spawn(move || {
      tx.send("Hello").unwrap();
  });

  println!("{}", rx.recv().unwrap());
  

How to read a file step by step

  1. Import std::fs.
  2. Use fs::read_to_string("file.txt").
  3. It returns Result.
  4. Use match or ? to handle errors.

Example:

  use std::fs;

  match fs::read_to_string("data.txt") {
      Ok(content) => println!("{}", content),
      Err(e) => println!("Error: {}", e),
  }
  

How to read command-line arguments step by step

  1. Import std::env.
  2. Use env::args().collect::<Vec<String>>().
  3. The first item is the program name.
  4. Use args[1], args[2], etc.
  5. Check args.len() to avoid errors.

Example:

  use std::env;

  let args: Vec = env::args().collect();
  if args.len() > 1 {
      println!("Hello, {}!", args[1]);
  }
  

Real-life Examples

  • Web servers: Handle many users at once with threads.
  • Video games: Run physics, graphics, and sound on different threads.
  • Data processing: Process many files at the same time.
  • Backup tools: Read and write files.
  • Log readers: Read files and count lines.

Nigerian Examples

  • POS network: Many POS machines sending transactions to a central server.
  • Bank servers: Handling many withdrawals at once.
  • Market records: A trader’s ledger file.
  • Bus booking: Command-line tool to book seats.
  • Electricity billing: Reading meter files and calculating bills.

Fun Examples Children Can Relate To

  • Video game: One thread moves the character, one plays music, one loads levels.
  • Robot: One thread moves the arms, one reads sensors, one talks.
  • Chat app: One thread sends messages, one receives them.
  • School quiz: One thread times the quiz, one checks answers.
  • Treasure hunt: Many threads looking for treasure in different places.

Everyday Examples

  • Cooking: Boiling rice while frying stew.
  • Laundry: Washing while drying.
  • School: Reading while listening to music.
  • Commute: Listening to a podcast while walking.
  • Home: Charging phone while watching TV.

Parent Tips

  • Encourage your child to think about doing things at the same time.
  • Let them make mistakes. Concurrency is tricky.
  • Use everyday examples (cooking, laundry) to explain concurrency.
  • Set a small daily coding time. Consistency helps.
  • Celebrate small wins, like making two threads run.
  • Be patient. Concurrency takes time to understand.
  • Ask them to explain their code out loud.
  • Keep it fun. Use games and stories.
  • Remind them that even expert programmers find concurrency hard.
  • Encourage them to read Rust’s official concurrency guide.

Interesting Facts

  • Rust prevents data races at compile time.
  • Rust’s concurrency model is inspired by Erlang and Go.
  • The Arc type uses atomic operations, which are safe across threads.
  • Rust’s Mutex releases the lock automatically when it goes out of scope.
  • Rust’s unsafe keyword does not turn off all checks, only some.
  • Async Rust is used by many web servers and network tools.
  • The Rust compiler will not let you forget to join a thread if you use scoped threads.
  • Rust’s channels are inspired by Hoare’s communicating sequential processes (CSP).

Did You Know?

  • Did you know that Rust can run many threads without a garbage collector?
  • Did you know that Arc stands for “Atomic Reference Counted”?
  • Did you know that Mutex stands for “Mutual Exclusion”?
  • Did you know that mpsc stands for “Multi-Producer, Single-Consumer”?
  • Did you know that Rust’s file functions return Result for safety?
  • Did you know that env::args() includes the program name as the first item?
  • Did you know that async Rust uses a “runtime” like Tokio or async-std?
  • Did you know that unsafe is used in many standard library internals?

Remember This

  • Concurrency means doing many tasks at once.
  • Threads are workers inside your program.
  • Channels let threads send messages safely.
  • Mutex allows only one thread at a time.
  • Arc allows shared ownership across threads.
  • Files store data with std::fs.
  • Arguments come from env::args().
  • Environment variables come from env::var().
  • Command-line tools combine arguments and logic.
  • Use unsafe only when necessary.
  • Always handle errors with Result.

Common Mistakes

  • Forgetting to join threads.
  • Forgetting to lock before using shared data.
  • Using Arc without Mutex when mutating.
  • Creating deadlocks with multiple locks.
  • Ignoring file errors.
  • Using unsafe without a good reason.
  • Forgetting to drop the original sender in a channel loop.
  • Ignoring compiler error messages.

Best Practices

  • Prefer message passing over shared memory.
  • Use Arc<Mutex<T>> for shared mutable data.
  • Keep locks short.
  • Always join threads.
  • Handle all IO errors with Result.
  • Use ? to propagate errors.
  • Document why you use unsafe.
  • Test concurrent code carefully.
  • Use cargo clippy.
  • Read the Rust concurrency documentation.

Illustrations and Diagrams

Threads Running at the Same Time

  Main Thread
      |
      +--> Worker Thread 1 ---> Task A
      |
      +--> Worker Thread 2 ---> Task B
      |
      +--> Worker Thread 3 ---> Task C
      |
      V
  Join all threads
      |
      V
  Program ends
  

Channel Communication

  Thread 1 (Sender)
      |
      V
  [ Channel ]
      |
      V
  Thread 2 (Receiver)
  

Mutex Locking

  Thread 1 ---> lock() ---> Use data ---> unlock()
  Thread 2 ---> wait...          |
                                 V
                          (when unlocked)
                                 |
                                 V
  Thread 2 ---> lock() ---> Use data ---> unlock()
  

Arc + Mutex Sharing

  Arc>
        |
        +--> Thread 1: lock, change, unlock
        |
        +--> Thread 2: lock, change, unlock
        |
        +--> Thread 3: lock, change, unlock
        |
        V
  Final value
  

File Read/Write

  Program
     |
     +--> fs::File::create("file.txt")
     |         |
     |         V
     |    writeln!(file, "data")
     |
     +--> fs::read_to_string("file.txt")
               |
               V
          Ok(content) or Err(e)
  

Your Learning Journey

  Module One: Rust Foundations
        |
        V
  Module Two: Control Flow and Ownership
        |
        V
  Module Three: Custom Types and Error Handling
        |
        V
  Module Four: Collections, Generics, and Traits
        |
        V
  Module Five: Concurrency and Systems Programming
        |
        V
  Module Six: Professional Rust and Capstone
        |
        V
  Certified Rust Programming Expert 🎉
  

Comparison Tables

Threads vs Channels vs Mutex

FeatureThreadsChannelsMutex
PurposeRun code in parallelSend messagesLock shared data
SafetySafe with ownershipVery safeSafe with care
Use caseParallel tasksCommunicationShared state

Mutex vs Arc

FeatureMutexArc
PurposeLock dataShare ownership
Used withThreadsThreads
TogetherArc<Mutex<T>> for shared mutable data

File Read vs Write

OperationFunctionReturns
Readfs::read_to_stringResult<String>
Writefs::writeResult<()>
Createfs::File::createResult<File>

Safe vs Unsafe Rust

FeatureSafe RustUnsafe Rust
Memory safetyGuaranteedYou must ensure
Use caseMost programsHardware, FFI
KeywordDefaultunsafe { }

Lesson Summaries

Lesson 1: Concurrency means doing many tasks at once.

Lesson 2: Threads are workers in your program. Use thread::spawn.

Lesson 3: Channels let threads send messages safely.

Lesson 4: Mutex allows only one thread to access data at a time.

Lesson 5: Arc allows shared ownership across threads.

Lesson 6: Files store data. Use std::fs.

Lesson 7: Command-line arguments come from env::args().

Lesson 8: Environment variables come from env::var().

Lesson 9: Command-line tools combine arguments and logic.

Lesson 10: unsafe lets you do dangerous things carefully.

Lesson 11: Async lets you handle slow tasks without blocking.

Lesson 12: Common mistakes include forgetting to join threads and deadlocks.

Lesson 13: Best practices: prefer channels, keep locks short, handle errors.

Lesson 14: Combine threads, channels, and files in a multi-threaded file processor.

Lesson 15: Build a command-line tool with concurrency.

End-of-Module Summary

Congratulations! You have finished Module Five of the Certified Rust Programming Expert course. You learned how to do many things at once with threads. You learned how to send messages between threads with channels. You learned how to share data safely with Mutex and Arc. You learned how to read and write files. You learned how to use command-line arguments and environment variables. You built a command-line tool. You also learned about unsafe Rust and async programming. You saw many examples from Nigeria and everyday life. You now know common mistakes and best practices. Most importantly, you can write programs that do many things at once and talk to the system. Keep practising, and you will become a Certified Rust Programming Expert!

Frequently Asked Questions

  1. What is concurrency? Doing many tasks at the same time.
  2. What is a thread? A worker inside your program.
  3. What is a channel? A safe way for threads to send messages.
  4. What is a Mutex? A lock that allows only one thread to access data at a time.
  5. What is Arc? A smart pointer for shared ownership across threads.
  6. How do I read a file? Use fs::read_to_string.
  7. How do I get command-line arguments? Use env::args().
  8. How do I read environment variables? Use env::var().
  9. What is unsafe? A keyword that lets you do dangerous things carefully.
  10. What is async? Doing slow tasks without blocking others.

Matching Exercises

Match the word to its definition.

WordDefinition
1. ThreadA. Safe message sending.
2. ChannelB. Lock for shared data.
3. MutexC. Worker in a program.
4. ArcD. Stored data on disk.
5. FileE. Shared ownership.

Answers: 1-C, 2-A, 3-B, 4-E, 5-D

Scenario-based Exercises

  1. Scenario: You want to process 10 files at the same time. What do you use?
    Answer: Threads.
  2. Scenario: Two threads need to add to the same counter. What do you use?
    Answer: Arc<Mutex<i32>>.
  3. Scenario: A thread needs to send a result to the main thread. What do you use?
    Answer: A channel.
  4. Scenario: You want to read a file and print its contents. What do you use?
    Answer: fs::read_to_string.
  5. Scenario: You want your program to accept a filename from the user. What do you use?
    Answer: env::args().

Group Activity

Title: “Build a Multi-Threaded Word Counter”

Instructions: In groups of 3–4, write a Rust program that takes several file names from the command line. Each file is processed in its own thread. Each thread counts the words and sends the result back through a channel. The main thread prints the total. Share your program with the class.

Goal: Practice threads, channels, files, and arguments.

Individual Activity

Task: Write a Rust program that spawns three threads. Each thread prints a different message five times. Use join to wait for all of them. Then run it and observe the output.

Hint: Use thread::spawn and handle.join().

Mini Project

Project: “My File Logger”

Write a Rust program that:

  1. Creates a file called log.txt.
  2. Writes a message to the file every time it runs.
  3. Reads the file and prints all previous messages.
  4. Uses Result and ? to handle errors.

Example output:

  Log started at 10:00
  Log started at 10:05
  Log started at 10:12
  

Practical Assignment

Assignment: Create a new Cargo project called file_stats. In main.rs, write a program that:

  1. Reads file names from command-line arguments.
  2. For each file, spawns a thread that reads the file and counts lines and words.
  3. Sends the results back through a channel.
  4. Prints the results in the main thread.
  5. Handles missing files gracefully with Result.

Submit: Your main.rs file and a screenshot of the program running.

Key Takeaways

  • Concurrency means doing many tasks at once.
  • Threads are workers in your program.
  • Channels send messages safely.
  • Mutex locks shared data.
  • Arc shares ownership.
  • Files store data with std::fs.
  • Arguments come from env::args().
  • Environment variables come from env::var().
  • Command-line tools automate tasks.
  • Use unsafe only when necessary.
  • Always handle errors with Result.

Classroom Discussion Questions

  1. Why is concurrency important in modern programs?
  2. What is the difference between a thread and a process?
  3. Why are channels safer than shared memory?
  4. Why does Rust have Mutex and Arc?
  5. What could happen if two threads write to the same file at once?
  6. Why are command-line tools useful?
  7. When should you use unsafe?
  8. What is the difference between sync and async code?
  9. How do you debug a concurrent program?
  10. What did Ada learn from the busy market stall story?

Preparation for Module Six

In Module Six, we will finish the course. We will learn about:

  • Project structure and workspaces.
  • Publishing crates to crates.io.
  • Documentation with rustdoc.
  • Benchmarking and performance tuning.
  • Code review and style guidelines.
  • Security best practices.
  • Capstone project: build a complete Rust application.
  • Exam preparation and practice questions.

To prepare, make sure you have completed the practical assignment and can write programs with threads, channels, files, and arguments. Review your notes on Arc and Mutex. Bring your curiosity!

See you in Module Six!


End of Module Five – Certified Rust Programming Expert

7

Module Six

Certified Rust Programming Expert – Module Six

Module Six: Professional Rust and Capstone Project – Becoming a Certified Rust Programming Expert

“Certified Rust Programming Expert” – A complete path from beginner to professional

Module Introduction

Welcome to the final module, future Rust expert! You have come a long way. In Module One, you learned the basics of Rust. In Module Two, you learned control flow and ownership. In Module Three, you learned custom types and error handling. In Module Four, you learned collections, generics, traits, modules, testing, and debugging. In Module Five, you learned concurrency and systems programming.

Now it is time to become a professional. In this module, we will learn how real Rust developers work. We will learn about project structure and workspaces. We will learn how to publish crates to crates.io. We will learn how to write documentation with rustdoc. We will learn about benchmarking and performance tuning. We will learn code review and style guidelines. We will learn security best practices.

Finally, we will build a capstone project—a complete Rust application that uses everything you have learned. We will also prepare for the Certified Rust Programming Expert exam with practice questions.

By the end of this module, you will be ready to call yourself a Certified Rust Programming Expert. Let’s finish strong!

Learning Objectives

After finishing this module, you will be able to:

  • Organise Rust projects with workspaces and modules.
  • Publish crates to crates.io.
  • Write documentation with rustdoc.
  • Benchmark and tune performance.
  • Review code and follow style guidelines.
  • Apply security best practices.
  • Build a complete capstone project.
  • Prepare for the Certified Rust Programming Expert exam.
  • Complete the course with confidence.

Warm-up Story: Ada Builds a Real Product

Ada is now a skilled Rust programmer. She has built many small programs: a market price checker, a student report card, a multi-threaded file processor. But now Ada wants to build something bigger. She wants to build a real product that people can use.

Ada decides to build a school management system for schools in Lagos. It will store student records, calculate grades, track attendance, and print reports. It will be a real, complete application.

Ada starts by planning the project. She divides it into parts: a library for data, a command-line tool for teachers, and a web server for parents. She puts each part in its own workspace. This keeps the code clean and easy to manage.

Ada writes documentation for every function, so other programmers can understand her code. She writes tests to make sure everything works. She runs benchmarks to make sure the program is fast. She reviews her code carefully, following style guidelines. She checks for security problems, like making sure passwords are not stored in plain text.

Finally, Ada publishes her library as a crate on crates.io. Now other programmers can use it in their own projects. Ada has become a Certified Rust Programming Expert!

Moral of the story: Professional Rust is about more than writing code. It is about organising projects, documenting, testing, benchmarking, reviewing, and securing your work. With practice, you can do it too.

Main Lessons

Lesson 1: Project Structure and Workspaces

Definition: A workspace is a group of related Rust projects that share one Cargo.toml and build together.

Why it is important: Big projects need to be organised. Workspaces keep related crates together.

Simple explanation: Think of a school with many classrooms. Each classroom is a crate. The school building is the workspace.

Real-life example: A company with many departments that share one address.

School example: A school with different subjects, all under one principal.

Home example: A house with many rooms, all under one roof.

Nigerian example: A market with many stalls, all under one market association.

Illustration:

  my_workspace/
  ├── Cargo.toml          (workspace settings)
  ├── library/            (crate 1)
  │   ├── Cargo.toml
  │   └── src/lib.rs
  ├── cli_tool/           (crate 2)
  │   ├── Cargo.toml
  │   └── src/main.rs
  └── web_server/         (crate 3)
      ├── Cargo.toml
      └── src/main.rs
  

Step-by-step:

  1. Create a folder for the workspace.
  2. Add a Cargo.toml with [workspace] and members = [...].
  3. Create each crate inside the workspace folder.
  4. Run cargo build from the workspace root to build all crates.

Mini summary: Workspaces group related crates. They keep big projects organised and easy to build.

Lesson 2: Publishing Crates to crates.io

Definition: A crate is a package of Rust code. crates.io is the official website where Rust programmers share crates.

Why it is important: Publishing lets other programmers use your code. It also helps you build a reputation.

Simple explanation: Think of a library. You write a book and put it on the shelf for others to read.

Real-life example: A musician releasing an album for the world to hear.

School example: A student sharing their notes with the whole class.

Home example: A cook sharing their recipe with the neighbourhood.

Nigerian example: A trader selling goods in a big market so everyone can buy.

Illustration:

  Your Crate
      |
      V
  cargo publish
      |
      V
  Uploaded to crates.io
      |
      V
  Other programmers can use it
  

Step-by-step:

  1. Create an account on crates.io.
  2. Get an API token from your account settings.
  3. Run cargo login and paste the token.
  4. Add metadata to Cargo.toml: name, version, authors, description, license.
  5. Write documentation and tests.
  6. Run cargo publish.

Mini summary: Publishing shares your code with the world. Use cargo publish after adding metadata.

Lesson 3: Documentation with rustdoc

Definition: rustdoc is Rust’s tool for generating documentation from special comments in your code.

Why it is important: Good documentation helps others use your code. It also helps you remember what you wrote.

Simple explanation: Like writing instructions on a machine so anyone can use it.

Real-life example: A manual that comes with a new phone.

School example: A textbook with explanations for every chapter.

Home example: A recipe card with clear steps.

Nigerian example: A signboard that explains how to use a POS machine.

Illustration:

  /// Adds two numbers together.
  ///
  /// # Examples
  ///
  /// ```
  /// let result = add(2, 3);
  /// assert_eq!(result, 5);
  /// ```
  pub fn add(a: i32, b: i32) -> i32 {
      a + b
  }
  

Step-by-step:

  1. Use /// for documentation comments.
  2. Write a short description.
  3. Add examples with ```.
  4. Run cargo doc to generate HTML documentation.
  5. Run cargo doc --open to view it.

Mini summary: Documentation comments start with ///. Use cargo doc to generate HTML docs.

Lesson 4: Benchmarking and Performance Tuning

Definition: Benchmarking means measuring how fast your code runs. Performance tuning means making it faster.

Why it is important: Fast programs are better for users. Rust is already fast, but you can make it faster.

Simple explanation: Like timing how long it takes to run a race, then training to run faster.

Real-life example: A car mechanic tuning an engine for more speed.

School example: Practising maths problems to get faster.

Home example: Finding a faster route to school.

Nigerian example: A trader finding a faster way to serve customers during rush hour.

Illustration:

  use std::time::Instant;

  fn main() {
      let start = Instant::now();

      // Code to measure
      let mut sum = 0;
      for i in 0..1_000_000 {
          sum += i;
      }

      let duration = start.elapsed();
      println!("Sum: {}", sum);
      println!("Time: {:?}", duration);
  }
  

Step-by-step:

  1. Use std::time::Instant to measure time.
  2. Record Instant::now() before and after.
  3. Use .elapsed() to get the duration.
  4. Use cargo bench for more advanced benchmarking.
  5. Use cargo build --release for optimised builds.

Mini summary: Benchmarking measures speed. Use Instant and cargo bench. Always build with --release for speed.

Lesson 5: Code Review and Style Guidelines

Definition: Code review means checking code for mistakes, clarity, and style. Style guidelines are rules for writing clean code.

Why it is important: Clean code is easier to read, fix, and share.

Simple explanation: Like checking your homework before submitting. Your teacher checks it too.

Real-life example: A newspaper editor checking articles before printing.

School example: A teacher marking your essay for grammar and clarity.

Home example: A parent checking your chores before you go out.

Nigerian example: A market inspector checking that goods are properly displayed and priced.

Illustration:

  Code Review Steps:
  1. Read the code carefully.
  2. Check for mistakes.
  3. Check for clarity.
  4. Check for style.
  5. Suggest improvements.
  6. Approve or request changes.
  

Step-by-step:

  1. Use cargo fmt to format your code automatically.
  2. Use cargo clippy to catch common mistakes.
  3. Follow Rust’s naming conventions: snake_case for functions, PascalCase for types.
  4. Keep functions small and focused.
  5. Write clear comments.
  6. Review other people’s code politely.

Mini summary: Code review and style guidelines keep code clean. Use cargo fmt and cargo clippy.

Lesson 6: Security Best Practices

Definition: Security means protecting your program and data from attacks.

Why it is important: A security bug can expose private data or let attackers control your program.

Simple explanation: Like locking your door at night to keep thieves out.

Real-life example: A bank using strong locks and cameras.

School example: Keeping your exam answers secret.

Home example: Not sharing your house keys with strangers.

Nigerian example: A POS operator checking a customer’s ID before a large transaction.

Illustration:

  Security Checklist:
  +-- Validate all input.
  +-- Never store passwords in plain text.
  +-- Use HTTPS for network communication.
  +-- Keep dependencies updated.
  +-- Avoid unsafe code unless necessary.
  +-- Handle errors without leaking information.
  

Step-by-step:

  1. Validate all user input.
  2. Use hashing for passwords (e.g., bcrypt, argon2).
  3. Use HTTPS for network communication.
  4. Run cargo audit to check for known vulnerabilities.
  5. Keep dependencies updated.
  6. Avoid unsafe unless necessary.

Mini summary: Security protects your program. Validate input, hash passwords, use HTTPS, and audit dependencies.

Lesson 7: Writing a Library Crate

Definition: A library crate is a package of reusable code that other programs can use.

Why it is important: Libraries save time. You write code once and use it in many projects.

Simple explanation: Like a toolbox. You keep tools in it and use them whenever you need.

Real-life example: A standard set of screws used in many machines.

School example: A shared formula sheet used in many exams.

Home example: A recipe book used for many meals.

Nigerian example: A standard measure for selling rice in the market.

Illustration:

  library/
  ├── Cargo.toml
  └── src/
       └── lib.rs    (library code)
  

Step-by-step:

  1. Run cargo new my_library --lib.
  2. Write public functions in src/lib.rs.
  3. Use pub to make items public.
  4. Add tests in #[cfg(test)].
  5. Use the library in another project by adding it to Cargo.toml.

Mini summary: Library crates are reusable code. Use --lib to create one. Use pub to share functions.

Lesson 8: Writing a Binary Crate

Definition: A binary crate is a program you can run. It has a main function.

Why it is important: Most applications are binary crates.

Simple explanation: Like a finished dish you can serve. The library is the recipe; the binary is the meal.

Real-life example: A car you can drive. The library is the engine parts.

School example: A finished project you submit.

Home example: A cooked meal ready to eat.

Nigerian example: A finished pot of jollof rice ready to serve.

Illustration:

  binary/
  ├── Cargo.toml
  └── src/
       └── main.rs    (program entry)
  

Step-by-step:

  1. Run cargo new my_program.
  2. Write code in src/main.rs.
  3. Use fn main() as the entry point.
  4. Run with cargo run.
  5. Build with cargo build --release.

Mini summary: Binary crates are runnable programs. Use fn main() as the entry point.

Lesson 9: Using External Crates

Definition: External crates are libraries written by other people. You can use them in your project.

Why it is important: They save you from writing everything from scratch.

Simple explanation: Like buying ingredients from the market instead of growing them yourself.

Real-life example: A builder buying bricks instead of making them.

School example: Using a calculator instead of doing all maths by hand.

Home example: Buying bread instead of baking it.

Nigerian example: A trader buying goods from a wholesaler instead of making them.

Illustration:

  [dependencies]
  serde = "1.0"
  rand = "0.8"
  

Step-by-step:

  1. Find a crate on crates.io.
  2. Add it to [dependencies] in Cargo.toml.
  3. Run cargo build to download it.
  4. Use it with use crate_name::...;.
  5. Check the crate’s documentation.

Mini summary: External crates save time. Add them to Cargo.toml. Cargo downloads them automatically.

Lesson 10: Writing Good Tests

Definition: Tests are code that checks if your code works. Good tests cover many cases.

Why it is important: Tests catch mistakes early and give you confidence.

Simple explanation: Like checking your homework before submitting.

Real-life example: A doctor running many tests before a diagnosis.

School example: A teacher marking many exam papers to be fair.

Home example: Tasting food before serving guests.

Nigerian example: A trader checking goods before selling them.

Illustration:

  #[cfg(test)]
  mod tests {
      use super::*;

      #[test]
      fn test_add_positive() {
          assert_eq!(add(2, 3), 5);
      }

      #[test]
      fn test_add_negative() {
          assert_eq!(add(-2, -3), -5);
      }

      #[test]
      fn test_add_zero() {
          assert_eq!(add(0, 0), 0);
      }
  }
  

Step-by-step:

  1. Use #[cfg(test)] for the test module.
  2. Use #[test] for each test function.
  3. Use assert_eq!, assert!, and assert_ne!.
  4. Test normal, edge, and error cases.
  5. Run with cargo test.

Mini summary: Good tests cover many cases. Use #[test] and run with cargo test.

Lesson 11: Error Handling in Professional Code

Definition: Professional error handling means using Result and Option properly, not unwrap.

Why it is important: Programs must not crash unexpectedly.

Simple explanation: Like a driver who checks the road carefully instead of driving blindly.

Real-life example: An ATM that gives a clear message when the card is not valid.

School example: A teacher who explains what went wrong in an exam.

Home example: A parent who explains why you cannot go out.

Nigerian example: A POS operator who tells you exactly why a transaction failed.

Illustration:

  fn read_age(s: &str) -> Result {
      match s.parse::() {
          Ok(age) if age <= 120 => Ok(age),
          Ok(_) => Err(String::from("Age too high")),
          Err(_) => Err(String::from("Not a number")),
      }
  }
  

Step-by-step:

  1. Return Result from functions that can fail.
  2. Use ? to propagate errors.
  3. Avoid unwrap in production code.
  4. Use match or if let to handle errors gracefully.
  5. Give clear error messages.

Mini summary: Professional error handling uses Result and Option. Avoid unwrap. Give clear messages.

Lesson 12: Capstone Project – Planning

Definition: A capstone project is a big project that uses everything you learned.

Why it is important: It proves you can build real software.

Simple explanation: Like a final exam where you show everything you know.

Real-life example: A graduation project.

School example: A final year project.

Home example: A big family dinner you prepare alone.

Nigerian example: A trader opening a new shop and managing everything.

Illustration:

  Project Idea: School Management System
        |
        V
  Plan: features, crates, data
        |
        V
  Build: library, CLI, tests
        |
        V
  Test: unit, integration
        |
        V
  Document: rustdoc
        |
        V
  Publish: crates.io
        |
        V
  Celebrate 🎉
  

Step-by-step:

  1. Choose a project idea.
  2. List the features.
  3. Plan the crates and modules.
  4. Design the data structures.
  5. Write tests for each feature.
  6. Document everything.

Mini summary: Planning is the first step of the capstone. Choose a project, list features, and plan crates.

Lesson 13: Capstone Project – Building

Definition: Building the capstone means writing the code, tests, and documentation.

Why it is important: Building is where the real learning happens.

Simple explanation: Like cooking a big meal. You follow the plan step by step.

Real-life example: A builder following a blueprint.

School example: Writing your final year project.

Home example: Cooking a big family dinner.

Nigerian example: A caterer cooking for a wedding.

Illustration:

  Step 1: Create workspace
        |
        V
  Step 2: Build library crate
        |
        V
  Step 3: Build CLI crate
        |
        V
  Step 4: Add tests
        |
        V
  Step 5: Add documentation
        |
        V
  Step 6: Run cargo test and cargo clippy
        |
        V
  Step 7: Celebrate 🎉
  

Step-by-step:

  1. Create the workspace.
  2. Build the library crate first.
  3. Build the binary crate that uses the library.
  4. Write tests for the library.
  5. Write documentation comments.
  6. Run cargo test and cargo clippy.
  7. Fix any issues.

Mini summary: Building the capstone means writing code, tests, and docs. Follow the plan step by step.

Lesson 14: Capstone Project – Finishing and Publishing

Definition: Finishing means polishing the project and publishing it.

Why it is important: A finished project is proof of your skills.

Simple explanation: Like wrapping a gift beautifully before giving it.

Real-life example: A chef plating a dish before serving.

School example: Binding your project neatly before submitting.

Home example: Cleaning the house before guests arrive.

Nigerian example: A trader arranging goods beautifully before opening the stall.

Illustration:

  Polish
     |
     V
  Document
     |
     V
  Test
     |
     V
  Publish to crates.io
     |
     V
  Share with friends and teachers
     |
     V
  Celebrate 🎉
  

Step-by-step:

  1. Run cargo fmt to format code.
  2. Run cargo clippy to catch issues.
  3. Run cargo test to make sure everything works.
  4. Run cargo doc --open to check documentation.
  5. Run cargo publish to share your library.
  6. Share your project with friends, teachers, and the Rust community.

Mini summary: Finishing means polishing, testing, documenting, and publishing. Share your work with pride.

Lesson 15: Preparing for the Certified Rust Programming Expert Exam

Definition: The exam tests everything you learned in all six modules.

Why it is important: Passing the exam proves you are a Certified Rust Programming Expert.

Simple explanation: Like a final test after a long course.

Real-life example: A driving test before getting a licence.

School example: A final exam at the end of the year.

Home example: A cooking test before you are allowed to cook alone.

Nigerian example: A trade test before becoming a master trader.

Illustration:

  Review all modules
        |
        V
  Practice coding daily
        |
        V
  Write small projects
        |
        V
  Read the Rust book
        |
        V
  Take practice exams
        |
        V
  Pass the exam 🎉
  

Step-by-step:

  1. Review Modules One to Five.
  2. Practise coding every day.
  3. Write small projects to test your skills.
  4. Read the official Rust book.
  5. Take practice exams.
  6. Get a good night’s sleep before the exam.
  7. Stay calm and confident.

Mini summary: Prepare by reviewing, practising, and staying calm. You are ready!

Key Vocabulary

WordSimple Definition
WorkspaceA group of related Rust projects.
crates.ioThe official website for sharing Rust crates.
CrateA package of Rust code.
rustdocRust’s documentation tool.
BenchmarkMeasuring how fast code runs.
Performance tuningMaking code faster.
Code reviewChecking code for mistakes and style.
Style guidelinesRules for writing clean code.
SecurityProtecting code and data from attacks.
Library crateReusable code for other programs.
Binary crateA runnable program.
External crateA library written by someone else.
Capstone projectA big project that uses everything you learned.
cargo publishCommand to share your crate.
cargo clippyTool to catch common mistakes.

Important Concepts

  • Workspaces: Group related crates.
  • Publishing: Share your code on crates.io.
  • Documentation: Write clear docs with ///.
  • Benchmarking: Measure and improve speed.
  • Code review: Check code for quality.
  • Security: Validate input, hash passwords, audit dependencies.
  • Library vs binary crates: Reusable code vs runnable programs.
  • External crates: Use other people’s code.
  • Good tests: Cover normal, edge, and error cases.
  • Professional error handling: Use Result, avoid unwrap.
  • Capstone: Plan, build, test, document, publish.

Step-by-step Explanations

How to create a workspace step by step

  1. Create a folder for the workspace.
  2. Add a Cargo.toml with [workspace] and members.
  3. Create each crate inside the workspace.
  4. Run cargo build from the root.

Example:

  [workspace]
  members = ["library", "cli_tool"]
  

How to publish a crate step by step

  1. Create a crates.io account.
  2. Run cargo login with your token.
  3. Add metadata to Cargo.toml.
  4. Write docs and tests.
  5. Run cargo publish.

Example metadata:

  [package]
  name = "my_crate"
  version = "0.1.0"
  authors = ["Ada <ada@example.com>"]
  description = "A helpful Rust library"
  license = "MIT"
  

How to write documentation step by step

  1. Use /// before items.
  2. Write a short description.
  3. Add an example in ```.
  4. Run cargo doc --open.

Example:

  /// Multiplies two numbers.
  ///
  /// # Examples
  ///
  /// ```
  /// assert_eq!(multiply(2, 3), 6);
  /// ```
  pub fn multiply(a: i32, b: i32) -> i32 {
      a * b
  }
  

How to benchmark step by step

  1. Use Instant::now() before and after.
  2. Print .elapsed().
  3. For serious benchmarks, use cargo bench.
  4. Always build with --release.

Example:

  use std::time::Instant;

  let start = Instant::now();
  // work
  println!("{:?}", start.elapsed());
  

Real-life Examples

  • Companies: Use workspaces for big projects.
  • Open source: Publish crates on crates.io.
  • Documentation: Help users understand APIs.
  • Benchmarking: Make software faster.
  • Security: Protect user data.

Nigerian Examples

  • Banking software: Uses workspaces and security best practices.
  • POS systems: Need fast and secure code.
  • School systems: Use library crates for shared logic.
  • Market apps: Publish reusable crates.
  • Nollywood streaming: Uses benchmarking for fast video.

Fun Examples Children Can Relate To

  • Video game: Workspace with graphics, sound, and physics crates.
  • Robot: Library crate for movement, binary for control.
  • Chat app: Publish a library for message handling.
  • School quiz: Benchmark to make questions load fast.
  • Treasure hunt: Security to keep the treasure safe.

Everyday Examples

  • Recipe book: A library crate of recipes.
  • Family calendar: A workspace with different activities.
  • Shopping list: Documented with clear notes.
  • Cooking: Benchmarked for the fastest way.
  • House security: Locks, alarms, and careful checks.

Parent Tips

  • Encourage your child to finish what they start.
  • Let them make mistakes. Errors are learning opportunities.
  • Use everyday examples (recipe book, market) to explain concepts.
  • Set a small daily coding time. Consistency helps.
  • Celebrate small wins, like publishing a crate.
  • Be patient. Professional practices take time.
  • Ask them to teach you what they learned.
  • Keep it fun. Use games and stories.
  • Remind them that even expert programmers keep learning.
  • Encourage them to read the Rust book and join the community.

Interesting Facts

  • Rust’s package manager, Cargo, is one of the most loved tools in programming.
  • crates.io hosts over 100,000 crates.
  • cargo clippy catches hundreds of common mistakes.
  • cargo fmt uses the official Rust style guide.
  • Rust’s documentation tool, rustdoc, can run examples as tests.
  • Rust has a strong security-focused community.
  • Many companies contribute crates to the Rust ecosystem.
  • The Rust book is free and available online.

Did You Know?

  • Did you know that cargo doc --open opens your docs in a browser?
  • Did you know that cargo publish cannot be undone?
  • Did you know that you can have both a library and a binary in one crate?
  • Did you know that cargo audit checks for security vulnerabilities?
  • Did you know that rustdoc examples are tested by cargo test?
  • Did you know that cargo bench uses nightly Rust for advanced benchmarks?
  • Did you know that Rust has a Code of Conduct for its community?
  • Did you know that the Rust compiler is written in Rust?

Remember This

  • Workspaces group related crates.
  • Publish crates with cargo publish.
  • Document with /// and cargo doc.
  • Benchmark with Instant and cargo bench.
  • Review code with cargo fmt and cargo clippy.
  • Secure code with validation, hashing, and audits.
  • Library crates are reusable; binary crates are runnable.
  • External crates save time.
  • Good tests cover many cases.
  • Professional error handling uses Result.
  • Capstone: plan, build, test, document, publish.

Common Mistakes

  • Forgetting metadata before publishing.
  • Not writing documentation.
  • Not testing edge cases.
  • Using unwrap in production code.
  • Ignoring security warnings.
  • Not formatting code with cargo fmt.
  • Not running cargo clippy.
  • Forgetting to update dependencies.
  • Publishing without testing.
  • Ignoring compiler warnings.

Best Practices

  • Use workspaces for big projects.
  • Write docs for every public item.
  • Write tests for every feature.
  • Benchmark before and after changes.
  • Run cargo fmt and cargo clippy often.
  • Validate all input.
  • Hash passwords.
  • Use HTTPS.
  • Audit dependencies with cargo audit.
  • Avoid unsafe unless necessary.
  • Review code with others.
  • Keep learning and reading the Rust book.

Illustrations and Diagrams

Workspace Structure

  my_workspace/
  ├── Cargo.toml
  ├── library/
  │   ├── Cargo.toml
  │   └── src/lib.rs
  └── cli_tool/
      ├── Cargo.toml
      └── src/main.rs
  

Publishing Flow

  Write code
     |
     V
  Write tests
     |
     V
  Write docs
     |
     V
  cargo publish
     |
     V
  crates.io
     |
     V
  Others use your crate
  

Documentation Generation

  Source code with /// comments
        |
        V
  cargo doc
        |
        V
  HTML documentation
        |
        V
  cargo doc --open
        |
        V
  Browser shows docs
  

Benchmarking Timeline

  Start timer
      |
      V
  Run code
      |
      V
  Stop timer
      |
      V
  Print duration
      |
      V
  Optimise code
      |
      V
  Repeat
  

Capstone Project Flow

  Idea
    |
    V
  Plan
    |
    V
  Build
    |
    V
  Test
    |
    V
  Document
    |
    V
  Publish
    |
    V
  Celebrate 🎉
  

Your Learning Journey

  Module One: Rust Foundations
        |
        V
  Module Two: Control Flow and Ownership
        |
        V
  Module Three: Custom Types and Error Handling
        |
        V
  Module Four: Collections, Generics, and Traits
        |
        V
  Module Five: Concurrency and Systems Programming
        |
        V
  Module Six: Professional Rust and Capstone
        |
        V
  Certified Rust Programming Expert 🎉
  

Comparison Tables

Library vs Binary Crate

FeatureLibrary CrateBinary Crate
PurposeReusable codeRunnable program
Entry pointlib.rsmain.rs
Use caseShared logicApplications
Commandcargo new --libcargo new

unwrap vs Result

FeatureunwrapResult
SafetyRisky (panics)Safe
Use caseTests, prototypesProduction code
Error handlingNoneFull

Testing vs Benchmarking

FeatureTestingBenchmarking
PurposeCheck correctnessMeasure speed
Toolcargo testcargo bench
OutputPass/failTime taken

Safe vs Unsafe Rust

FeatureSafe RustUnsafe Rust
Memory safetyGuaranteedYou must ensure
Use caseMost programsHardware, FFI
KeywordDefaultunsafe { }

Lesson Summaries

Lesson 1: Workspaces group related crates.

Lesson 2: Publish crates to crates.io with cargo publish.

Lesson 3: Document with /// and cargo doc.

Lesson 4: Benchmark with Instant and cargo bench.

Lesson 5: Review code with cargo fmt and cargo clippy.

Lesson 6: Secure code with validation, hashing, and audits.

Lesson 7: Library crates are reusable code.

Lesson 8: Binary crates are runnable programs.

Lesson 9: External crates save time.

Lesson 10: Good tests cover many cases.

Lesson 11: Professional error handling uses Result.

Lesson 12: Plan the capstone project.

Lesson 13: Build the capstone project step by step.

Lesson 14: Polish, document, and publish.

Lesson 15: Prepare for the exam by reviewing and practising.

End-of-Module Summary

Congratulations! You have finished Module Six of the Certified Rust Programming Expert course. You learned how to organise big projects with workspaces. You learned how to publish crates to crates.io. You learned how to write documentation with rustdoc. You learned how to benchmark and tune performance. You learned about code review, style guidelines, and security best practices. You learned the difference between library and binary crates. You learned how to use external crates. You learned how to write good tests and handle errors professionally. You planned, built, tested, documented, and published a capstone project. You also prepared for the Certified Rust Programming Expert exam. You are now ready to call yourself a Certified Rust Programming Expert!

Frequently Asked Questions

  1. What is a workspace? A group of related Rust projects.
  2. How do I publish a crate? Use cargo publish after adding metadata.
  3. How do I write documentation? Use /// comments and cargo doc.
  4. How do I benchmark? Use Instant or cargo bench.
  5. How do I check my code style? Use cargo fmt and cargo clippy.
  6. How do I secure my code? Validate input, hash passwords, and audit dependencies.
  7. What is the difference between a library and a binary crate? Library is reusable, binary is runnable.
  8. How do I use an external crate? Add it to [dependencies] in Cargo.toml.
  9. What makes a good test? Tests that cover normal, edge, and error cases.
  10. How do I prepare for the exam? Review, practise, and stay calm.

Matching Exercises

Match the word to its definition.

WordDefinition
1. WorkspaceA. Measuring code speed.
2. CrateB. Group of related projects.
3. BenchmarkC. Package of Rust code.
4. rustdocD. Checking code for quality.
5. Code reviewE. Documentation tool.

Answers: 1-B, 2-C, 3-A, 4-E, 5-D

Scenario-based Exercises

  1. Scenario: You have three related crates. What do you use?
    Answer: A workspace.
  2. Scenario: You want to share your library with the world. What do you do?
    Answer: Publish to crates.io.
  3. Scenario: You want to measure how fast your code runs. What do you use?
    Answer: Benchmarking with Instant.
  4. Scenario: You want to catch common mistakes in your code. What do you run?
    Answer: cargo clippy.
  5. Scenario: You want to protect user passwords. What do you do?
    Answer: Hash them with bcrypt or argon2.

Group Activity

Title: “Plan a Capstone Project”

Instructions: In groups of 3–4, choose a project idea. Plan the workspace, library crate, binary crate, tests, and documentation. Present your plan to the class.

Goal: Practice planning and organisation.

Individual Activity

Task: Write a small library crate with two public functions, documentation comments, and tests. Run cargo test and cargo doc --open.

Hint: Use cargo new my_library --lib.

Mini Project

Project: “My First Published Crate”

Create a small library crate that does something useful (e.g., convert temperatures, calculate grades, or format names). Write documentation and tests. Publish it to crates.io (or prepare it for publishing).

Practical Assignment

Assignment: Create a new Cargo workspace called school_system. Inside, create two crates:

  1. A library crate called school_lib with a Student struct and functions to calculate grades.
  2. A binary crate called school_cli that uses school_lib to print student reports.
  3. Add documentation to the library.
  4. Write tests for the library.
  5. Run cargo test, cargo fmt, and cargo clippy.

Submit: Your workspace folder and screenshots of the program running and tests passing.

Key Takeaways

  • Workspaces group related crates.
  • Publish crates with cargo publish.
  • Document with /// and cargo doc.
  • Benchmark with Instant and cargo bench.
  • Review code with cargo fmt and cargo clippy.
  • Secure code with validation, hashing, and audits.
  • Library crates are reusable; binary crates are runnable.
  • External crates save time.
  • Good tests cover many cases.
  • Professional error handling uses Result.
  • Capstone: plan, build, test, document, publish.
  • Prepare for the exam by reviewing and practising.

Classroom Discussion Questions

  1. Why are workspaces useful for big projects?
  2. Why is documentation important?
  3. How does benchmarking help improve software?
  4. Why is code review important?
  5. What are the most important security practices?
  6. What is the difference between a library and a binary crate?
  7. Why should you avoid unwrap in production code?
  8. What makes a good test?
  9. How do you prepare for a big exam?
  10. What did Ada learn from building her school management system?

Course Conclusion and Next Steps

Congratulations! You have completed all six modules of the Certified Rust Programming Expert course. You have learned:

  • Rust foundations: variables, types, functions, and Cargo.
  • Control flow and ownership: if, match, loops, borrowing, and lifetimes.
  • Custom types and error handling: structs, enums, Option, and Result.
  • Collections, generics, and traits: vectors, hash maps, iterators, closures, and modules.
  • Concurrency and systems programming: threads, channels, Mutex, Arc, files, and command-line tools.
  • Professional Rust: workspaces, publishing, documentation, benchmarking, security, and the capstone project.

What’s next?

  • Take the Certified Rust Programming Expert exam.
  • Build more projects to sharpen your skills.
  • Contribute to open-source Rust projects.
  • Read the official Rust book and other advanced resources.
  • Join the Rust community online and at meetups.
  • Keep learning and never stop asking questions.

You are now a Certified Rust Programming Expert. Go out and build amazing things!

Congratulations! 🎉


End of Module Six – Certified Rust Programming Expert

🏆 Get Certified

🔒

Earn this certificate

Every lesson is already free to read. Sign up, pass the exam, and unlock Practice Tools plus a verified certificate with your name on it — ₦4,000/month.

🎓 Sign Up & Unlock for ₦4,000/month
🛠️ Practice Tools
Hands-on simulators & labs - subscription required.
→
🎯 Internship Tasks
Real-world tasks to build your portfolio - try them free for 7 days, no card required.
→