AI Instructor Live Labs Included

Idiomatic Rust: Traits, Generics, Error Handling

Traits, generics, error handling, and the orphan rule. Reach fluency with Rust's type system. Course 3 of 12 in the Programming with Rust skill path.

Intermediate
19h 5m
21 Lessons
SMU-RUST-300
Idiomatic Rust — Traits, Generics, Error Handling Badge

View badge details

About This Course

Reach fluency with Rust's type system. You learn to express abstractions over types using traits and generics, write production-grade error-handling pipelines with Result and the thiserror / anyhow crates, and read any production Rust codebase without stopping on the type signatures. By the end of this course you can declare and implement traits with default methods, bound generics with multiple constraints, choose between static dispatch (impl Trait, generics) and dynamic dispatch (dyn Trait), design error types for both libraries and applications, and understand what the compiler is checking when you see a trait-bound error.By the end of this course you will be able to declare traits with associated types + default implementations, implement traits for both your own and foreign types (navigating the orphan rule), use generics bounded by multiple traits, pick between static dispatch and dynamic dispatch for a given shape, write custom error types with thiserror, propagate errors with anyhow in applications, and organize code into modules + crates for a real project.Prerequisites: Courses 1 and 2 of the Programming with Rust skill path OR equivalent fluency with ownership + borrows + lifetimes.

Course Curriculum

21 Lessons
01
AI Lesson
AI Lesson

Enums + match in depth — exhaustiveness, if let, while let, nested patterns, guards

1h 0m

Reach fluency with Rust's enum and match ecosystem. You start from the exhaustive-match guarantee the compiler enforces, then layer on the ergonomic shortcuts (if let, while let, let-else) that keep code flat, and finish with the refinement tools (guards, range patterns, @ bindings) that make match arms expressive without becoming unreadable. By the end you can read any production Rust match block with confidence and know when to reach for which tool.

By the end of this lesson you will be able to explain why Rust's compiler forces every match on an enum to be exhaustive, apply if let / while let / let-else to replace nested-match pyramids, and pick between a plain pattern, a guard, a range, or an @ binding for a given classification shape.

Follows Rust 2024 edition on stable Rust 1.99. Prerequisites: Course 1 + Course 2.

02
Lab Exercise
Lab Exercise

Enum + match in practice — exhaustive, guards, let-else - Lab Exercises

40m 5 Exercises

Northhollow Analytics needs a tiny request-router. Across five short exercises you implement a handle function that routes an enum Request through an exhaustive match, catches edge cases with a guard, destructures nested fields, uses let-else to short-circuit, and finishes with an @ binding on a sub-range of IDs.

By the end of this lab you can read any Rust codebase that leans on enums + pattern matching and write handle-style routers that compile the first time.

03
AI Lesson
AI Lesson

Option and Result in production — map, and_then, unwrap_or, ok_or, and ? in depth

1h 0m

Internalize the Option and Result combinator vocabulary that idiomatic Rust code uses every day. By the end of this lesson you can replace a 10-line match pyramid with a 1-line .and_then chain, explain how the ? operator uses From to auto-convert between error types, and use the .collect::<Result<Vec<_>, _>>() idiom to turn an iterator of Results into a single short-circuiting Result. Builds on Course 1 Lesson 8 (basics of panic / Result / Option / ?) and prepares for Lesson 3's thiserror and anyhow.

04
Lab Exercise
Lab Exercise

Build Option/Result pipelines with combinators - Lab Exercises

40m 5 Exercises

Build real Rust pipelines by chaining .map, .and_then, and ? on Option<T> and Result<T, E>. You replace nested match ladders with linear combinator chains, use .ok_or to turn Option into Result, and finish with the .collect::<Result<Vec<_>, _>>() idiom that batch-validates a sequence of fallible inputs and short-circuits on the first error. By the end you can design readable, error-propagating pipelines that look nothing like the C-style step-by-step error checks a developer coming from another language might first write in Rust.

05
AI Lesson
AI Lesson

Error handling design — thiserror for libraries, anyhow for applications

1h 0m

Learn the two canonical error-handling strategies for Rust: precise custom error types for libraries, and context-rich wrapped errors for applications. You will see when thiserror makes sense (library crates whose callers need to match on specific variants), when anyhow makes sense (binaries and CLIs that just propagate), and how the two nest when a binary built with anyhow calls libraries that return thiserror types.

