Fork me on GitHub

Project Notes

#347 Create Laundry Item

Using rust to implement a generator for a laundry object; cassidoo’s interview question of the week (2025-08-11). Rust does not encourage classic object-oriented patterns by default, but it can be used to model similar concepts with the struct keyword.

Notes

The interview question of the week (2025-08-18) teases a classic object-oriented generator pattern:

Write a generator function createLaundryItem() that returns an object representing a laundry item. This object should have a method nextCycle() which, when called, advances the item through a series of laundry cycles in order: “soak”, “wash”, “rinse”, “spin”, and “dry”. After the final cycle, subsequent calls to nextCycle() should return “done”.

Example:

let towel = createLaundryItem();

console.log(towel.nextCycle()); // "soak"
console.log(towel.nextCycle()); // "wash"
console.log(towel.nextCycle()); // "rinse"
console.log(towel.nextCycle()); // "spin"
console.log(towel.nextCycle()); // "dry"
console.log(towel.nextCycle()); // "done"
console.log(towel.nextCycle()); // "done"

The Basic Approach

While rust is not inherently object-oriented, it does support OOP patterns with its struct support.

For example, we could model the LaundryItem as a struct, with a LaundryState enum to track the current state, and implement a public method nextCycle to handle state transition, returning the new state:

pub struct LaundryItem {
    cycle: LaundryState
}

impl LaundryItem {
    pub fn nextCycle(&mut self) -> String {
        ...
    }
}

As for tracking the state and managing state transitions, there are perhaps 3 main approaches:

  1. Use an enum with a next() method, e.g.:

       enum State {
           Soak,
           Wash,
           Dry,
       }
    
       impl State {
           fn next(self) -> Option<Self> {
               use State::*;
               match self {
                   Soak => Some(Wash),
                   Wash => Some(Dry),
                   Dry => None, // reached the end
               }
           }
       }
    
  2. Using num_enum for automatic numeric conversion, e.g.:

       use num_enum::{IntoPrimitive, TryFromPrimitive};
    
       #[derive(Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)]
       #[repr(u8)]
       enum State {
           Soak = 0,
           Wash = 1,
           Dry = 2,
       }
    
       impl State {
           fn next(self) -> Option<Self> {
               let value: u8 = self.into();
               Self::try_from(value + 1).ok()
           }
       }
    
  3. Using strum for iteration

       use strum::IntoEnumIterator;
       use strum_macros::EnumIter;
    
       #[derive(Debug, EnumIter, Clone, Copy, PartialEq, Eq)]
       enum State {
           Start,
           Middle,
           End,
       }
    
       fn main() {
           for state in State::iter() {
               println!("{:?}", state);
           }
       }
    

Initial Implementation

I decided to keep things simple with an enum to codify the possible states, and hard-code the state transition rules in the next() method.

I’ve also decided to go with conventional rust linting practices rather than follow the specification to the letter, hence snake-case create_laundry_item() instead of createLaundryItem(), and next_cycle() instead of nextCycle().

In action:

$ cd laundry
$ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/laundry`
soak
wash
rinse
spin
dry
done
done

Tests

I’ve added some basic unit tests for the main algorithm functions:

$ cargo test
   Compiling laundry v0.1.0 (~/LittleCodingKata/rust/create-laundry-item/laundry)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.10s
     Running unittests src/lib.rs (target/debug/deps/laundry-ad372ac221031815)

running 2 tests
test tests::create_laundry_item_returns_expected_object ... ok
test tests::create_laundry_next_cycle_transitions_states_correctly ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/laundry-c662dc5c5fe8e1f1)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests laundry

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Example Code

Final code comprises:

main.rs:

use laundry::create_laundry_item;

fn main() {
    let mut towel = create_laundry_item();

    println!("{}", towel.next_cycle());
    println!("{}", towel.next_cycle());
    println!("{}", towel.next_cycle());
    println!("{}", towel.next_cycle());
    println!("{}", towel.next_cycle());
    println!("{}", towel.next_cycle());
    println!("{}", towel.next_cycle());
}

lib.rs:

pub fn create_laundry_item() -> LaundryItem {
  LaundryItem {
    cycle: LaundryState::Ready
  }
}

#[derive(Debug)]
pub struct LaundryItem {
    cycle: LaundryState
}

impl LaundryItem {
    pub fn next_cycle(&mut self) -> String {
        self.cycle = match self.cycle  {
            LaundryState::Ready => LaundryState::Soak,
            LaundryState::Soak => LaundryState::Wash,
            LaundryState::Wash => LaundryState::Rinse,
            LaundryState::Rinse => LaundryState::Spin,
            LaundryState::Spin => LaundryState::Dry,
            LaundryState::Dry => LaundryState::Done,
            LaundryState::Done => LaundryState::Done
        };
        format!("{:?}", &self.cycle).to_lowercase()
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum LaundryState {
    Ready,
    Soak,
    Wash,
    Rinse,
    Spin,
    Dry,
    Done
}

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

    #[test]
    fn create_laundry_item_returns_expected_object() {
        let obj = create_laundry_item();
        assert_eq!(obj.cycle, LaundryState::Ready);
    }

    #[test]
    fn create_laundry_next_cycle_transitions_states_correctly() {
        let mut obj = create_laundry_item();
        assert_eq!(obj.next_cycle(), "soak");
        assert_eq!(obj.next_cycle(), "wash");
        assert_eq!(obj.next_cycle(), "rinse");
        assert_eq!(obj.next_cycle(), "spin");
        assert_eq!(obj.next_cycle(), "dry");
        assert_eq!(obj.next_cycle(), "done");
        assert_eq!(obj.next_cycle(), "done");
        assert_eq!(obj.cycle, LaundryState::Done);
    }
}

Credits and References

About LCK#347 rustcassidoo

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

Project Source on GitHub Return to the LittleCodingKata Catalog
About LittleCodingKata

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.

Follow the Blog follow projects and notes as they are published in your favourite feed reader