Structs, enums, and pattern matching
Ownership keeps your memory correct. The type system keeps your logic correct — and Rust’s is built around one idea borrowed from functional languages: model your data so that invalid states simply can’t be constructed.
Structs
Structs are the familiar part — named fields, no surprises:
struct Post {
title: String,
published: bool,
views: u64,
}Enums are the good part
A Rust enum isn’t a list of integer constants — each variant can carry its own data:
enum Status {
Draft,
Scheduled { publish_at: String },
Published { views: u64 },
}A scheduled post has a publish date; a published one has a view count; a draft has neither. There is no way to construct a draft with views. The bug where you read publish_at on a published post isn’t caught at runtime — it’s unwritable.
Match makes it total
match forces you to handle every variant:
fn describe(status: &Status) -> String {
match status {
Status::Draft => "draft".to_string(),
Status::Scheduled { publish_at } => format!("goes live {publish_at}"),
Status::Published { views } => format!("{views} views"),
}
}Delete an arm and the program stops compiling. Add a new variant next year, and the compiler lists every match in the codebase you now need to update. This is exhaustiveness checking, and once you’ve shipped refactors with it, going back to a language without it feels like removing the handrails.
The standard library runs on this machinery: Option<T> is an enum (Some/None) that replaces null, and Result<T, E> (Ok/Err) replaces exceptions. You’ve now seen everything they’re made of — which is exactly where the next part picks up.