By the end of this lesson you will be able to recognize when precise error enums are the right design, author a thiserror enum with #[from] + #[source] + #[error("...")] attributes, and layer anyhow::Error with .context("...") to produce the human-readable error chains that make CLI failures debuggable.

This lesson builds on Course 1 Lesson 8 (panic / Result / Option / ?) and Course 3 Lesson 2 (combinators + the ? auto-From conversion). Written against the current Rust 2024 edition on stable Rust 1.99.

06
Lab Exercise
Lab Exercise

Design error types with thiserror and anyhow - Lab Exercises

45m 5 Exercises

Design a production-shaped error strategy — your library uses thiserror to expose a precise, matchable error enum; your application uses anyhow + .context() to propagate anything with human-readable chains. You take a two-crate Cargo workspace with stubbed LoadError + load_config, build out the thiserror variants with #[error(...)] attributes and #[from] conversions, implement the real file-read + parse logic, and finally wire the application entry point to add a context frame before printing the chain.

By the end you can design an idiomatic error type for a library, enable From conversions with #[from], propagate across crate boundaries with ?, and wrap library errors in anyhow::Error with .context() so operators see a readable chain in production logs.

07
AI Lesson
AI Lesson

Traits — definition, implementation, default methods, basic trait bounds

1h 0m

Reach fluency with Rust traits — the primitive that powers Rust's polymorphism. In this lesson you learn to declare traits with required and default methods, implement traits for your own and foreign types, bound generics with trait constraints, and pick between inline, where-clause, and impl Trait syntax. By the end you can write a generic function that works for any type that satisfies a trait contract, without stopping to think about which syntax to use.

By the end of this lesson you will be able to declare a trait with required and default methods, implement it for a concrete type, explain when overriding a default method is appropriate, write a generic function bounded by one or more traits, and pick between inline, where-clause, and impl Trait syntax for a given signature shape.

Prerequisite: Course 1 Lesson 11 (Structs, methods, derived traits) and comfort reading Rust struct + impl syntax.

08
Lab Exercise
Lab Exercise

Design a Shape trait with default methods - Lab Exercises

40m 5 Exercises

Design a Rust Shape trait with required methods and a default implementation that depends on them, then use it with bounded generics. Across the exercises you declare trait Shape with area and perimeter, add a default describe method, implement the trait for Circle, Rectangle, and Triangle, override describe for one shape, and write generic report and total-area functions bounded by the trait. The optional capstone exercise observes why mixing concrete types in a single Vec needs dynamic dispatch — the setup for Lesson 5.

09
AI Lesson
AI Lesson

Trait objects — dyn Trait, dynamic dispatch, object safety

1h 15m

Trait objects let a program carry different concrete types through the same interface at runtime. You learn how dyn Trait is laid out in memory, when to pick dynamic dispatch over the generic alternative (and vice versa), which traits can and cannot become trait objects, and the native upcasting the 2024 edition unlocked.

By the end of this lesson you will be able to read any Box<dyn Trait> or &dyn Trait signature in a production codebase, decide between static and dynamic dispatch for a specific situation, recognise when a trait is not object-safe and apply the where Self: Sized escape hatch, and reason about the real runtime cost of trait objects.

10
Lab Exercise
Lab Exercise

Heterogeneous shapes with Vec<Box<dyn Shape>> - Lab Exercises

45m 5 Exercises

Store different shape types in the same collection and iterate over them with dynamic dispatch. Across the exercises you build a Vec of boxed trait objects, call methods through the trait object, compute an aggregate across the heterogeneous collection, and hit the object-safety constraint head-on by trying to add a method that cannot live in a vtable. By the end of this lab you can read a Vec<Box<dyn Shape>> + know when to reach for dynamic dispatch over static generics.

11
AI Lesson
AI Lesson

Generics in depth — associated types, where clauses, PhantomData

1h 15m

Reach fluency with Rust's deeper type system. You practice bounded generics with multiple constraints, learn when to reach for a where clause instead of inline bounds, define traits with associated types for the "one natural type per implementor" shape, work out the decision rule between generic parameters and associated types, use PhantomData for compile-time type-state patterns, and get a preview of const generics for dimension-safe array operations.

