🦀 Functional Rust
🎬 How Rust Iterators Work Lazy evaluation, chaining, collect(), and zero-cost abstractions.
📝 Text version (for readers / accessibility)

• Iterators are lazy — .map(), .filter(), .take() build a chain but do no work until consumed

• .collect() triggers evaluation, transforming the chain into a Vec, HashMap, or other collection

• Zero-cost abstraction: iterator chains compile to the same machine code as hand-written loops

• .iter() borrows, .into_iter() consumes, .iter_mut() borrows mutably

• Chaining replaces nested loops with a readable, composable pipeline

088: Allergies — Bitflag Decoding

Difficulty: Beginner Category: Enums / Bit Manipulation Concept: Using bitwise operations to encode and decode sets of flags Key Insight: Bitflag patterns are nearly identical in both languages. OCaml uses `land` (logical AND); Rust uses `&`. Both use enums to give meaning to bit positions.
/// Allergies — Bitflag Decoding
///
/// Ownership: Allergen is Copy. Score is a simple u32.
/// No heap allocation needed.

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Allergen {
    Eggs, Peanuts, Shellfish, Strawberries,
    Tomatoes, Chocolate, Pollen, Cats,
}

impl Allergen {
    pub const ALL: [Allergen; 8] = [
        Allergen::Eggs, Allergen::Peanuts, Allergen::Shellfish,
        Allergen::Strawberries, Allergen::Tomatoes, Allergen::Chocolate,
        Allergen::Pollen, Allergen::Cats,
    ];

    pub fn score(self) -> u32 {
        match self {
            Allergen::Eggs => 1,
            Allergen::Peanuts => 2,
            Allergen::Shellfish => 4,
            Allergen::Strawberries => 8,
            Allergen::Tomatoes => 16,
            Allergen::Chocolate => 32,
            Allergen::Pollen => 64,
            Allergen::Cats => 128,
        }
    }

    pub fn name(self) -> &'static str {
        match self {
            Allergen::Eggs => "eggs",
            Allergen::Peanuts => "peanuts",
            Allergen::Shellfish => "shellfish",
            Allergen::Strawberries => "strawberries",
            Allergen::Tomatoes => "tomatoes",
            Allergen::Chocolate => "chocolate",
            Allergen::Pollen => "pollen",
            Allergen::Cats => "cats",
        }
    }
}

pub fn is_allergic_to(allergen: Allergen, score: u32) -> bool {
    score & allergen.score() != 0
}

pub fn allergies(score: u32) -> Vec<Allergen> {
    Allergen::ALL
        .iter()
        .copied()
        .filter(|&a| is_allergic_to(a, score))
        .collect()
}

/// Version 2: Using bit position instead of match
pub fn allergies_bitpos(score: u32) -> Vec<Allergen> {
    Allergen::ALL
        .iter()
        .enumerate()
        .filter(|&(i, _)| score & (1 << i) != 0)
        .map(|(_, &a)| a)
        .collect()
}

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

    #[test]
    fn test_eggs_only() {
        assert_eq!(allergies(1), vec![Allergen::Eggs]);
    }

    #[test]
    fn test_peanuts_and_chocolate() {
        assert_eq!(allergies(34), vec![Allergen::Peanuts, Allergen::Chocolate]);
    }

    #[test]
    fn test_everything() {
        assert_eq!(allergies(255).len(), 8);
    }

    #[test]
    fn test_none() {
        assert_eq!(allergies(0), vec![]);
    }

    #[test]
    fn test_is_allergic() {
        assert!(is_allergic_to(Allergen::Peanuts, 34));
        assert!(!is_allergic_to(Allergen::Eggs, 34));
    }

    #[test]
    fn test_ignore_high_bits() {
        assert_eq!(allergies(257), vec![Allergen::Eggs]);
    }
}

fn main() {
    println!("{:?}", allergies(1), vec![Allergen::Eggs]);
    println!("{:?}", allergies(34), vec![Allergen::Peanuts, Allergen::Chocolate]);
    println!("{:?}", allergies(255).len(), 8);
}
(* Allergies — Bitflag Decoding *)

type allergen = Eggs | Peanuts | Shellfish | Strawberries
  | Tomatoes | Chocolate | Pollen | Cats

let allergen_score = function
  | Eggs -> 1 | Peanuts -> 2 | Shellfish -> 4 | Strawberries -> 8
  | Tomatoes -> 16 | Chocolate -> 32 | Pollen -> 64 | Cats -> 128

let all = [Eggs;Peanuts;Shellfish;Strawberries;Tomatoes;Chocolate;Pollen;Cats]

let is_allergic_to allergen score =
  score land allergen_score allergen <> 0

let allergies score =
  List.filter (fun a -> is_allergic_to a score) all

let () =
  assert (allergies 34 |> List.map allergen_score = [2; 32]);
  assert (is_allergic_to Peanuts 34);
  assert (not (is_allergic_to Eggs 34))

📊 Detailed Comparison

Allergies — Comparison

Core Insight

Bitflag decoding maps cleanly between both languages. The pattern — enumerate variants, assign power-of-2 scores, use bitwise AND to test membership — is universal. The difference is syntactic, not conceptual.

OCaml Approach

  • `land` operator for bitwise AND
  • Variant list `[Eggs; Peanuts; ...]` as the universe of allergens
  • `List.filter` to find matching allergens
  • `function` keyword for concise pattern match

Rust Approach

  • `&` operator for bitwise AND
  • `const ALL` array on the enum for iteration
  • `.filter().collect()` with iterator chain
  • `1 << i` alternative using bit position

Comparison Table

AspectOCamlRust
Bitwise AND`land``&`
Enum list`let all = [...]``const ALL: [Allergen; 8]`
Filter`List.filter``.filter().collect()`
Score type`int``u32`
String nameManual functionMethod returning `&'static str`

Learner Notes

  • Rust has no `land`/`lor` — uses C-style `&`, `|`, `^` operators
  • `score & (1 << i)` is an alternative to explicit score matching
  • Both languages guarantee exhaustive matches — adding an allergen forces updates
  • Consider `bitflags` crate for production Rust bitflag patterns