088 — Allergies
Tutorial Video
Text description (accessibility)
This video demonstrates the "088 — Allergies" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Given a score (up to 255), determine which allergens a person reacts to by decoding a bitflag. Key difference from OCaml: | Aspect | Rust | OCaml |
Tutorial
The Problem
Given a score (up to 255), determine which allergens a person reacts to by decoding a bitflag. Each of the eight allergens maps to a power-of-two score. Implement is_allergic_to(allergen, score) -> bool and allergies(score) -> Vec<Allergen> using bitwise AND. Compare with OCaml's land operator and list filtering.
🎯 Learning Outcomes
&) to test individual bits in a compact integer representationmatch or a shift expressionAllergen::ALL constant array to iterate all variants for allergiesu32 score (not u8) avoids overflow when scores combineland (logical AND) integer operatorCode Example
#![allow(clippy::all)]
/// 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]);
}
}Key Differences
| Aspect | Rust | OCaml |
|---|---|---|
| Bitwise AND | score & allergen.score() != 0 | score land allergen_score allergen <> 0 |
| All variants | const ALL: [Allergen; 8] | let all = [Eggs; …] (list) |
| Filter | .iter().filter(…).collect() | List.filter (fun a -> …) all |
| Score type | u32 | int |
| Variant copy | #[derive(Copy)] | Value type by default |
| Score mapping | match self { Eggs => 1, … } | function \| Eggs -> 1 \| … |
The bitflag pattern is efficient and compact: eight boolean attributes stored in a single byte. The same technique underpins Unix file permissions, network flags, and capability bitmasks. The enum-to-power-of-two mapping with a const ALL array is a clean Rust idiom for this use case.
OCaml Approach
OCaml uses land (bitwise AND): score land allergen_score allergen <> 0. allergies score is List.filter (fun a -> is_allergic_to a score) all where all is a manually defined list. The logic is identical; the differences are syntactic (land vs &, list vs array) and minor.
Full Source
#![allow(clippy::all)]
/// 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]);
}
}#[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]);
}
}
Deep 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[Eggs; Peanuts; ...] as the universe of allergensList.filter to find matching allergensfunction keyword for concise pattern matchRust Approach
& operator for bitwise ANDconst ALL array on the enum for iteration.filter().collect() with iterator chain1 << i alternative using bit positionComparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Bitwise AND | land | & |
| Enum list | let all = [...] | const ALL: [Allergen; 8] |
| Filter | List.filter | .filter().collect() |
| Score type | int | u32 |
| String name | Manual function | Method returning &'static str |
Learner Notes
land/lor — uses C-style &, |, ^ operatorsscore & (1 << i) is an alternative to explicit score matchingbitflags crate for production Rust bitflag patternsExercises
score match with a bit-shift: 1u32 << (self as u32) for a zero-indexed enum. Verify the same scores result.Allergen::from_score(score: u32) -> Option<Allergen> that maps a single power-of-two back to a variant.allergies_count(score: u32) -> usize using .count_ones() on the score casted to u8.u16.has_all : allergen list -> int -> bool predicate that returns true only when every allergen in the list is active in the score.