By the end of this lesson you will be able to write generic functions with multi-trait bounds using the clearest syntax for the shape, define a trait with an associated type and justify the choice, apply PhantomData to a type-state pattern, and read any production Rust trait signature with precision.

12
Lab Exercise
Lab Exercise

Build a Repository trait with associated types - Lab Exercises

50m 5 Exercises

Design a Repository abstraction that stops being tied to a specific storage type. You build a Repository trait with an associated Item type, implement it for a generic InMemoryRepo<T>, then layer a type-state pattern with PhantomData<State> so the compiler enforces that only an authenticated repo can delete rows. Associated types vs generic parameters is the hardest Intermediate concept in Rust's type system; this lab exercises both choices back-to-back against the same shape so the tradeoff clicks. By the end you can design small type-system APIs that make incorrect uses fail to compile.

13
AI Lesson
AI Lesson

The orphan rule — when you can and can't impl Foreign for Foreign

45m

The orphan rule — the one coherence rule that quietly shapes every Rust library. In 45 minutes you learn what the rule is, why Rust enforces it, and the two canonical escape hatches: the newtype pattern for adding behavior to a single foreign type, and extension traits with blanket impls for the common "I want this method on everything that converts into a String" case.

By the end of this lesson you will be able to state the orphan rule precisely, decide whether an impl you want to write is legal or will fail with E0117, apply the newtype pattern to wrap a foreign type so you can impl a foreign trait on it, and design a trait + blanket impl pair that lets you add methods to any type you don't own.

14
Lab Exercise
Lab Exercise

Work around the orphan rule — newtype and extension traits - Lab Exercises

40m 5 Exercises

Hit the orphan rule the hard way. The starter opens with an impl ToString for Vec<i32> that fails to compile because neither the trait nor the type belongs to our crate. You read the error, make the project build, then apply the two canonical workarounds Rust gives you: wrap the foreign type in a newtype so YOU own one of the two sides, or define a brand-new trait in your crate and attach it to foreign types via a blanket impl. By the end you can look at any "you can only implement traits defined in the current crate" error and know which escape hatch fits.

15
AI Lesson
AI Lesson

Derive macros in practice — Debug, Clone, PartialEq, Serialize/Deserialize

1h 0m

Reach for #[derive(...)] the way production Rust code does. You learn what the compiler synthesizes when you add Debug or Clone, name the 8 std-derivable traits with the field-level requirement each one imposes, and use serde's derive + field attributes to decode a real JSON API response into a Rust struct.

By the end of this lesson you can explain the macro-expansion mechanics of #[derive(Trait)], pick the right subset of the 8 std-derivable traits for a given struct, call out when derive will produce wrong behavior (floats + Eq, hash-order undefined fields), and set up a serde Deserialize impl with rename / skip / default attributes to handle a mismatched JSON schema.

16
Lab Exercise
Lab Exercise

Derive macros + serde in practice - Lab Exercises

45m 5 Exercises

Add the derive macros that unlock serde + round-trip a config struct through JSON. You open the starter and watch cargo build fail because AppConfig doesn't yet derive Deserialize. Reading that compiler error is step one. Then you add #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)], import the trait names, and the same cargo run prints a parsed struct. From there you reach into serde's field attributes to rename fields on the Rust side while keeping the JSON key stable, default values for optional-at-parse-time fields, and skip-if-none on round-trip output.

By the end you can look at a plain struct declaration and know which four or five derive attributes it needs to feel like a first-class data type, when to reach for #[serde(...)] field attributes, and why the standard-library's Hash derive refuses to apply to a type with floating-point fields.

Pairs with Course 3 Lesson 8 on derive macros.

17
AI Lesson
AI Lesson

Modules + crate organization — mod, pub, use, workspaces

1h 0m

Scale from one-file crates to real multi-file + multi-crate Rust projects. This lesson covers the four file-based module layouts, the four visibility levels (pub, pub(crate), pub(super), pub(in path)), the lib.rs vs main.rs distinction, and cargo workspaces with path dependencies. By the end you can lay out a Rust project the way the standard library and major crates do, and you know when to pick each layout over the others.

By the end of this lesson you will be able to organize a Rust project into file-based modules with the right visibility level per item, choose between src/foo.rs and src/foo/mod.rs for a given module shape, split a crate into a reusable library and one or more binaries, and set up a cargo workspace with path-dependency member crates.

