Variants — Days of the Week
Tutorial Video
Text description (accessibility)
This video demonstrates the "Variants — Days of the Week" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Algebraic Data Types. Model the days of the week as an algebraic data type. Key difference from OCaml: 1. **Derive macros:** Rust requires explicit `#[derive(Debug, Clone, Copy, PartialEq, Eq)]`; OCaml gets these implicitly
Tutorial
The Problem
Model the days of the week as an algebraic data type. Implement day_name, is_weekend, and next_day using exhaustive pattern matching.
🎯 Learning Outcomes
impl methods#[derive] to get traits OCaml variants have implicitly🦀 The Rust Way
impl block methods (self.name(), self.is_weekend(), self.next())next_dayCode Example
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat }
impl Day {
pub fn name(self) -> &'static str {
match self {
Day::Sun => "Sunday", Day::Mon => "Monday", /* ... */
}
}
pub fn is_weekend(self) -> bool {
matches!(self, Day::Sun | Day::Sat)
}
pub fn next(self) -> Day {
match self {
Day::Sun => Day::Mon, /* ... */ Day::Sat => Day::Sun,
}
}
}Key Differences
#[derive(Debug, Clone, Copy, PartialEq, Eq)]; OCaml gets these implicitlyimpl blocks; OCaml uses standalone functionsDisplay impl; OCaml can derive show via ppx or manually printCopy must be opted into in Rust; OCaml values are always copyableenum Day and OCaml's type day = Mon | Tue | ... are closed sum types — the set of values is fixed at definition time. This enables exhaustiveness checking.switch:** Both languages check exhaustiveness at compile time. Java's switch on enums checks exhaustiveness only with --enable-preview in Java 17+. C's switch has no exhaustiveness checking.PartialEq, Debug:** Rust's #[derive] automatically generates equality and debug printing for enums. OCaml's structural equality (=) works on variant types without any annotation.impl Day block adds methods directly to the enum. OCaml uses standalone functions (no method syntax). This is a major ergonomic difference.OCaml Approach
OCaml variants are lightweight — declare the type, write functions with match/function. Equality, printing, and copying come free. All functions are standalone (no method syntax).
Full Source
#![allow(clippy::all)]
//! # Variants — Days of the Week
//!
//! OCaml variants map cleanly to Rust enums. Both are algebraic data types
//! with exhaustive pattern matching enforced by the compiler.
// ---------------------------------------------------------------------------
// Approach A: Idiomatic Rust — enum with methods via impl block
// ---------------------------------------------------------------------------
/// Days of the week as a simple C-like enum.
///
/// We derive common traits that OCaml variants get implicitly:
/// - `Debug` for printing
/// - `Clone, Copy` because this is a simple fieldless enum
/// - `PartialEq, Eq` for equality comparison
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Day {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat,
}
impl Day {
/// Human-readable name.
pub fn name(self) -> &'static str {
match self {
Day::Sun => "Sunday",
Day::Mon => "Monday",
Day::Tue => "Tuesday",
Day::Wed => "Wednesday",
Day::Thu => "Thursday",
Day::Fri => "Friday",
Day::Sat => "Saturday",
}
}
/// Is this a weekend day?
pub fn is_weekend(self) -> bool {
matches!(self, Day::Sun | Day::Sat)
}
/// Next day of the week (wraps around).
pub fn next(self) -> Day {
match self {
Day::Sun => Day::Mon,
Day::Mon => Day::Tue,
Day::Tue => Day::Wed,
Day::Wed => Day::Thu,
Day::Thu => Day::Fri,
Day::Fri => Day::Sat,
Day::Sat => Day::Sun,
}
}
}
// ---------------------------------------------------------------------------
// Approach B: Functional style — free functions with pattern matching
// ---------------------------------------------------------------------------
/// Mirrors the OCaml `day_name` function — a standalone function,
/// not a method.
pub fn day_name(d: Day) -> &'static str {
// Identical logic but as a free function rather than a method.
// In OCaml all functions on types are free functions.
d.name()
}
pub fn is_weekend(d: Day) -> bool {
d.is_weekend()
}
pub fn next_day(d: Day) -> Day {
d.next()
}
// ---------------------------------------------------------------------------
// Approach C: Numeric representation — using discriminants
// ---------------------------------------------------------------------------
impl Day {
/// Convert from a 0-based index (Sun=0 .. Sat=6).
pub fn from_index(i: u8) -> Option<Day> {
match i {
0 => Some(Day::Sun),
1 => Some(Day::Mon),
2 => Some(Day::Tue),
3 => Some(Day::Wed),
4 => Some(Day::Thu),
5 => Some(Day::Fri),
6 => Some(Day::Sat),
_ => None,
}
}
/// Convert to a 0-based index.
pub fn to_index(self) -> u8 {
self as u8
}
/// Next day using arithmetic modulo — avoids exhaustive match.
pub fn next_arithmetic(self) -> Day {
Day::from_index((self.to_index() + 1) % 7).unwrap()
}
}
/// Display trait for pretty-printing.
impl std::fmt::Display for Day {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_day_names() {
assert_eq!(Day::Sun.name(), "Sunday");
assert_eq!(Day::Wed.name(), "Wednesday");
assert_eq!(Day::Sat.name(), "Saturday");
}
#[test]
fn test_is_weekend() {
assert!(Day::Sun.is_weekend());
assert!(Day::Sat.is_weekend());
assert!(!Day::Mon.is_weekend());
assert!(!Day::Wed.is_weekend());
assert!(!Day::Fri.is_weekend());
}
#[test]
fn test_next_day() {
assert_eq!(Day::Sun.next(), Day::Mon);
assert_eq!(Day::Wed.next(), Day::Thu);
assert_eq!(Day::Sat.next(), Day::Sun); // wraps around
}
#[test]
fn test_next_full_cycle() {
// Going through all 7 days should return to start
let mut d = Day::Mon;
for _ in 0..7 {
d = d.next();
}
assert_eq!(d, Day::Mon);
}
#[test]
fn test_arithmetic_next() {
assert_eq!(Day::Sun.next_arithmetic(), Day::Mon);
assert_eq!(Day::Sat.next_arithmetic(), Day::Sun);
// Should agree with pattern-match version for all days
for i in 0..7 {
let d = Day::from_index(i).unwrap();
assert_eq!(d.next(), d.next_arithmetic());
}
}
#[test]
fn test_from_index_invalid() {
assert_eq!(Day::from_index(7), None);
assert_eq!(Day::from_index(255), None);
}
#[test]
fn test_display() {
assert_eq!(format!("{}", Day::Fri), "Friday");
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_day_names() {
assert_eq!(Day::Sun.name(), "Sunday");
assert_eq!(Day::Wed.name(), "Wednesday");
assert_eq!(Day::Sat.name(), "Saturday");
}
#[test]
fn test_is_weekend() {
assert!(Day::Sun.is_weekend());
assert!(Day::Sat.is_weekend());
assert!(!Day::Mon.is_weekend());
assert!(!Day::Wed.is_weekend());
assert!(!Day::Fri.is_weekend());
}
#[test]
fn test_next_day() {
assert_eq!(Day::Sun.next(), Day::Mon);
assert_eq!(Day::Wed.next(), Day::Thu);
assert_eq!(Day::Sat.next(), Day::Sun); // wraps around
}
#[test]
fn test_next_full_cycle() {
// Going through all 7 days should return to start
let mut d = Day::Mon;
for _ in 0..7 {
d = d.next();
}
assert_eq!(d, Day::Mon);
}
#[test]
fn test_arithmetic_next() {
assert_eq!(Day::Sun.next_arithmetic(), Day::Mon);
assert_eq!(Day::Sat.next_arithmetic(), Day::Sun);
// Should agree with pattern-match version for all days
for i in 0..7 {
let d = Day::from_index(i).unwrap();
assert_eq!(d.next(), d.next_arithmetic());
}
}
#[test]
fn test_from_index_invalid() {
assert_eq!(Day::from_index(7), None);
assert_eq!(Day::from_index(255), None);
}
#[test]
fn test_display() {
assert_eq!(format!("{}", Day::Fri), "Friday");
}
}
Deep Comparison
Comparison: Variants — Days of the Week — OCaml vs Rust
OCaml
type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat
let day_name = function
| Sun -> "Sunday" | Mon -> "Monday" | Tue -> "Tuesday"
| Wed -> "Wednesday" | Thu -> "Thursday" | Fri -> "Friday"
| Sat -> "Saturday"
let is_weekend = function
| Sun | Sat -> true
| _ -> false
let next_day = function
| Sun -> Mon | Mon -> Tue | Tue -> Wed | Wed -> Thu
| Thu -> Fri | Fri -> Sat | Sat -> Sun
Rust — Idiomatic (impl methods)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat }
impl Day {
pub fn name(self) -> &'static str {
match self {
Day::Sun => "Sunday", Day::Mon => "Monday", /* ... */
}
}
pub fn is_weekend(self) -> bool {
matches!(self, Day::Sun | Day::Sat)
}
pub fn next(self) -> Day {
match self {
Day::Sun => Day::Mon, /* ... */ Day::Sat => Day::Sun,
}
}
}
Rust — Numeric (discriminant arithmetic)
impl Day {
pub fn from_index(i: u8) -> Option<Day> { /* 0..=6 → variant */ }
pub fn to_index(self) -> u8 { self as u8 }
pub fn next_arithmetic(self) -> Day {
Day::from_index((self.to_index() + 1) % 7).unwrap()
}
}
Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Declaration | type day = Sun \| Mon \| ... | enum Day { Sun, Mon, ... } |
| Equality | Built-in (structural) | Requires #[derive(PartialEq, Eq)] |
| Copying | Implicit (all values copyable) | Requires #[derive(Clone, Copy)] |
| Debug printing | Via ppx_deriving or manual | #[derive(Debug)] |
| Pattern syntax | function \| Sun -> ... | match self { Day::Sun => ... } |
| Or-patterns | \| Sun \| Sat -> true | matches!(self, Day::Sun \| Day::Sat) |
| Namespace | Flat (just Sun) | Prefixed (Day::Sun) unless use Day::* |
| Method attachment | No (free functions only) | impl Day { fn name(self) ... } |
Type Signatures Explained
OCaml: val day_name : day -> string — simple function from variant to string
Rust: fn name(self) -> &'static str — method taking self by copy (since Day: Copy), returning a string slice with 'static lifetime (string literals live forever)
Takeaways
derive macros replace OCaml's built-in structural equality and copyDay::Sun); OCaml's are module-level (Sun)impl blocks; OCaml keeps everything as standalone functionsmatches! macro** is Rust's ergonomic equivalent of OCaml's multi-arm pattern returning boolExercises
is_weekend to the Day enum using match, and implement working_days_until that counts weekdays between two days of the week.Day enum to a WorkDay { day: Day, hours: f32 } struct and implement total_hours for a slice of WorkDay values using an iterator fold.Month enum with all 12 months and implement days_in_month that accounts for leap years, then write a calendar_days iterator that yields every (Month, u8) day pair for a given year.days_until(from: Day, to: Day) -> usize that returns the number of days from from to to, wrapping around (Monday to Friday = 4, Friday to Monday = 3).is_business_day(day: Day) -> bool and next_business_day(day: Day) -> Day using pattern matching. Then implement business_days_between(from: Day, to: Day) -> usize.