ExamplesBy LevelBy TopicLearning Paths
514 Intermediate

Closure Observer Pattern

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Closure Observer Pattern" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. The observer (publish-subscribe) pattern originated in GUI frameworks of the 1980s and became one of the Gang of Four behavioral design patterns. Key difference from OCaml: 1. **Storage**: Rust requires `Box<dyn FnMut>` with a `'static` bound to store closures in a collection; OCaml closures are heap

Tutorial

The Problem

The observer (publish-subscribe) pattern originated in GUI frameworks of the 1980s and became one of the Gang of Four behavioral design patterns. The core problem: how do you notify multiple independent components when a value changes, without tightly coupling them together? Traditional OOP solutions require implementing an EventListener interface and registering objects. Closures eliminate the need for a class hierarchy — any callable that matches the signature can be registered as a handler, making the pattern far more composable and concise.

🎯 Learning Outcomes

  • • How FnMut closures serve as stateful event handlers stored in a Vec
  • • Why Box<dyn FnMut(&E)> is needed to store heterogeneous closures together
  • • How to implement subscribe/emit mechanics with mutable interior dispatch
  • • How Observable<T> notifies listeners with both old and new values
  • • Why 'static bounds are required when storing closures beyond the current scope
  • Code Example

    pub struct EventEmitter<E> {
        handlers: Vec<Box<dyn FnMut(&E)>>,
    }
    
    impl<E> EventEmitter<E> {
        pub fn subscribe(&mut self, handler: impl FnMut(&E) + 'static) {
            self.handlers.push(Box::new(handler));
        }
    
        pub fn emit(&mut self, event: &E) {
            for handler in &mut self.handlers { handler(event); }
        }
    }

    Key Differences

  • Storage: Rust requires Box<dyn FnMut> with a 'static bound to store closures in a collection; OCaml closures are heap-allocated and GC-managed with no annotation needed.
  • Mutability tracking: Rust distinguishes Fn, FnMut, and FnOnce — using FnMut explicitly signals handlers may modify captured state; OCaml has no such distinction.
  • Propagation control: Returning bool from Rust handlers to stop propagation is natural and type-safe; OCaml uses exceptions or a mutable flag for the same effect.
  • Generic events: Rust uses a single generic EventEmitter<E> working for any event type at compile time; OCaml uses polymorphic variants or a dedicated GADT for type-safe event dispatch.
  • OCaml Approach

    OCaml implements observers with mutable reference lists holding function values. A ref holds a list of 'a -> unit functions; subscribers cons onto the list; emit iterates with List.iter. Since OCaml functions are first-class and garbage collected, there is no boxing overhead or lifetime tracking — the runtime handles memory automatically.

    let make_emitter () =
      let handlers = ref [] in
      let subscribe h = handlers := h :: !handlers in
      let emit e = List.iter (fun h -> h e) !handlers in
      (subscribe, emit)
    

    Full Source

    #![allow(clippy::all)]
    //! Observer/Callback Pattern
    //!
    //! Event system using FnMut closures as handlers.
    
    /// Event emitter that stores FnMut handlers.
    pub struct EventEmitter<E> {
        handlers: Vec<Box<dyn FnMut(&E)>>,
    }
    
    impl<E> EventEmitter<E> {
        pub fn new() -> Self {
            EventEmitter {
                handlers: Vec::new(),
            }
        }
    
        /// Register a handler — returns its index for potential removal.
        pub fn subscribe(&mut self, handler: impl FnMut(&E) + 'static) -> usize {
            self.handlers.push(Box::new(handler));
            self.handlers.len() - 1
        }
    
        /// Emit an event — all handlers are called in registration order.
        pub fn emit(&mut self, event: &E) {
            for handler in &mut self.handlers {
                handler(event);
            }
        }
    
        /// Number of registered handlers.
        pub fn handler_count(&self) -> usize {
            self.handlers.len()
        }
    }
    
    impl<E> Default for EventEmitter<E> {
        fn default() -> Self {
            Self::new()
        }
    }
    
    /// Simple button events.
    #[derive(Debug, Clone)]
    pub enum ButtonEvent {
        Click { x: i32, y: i32 },
        Hover { x: i32, y: i32 },
        KeyPress(char),
    }
    
    /// Observable value that notifies on change.
    pub struct Observable<T> {
        value: T,
        listeners: Vec<Box<dyn FnMut(&T, &T)>>, // (old, new)
    }
    
    impl<T: Clone> Observable<T> {
        pub fn new(value: T) -> Self {
            Observable {
                value,
                listeners: Vec::new(),
            }
        }
    
        pub fn get(&self) -> &T {
            &self.value
        }
    
        pub fn set(&mut self, new_value: T) {
            let old = self.value.clone();
            self.value = new_value;
            for listener in &mut self.listeners {
                listener(&old, &self.value);
            }
        }
    
        pub fn on_change(&mut self, listener: impl FnMut(&T, &T) + 'static) {
            self.listeners.push(Box::new(listener));
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        use std::cell::RefCell;
        use std::rc::Rc;
    
        #[test]
        fn test_emitter_subscribe_emit() {
            let counter = Rc::new(RefCell::new(0));
            let counter_clone = counter.clone();
    
            let mut emitter: EventEmitter<i32> = EventEmitter::new();
            emitter.subscribe(move |_| {
                *counter_clone.borrow_mut() += 1;
            });
    
            emitter.emit(&42);
            emitter.emit(&43);
    
            assert_eq!(*counter.borrow(), 2);
        }
    
        #[test]
        fn test_emitter_multiple_handlers() {
            let log = Rc::new(RefCell::new(Vec::new()));
            let log1 = log.clone();
            let log2 = log.clone();
    
            let mut emitter: EventEmitter<&str> = EventEmitter::new();
            emitter.subscribe(move |e| log1.borrow_mut().push(format!("h1:{}", e)));
            emitter.subscribe(move |e| log2.borrow_mut().push(format!("h2:{}", e)));
    
            emitter.emit(&"test");
    
            assert_eq!(*log.borrow(), vec!["h1:test", "h2:test"]);
        }
    
        #[test]
        fn test_emitter_handler_count() {
            let mut emitter: EventEmitter<()> = EventEmitter::new();
            assert_eq!(emitter.handler_count(), 0);
            emitter.subscribe(|_| {});
            assert_eq!(emitter.handler_count(), 1);
            emitter.subscribe(|_| {});
            assert_eq!(emitter.handler_count(), 2);
        }
    
        #[test]
        fn test_observable_set_notifies() {
            let changes = Rc::new(RefCell::new(Vec::new()));
            let changes_clone = changes.clone();
    
            let mut obs = Observable::new(10);
            obs.on_change(move |old, new| {
                changes_clone.borrow_mut().push((*old, *new));
            });
    
            obs.set(20);
            obs.set(30);
    
            assert_eq!(*changes.borrow(), vec![(10, 20), (20, 30)]);
        }
    
        #[test]
        fn test_observable_get() {
            let obs = Observable::new("hello");
            assert_eq!(*obs.get(), "hello");
        }
    
        #[test]
        fn test_button_events() {
            let clicks = Rc::new(RefCell::new(0));
            let clicks_clone = clicks.clone();
    
            let mut emitter: EventEmitter<ButtonEvent> = EventEmitter::new();
            emitter.subscribe(move |e| {
                if matches!(e, ButtonEvent::Click { .. }) {
                    *clicks_clone.borrow_mut() += 1;
                }
            });
    
            emitter.emit(&ButtonEvent::Click { x: 10, y: 20 });
            emitter.emit(&ButtonEvent::Hover { x: 15, y: 25 });
            emitter.emit(&ButtonEvent::Click { x: 30, y: 40 });
    
            assert_eq!(*clicks.borrow(), 2);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        use std::cell::RefCell;
        use std::rc::Rc;
    
        #[test]
        fn test_emitter_subscribe_emit() {
            let counter = Rc::new(RefCell::new(0));
            let counter_clone = counter.clone();
    
            let mut emitter: EventEmitter<i32> = EventEmitter::new();
            emitter.subscribe(move |_| {
                *counter_clone.borrow_mut() += 1;
            });
    
            emitter.emit(&42);
            emitter.emit(&43);
    
            assert_eq!(*counter.borrow(), 2);
        }
    
        #[test]
        fn test_emitter_multiple_handlers() {
            let log = Rc::new(RefCell::new(Vec::new()));
            let log1 = log.clone();
            let log2 = log.clone();
    
            let mut emitter: EventEmitter<&str> = EventEmitter::new();
            emitter.subscribe(move |e| log1.borrow_mut().push(format!("h1:{}", e)));
            emitter.subscribe(move |e| log2.borrow_mut().push(format!("h2:{}", e)));
    
            emitter.emit(&"test");
    
            assert_eq!(*log.borrow(), vec!["h1:test", "h2:test"]);
        }
    
        #[test]
        fn test_emitter_handler_count() {
            let mut emitter: EventEmitter<()> = EventEmitter::new();
            assert_eq!(emitter.handler_count(), 0);
            emitter.subscribe(|_| {});
            assert_eq!(emitter.handler_count(), 1);
            emitter.subscribe(|_| {});
            assert_eq!(emitter.handler_count(), 2);
        }
    
        #[test]
        fn test_observable_set_notifies() {
            let changes = Rc::new(RefCell::new(Vec::new()));
            let changes_clone = changes.clone();
    
            let mut obs = Observable::new(10);
            obs.on_change(move |old, new| {
                changes_clone.borrow_mut().push((*old, *new));
            });
    
            obs.set(20);
            obs.set(30);
    
            assert_eq!(*changes.borrow(), vec![(10, 20), (20, 30)]);
        }
    
        #[test]
        fn test_observable_get() {
            let obs = Observable::new("hello");
            assert_eq!(*obs.get(), "hello");
        }
    
        #[test]
        fn test_button_events() {
            let clicks = Rc::new(RefCell::new(0));
            let clicks_clone = clicks.clone();
    
            let mut emitter: EventEmitter<ButtonEvent> = EventEmitter::new();
            emitter.subscribe(move |e| {
                if matches!(e, ButtonEvent::Click { .. }) {
                    *clicks_clone.borrow_mut() += 1;
                }
            });
    
            emitter.emit(&ButtonEvent::Click { x: 10, y: 20 });
            emitter.emit(&ButtonEvent::Hover { x: 15, y: 25 });
            emitter.emit(&ButtonEvent::Click { x: 30, y: 40 });
    
            assert_eq!(*clicks.borrow(), 2);
        }
    }

    Deep Comparison

    OCaml vs Rust: Observer Pattern

    OCaml

    type 'e emitter = {
      mutable handlers: ('e -> unit) list
    }
    
    let subscribe emitter handler =
      emitter.handlers <- handler :: emitter.handlers
    
    let emit emitter event =
      List.iter (fun h -> h event) emitter.handlers
    

    Rust

    pub struct EventEmitter<E> {
        handlers: Vec<Box<dyn FnMut(&E)>>,
    }
    
    impl<E> EventEmitter<E> {
        pub fn subscribe(&mut self, handler: impl FnMut(&E) + 'static) {
            self.handlers.push(Box::new(handler));
        }
    
        pub fn emit(&mut self, event: &E) {
            for handler in &mut self.handlers { handler(event); }
        }
    }
    

    Key Differences

  • OCaml: Mutable list of handlers, immutable closures
  • Rust: FnMut allows handlers to mutate their captured state
  • Both: Closures stored in collections for later invocation
  • Rust: Explicit lifetime bounds with 'static
  • Both enable decoupled event-driven architectures
  • Exercises

  • Unsubscribe support: Extend EventEmitter so subscribe returns an index handle and add unsubscribe(handle: usize) that removes that handler by swapping in a no-op closure.
  • Filtered subscription: Add subscribe_filtered(pred, handler) that only calls handler when pred(&event) returns true, composing the predicate inside the stored closure.
  • Once handler: Add subscribe_once that automatically unregisters the handler after it fires once, using an Option inside the closure that is taken on first call.
  • Open Source Repos