Course 3 Lesson 9. Prerequisites: Course 1 Lesson 13 (modules + crates intro) + Courses 1 and 2 overall.

18
Lab Exercise
Lab Exercise

Split a project into a cargo workspace with lib + bin crates - Lab Exercises

50m 5 Exercises

Refactor a flat single-crate Rust project into a cargo workspace. You keep the shape-rendering code working end-to-end while you first split it into a module file, then promote it out of main.rs into a sibling library crate (shapes-core), and wire a binary crate (shapes-cli) to depend on it via a path dependency. By the end you understand how real multi-crate Rust projects are organized: when to use mod vs a sibling crate, how [workspace.dependencies] shares versions across members, and how integration tests live beside library code.

19
AI Lesson
AI Lesson

Cargo in daily use — dependencies, features, cargo tree, cargo add

1h 0m

You already know Cargo well enough to build and run a Rust project. This lesson closes the gap between "cargo works" and "I use Cargo with intent." You learn how semver resolves foo = "1.2" versus foo = "=1.2.0", when cargo add edits Cargo.toml for you and when the hand-edit path is still right, how feature flags shape your build and which pattern (default-features = false + opt-in) keeps binaries lean, how cargo tree -d surfaces duplicate dependency versions, when cargo update is actually safe inside a 0.x series, and how [workspace.dependencies] pins library versions once for every member crate. By the end of this lesson you can read any production Cargo.toml and know what each line is asking of the resolver.

20
Lab Exercise
Lab Exercise

Cargo in practice — features, tree, and workspace deps - Lab Exercises

45m 5 Exercises

Practice day-to-day Cargo workflows. You pick up a dep-free starter and drive it through the operations a working Rust developer does weekly — adding deps with cargo add, defining your own features with #[cfg(...)] gates, reading cargo tree -d to spot duplicated transitive dependencies, and tuning the release profile for binary size. No new Rust language concepts; this is about owning the tooling.

By the end of this lab you will be able to add deps via cargo add (including feature flags), define and consume your own crate features with #[cfg(feature = "...")] gates, read cargo tree output to understand your dependency graph, and tune release profiles for size or speed.

21
Lab Exercise
Lab Exercise

Capstone — Build a mini-grep CLI with traits, generics, and error design - Lab Exercises

1h 30m 6 Exercises

Course 3 capstone. Synthesize everything the course taught — traits, generics, dynamic dispatch, thiserror, anyhow, modules, workspaces, derive macros, clap, and serde — into one small build: a mini-grep CLI that reads a pattern + an optional file, matches lines, and prints hits.

By the end of the lab you have shipped a two-crate cargo workspace: a grep-core library with a Pattern trait (Literal + Regex implementations), a Source trait (File + Stdin implementations behind Box<dyn Source>), and a thiserror-based GrepError. A grep-cli binary wires it all together with clap-parsed arguments, anyhow::Context on IO calls, and optional serde-loaded defaults from a config file. The lab is the capstone sign-off for Course 3 and hands off to Course 4 (Iterators, Collections, and Functional Rust).

Prerequisites: completion of Lessons 1–10 of Course 3. Follows the current Rust 2024 edition on stable Rust 1.99.

This course includes:

  • 24/7 AI Instructor Support
  • Live Lab Environments
  • 11 Hands-on Lessons
  • Completion Badge
Idiomatic Rust — Traits, Generics, Error Handling Badge

Earn Your Badge

Complete all lessons to unlock the Idiomatic Rust — Traits, Generics, Error Handling achievement badge.

Skill Level Intermediate
Total Duration 19h 5m
Idiomatic Rust — Traits, Generics, Error Handling Badge
Achievement Badge

Idiomatic Rust — Traits, Generics, Error Handling

Awarded for completing SMU-RUST-300 Idiomatic Rust: Traits, Generics, Error Handling. Demonstrates fluency with trait definitions + implementations, generic bounds, static vs dynamic dispatch, custom error types via thiserror, error propagation via anyhow, and module/crate organization.

Course Idiomatic Rust: Traits, Generics, Error Handling
Criteria Complete all lessons with assessment scores of 80% or above and submit the final capstone (refactor a working application to use traits, generics, and proper error types).

Skills You'll Earn

Rust traits Generics Trait bounds Static vs dynamic dispatch Associated types Error handling with thiserror and anyhow Modules and crates The orphan rule

Complete all lessons in this course to earn this badge