ExamplesBy LevelBy TopicLearning Paths
523 Intermediate

Event Handler Pattern

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Event Handler Pattern" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Event-driven programming is the foundation of GUI toolkits (GTK, Qt, winit), async runtimes (Tokio, async-std), and game engines (Bevy). Key difference from OCaml: 1. **Handler state**: Rust uses `FnMut` to allow handlers to maintain state between events (e.g., drag tracking); OCaml would use `ref` inside the closure for the same effect.

Tutorial

The Problem

Event-driven programming is the foundation of GUI toolkits (GTK, Qt, winit), async runtimes (Tokio, async-std), and game engines (Bevy). The central challenge is routing typed events to the correct handlers in priority order, while allowing handlers to stop propagation — preventing lower-priority handlers from seeing an event already consumed by a higher-priority one. This example implements a typed event dispatcher with priority-ordered handler chains using closures as the handler mechanism.

🎯 Learning Outcomes

  • • How FnMut(&UiEvent) -> bool closures model stateful handlers with propagation control
  • • How to sort handlers by a Priority enum using Ord derivation
  • • How to implement a dispatcher that calls handlers in priority order and respects stop-propagation
  • • How typed event enums (UiEvent) enable exhaustive handling with match
  • • Where this pattern appears in real frameworks: winit, egui, Bevy's event system
  • Code Example

    pub struct Handler {
        pub priority: Priority,
        pub name: &'static str,
        handle: Box<dyn FnMut(&UiEvent) -> bool>,
    }
    
    impl EventDispatcher {
        pub fn dispatch(&mut self, event: &UiEvent) -> bool {
            for handler in &mut self.handlers {
                if handler.handle(event) { return true; }
            }
            false
        }
    }

    Key Differences

  • Handler state: Rust uses FnMut to allow handlers to maintain state between events (e.g., drag tracking); OCaml would use ref inside the closure for the same effect.
  • Priority ordering: Rust derives Ord on an enum to enable sort_by_key; OCaml would use compare on a custom type or manual ordering.
  • Type-erased storage: Rust's Box<dyn FnMut> erases the concrete closure type; OCaml closures are already type-erased at the value level, no boxing annotation required.
  • Stop-propagation: Rust models this as bool in the return type — expressive and visible; OCaml uses the same approach or raises an exception for non-local control flow.
  • OCaml Approach

    OCaml event systems use lists of priority * (event -> bool) tuples sorted by priority. A dispatch function applies each handler in order and stops when one returns true. The event type is typically a variant type matching Rust's enum.

    type event = Click of int * int | KeyPress of char | Scroll of float
    type priority = High | Normal | Low
    let dispatch handlers event =
      List.sort (fun (p1,_) (p2,_) -> compare p1 p2) handlers
      |> List.exists (fun (_,h) -> h event)
    

    Full Source

    #![allow(clippy::all)]
    //! Event Handler Pattern
    //!
    //! Typed events with priority-ordered handler chains and propagation control.
    
    /// UI Event types.
    #[derive(Debug, Clone)]
    pub enum UiEvent {
        Click { x: i32, y: i32 },
        KeyPress(char),
        Scroll(f32),
    }
    
    /// Handler priority levels.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
    pub enum Priority {
        High = 0,
        Normal = 1,
        Low = 2,
    }
    
    /// Event handler with priority.
    pub struct Handler {
        pub priority: Priority,
        pub name: &'static str,
        handle: Box<dyn FnMut(&UiEvent) -> bool>, // true = stop propagation
    }
    
    impl Handler {
        pub fn new(
            priority: Priority,
            name: &'static str,
            f: impl FnMut(&UiEvent) -> bool + 'static,
        ) -> Self {
            Handler {
                priority,
                name,
                handle: Box::new(f),
            }
        }
    
        pub fn handle(&mut self, event: &UiEvent) -> bool {
            (self.handle)(event)
        }
    }
    
    /// Event dispatcher with ordered handlers.
    pub struct EventDispatcher {
        handlers: Vec<Handler>,
    }
    
    impl EventDispatcher {
        pub fn new() -> Self {
            EventDispatcher {
                handlers: Vec::new(),
            }
        }
    
        pub fn register(&mut self, handler: Handler) {
            self.handlers.push(handler);
            self.handlers.sort_by_key(|h| h.priority);
        }
    
        /// Dispatch event to all handlers (or until one stops propagation).
        pub fn dispatch(&mut self, event: &UiEvent) -> bool {
            for handler in &mut self.handlers {
                if handler.handle(event) {
                    return true; // propagation stopped
                }
            }
            false
        }
    }
    
    impl Default for EventDispatcher {
        fn default() -> Self {
            Self::new()
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        use std::cell::RefCell;
        use std::rc::Rc;
    
        #[test]
        fn test_handler_called() {
            let called = Rc::new(RefCell::new(false));
            let called_clone = called.clone();
    
            let mut handler = Handler::new(Priority::Normal, "test", move |_| {
                *called_clone.borrow_mut() = true;
                false
            });
    
            handler.handle(&UiEvent::Click { x: 0, y: 0 });
            assert!(*called.borrow());
        }
    
        #[test]
        fn test_dispatcher_order() {
            let order = Rc::new(RefCell::new(Vec::new()));
    
            let mut dispatcher = EventDispatcher::new();
    
            let o1 = order.clone();
            dispatcher.register(Handler::new(Priority::Low, "low", move |_| {
                o1.borrow_mut().push("low");
                false
            }));
    
            let o2 = order.clone();
            dispatcher.register(Handler::new(Priority::High, "high", move |_| {
                o2.borrow_mut().push("high");
                false
            }));
    
            let o3 = order.clone();
            dispatcher.register(Handler::new(Priority::Normal, "normal", move |_| {
                o3.borrow_mut().push("normal");
                false
            }));
    
            dispatcher.dispatch(&UiEvent::KeyPress('a'));
    
            assert_eq!(*order.borrow(), vec!["high", "normal", "low"]);
        }
    
        #[test]
        fn test_stop_propagation() {
            let calls = Rc::new(RefCell::new(0));
    
            let mut dispatcher = EventDispatcher::new();
    
            let c1 = calls.clone();
            dispatcher.register(Handler::new(Priority::High, "stopper", move |_| {
                *c1.borrow_mut() += 1;
                true // stop propagation
            }));
    
            let c2 = calls.clone();
            dispatcher.register(Handler::new(Priority::Low, "never_called", move |_| {
                *c2.borrow_mut() += 1;
                false
            }));
    
            let stopped = dispatcher.dispatch(&UiEvent::Scroll(1.0));
    
            assert!(stopped);
            assert_eq!(*calls.borrow(), 1);
        }
    
        #[test]
        fn test_event_types() {
            let mut clicks = 0;
            let mut keys = 0;
    
            let mut handler = Handler::new(Priority::Normal, "counter", move |e| {
                match e {
                    UiEvent::Click { .. } => clicks += 1,
                    UiEvent::KeyPress(_) => keys += 1,
                    _ => {}
                }
                false
            });
    
            handler.handle(&UiEvent::Click { x: 10, y: 20 });
            handler.handle(&UiEvent::KeyPress('x'));
            // Note: we can't easily access clicks/keys from outside due to move
            // This test just verifies the pattern compiles and runs
        }
    
        #[test]
        fn test_empty_dispatcher() {
            let mut dispatcher = EventDispatcher::new();
            let stopped = dispatcher.dispatch(&UiEvent::Click { x: 0, y: 0 });
            assert!(!stopped);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        use std::cell::RefCell;
        use std::rc::Rc;
    
        #[test]
        fn test_handler_called() {
            let called = Rc::new(RefCell::new(false));
            let called_clone = called.clone();
    
            let mut handler = Handler::new(Priority::Normal, "test", move |_| {
                *called_clone.borrow_mut() = true;
                false
            });
    
            handler.handle(&UiEvent::Click { x: 0, y: 0 });
            assert!(*called.borrow());
        }
    
        #[test]
        fn test_dispatcher_order() {
            let order = Rc::new(RefCell::new(Vec::new()));
    
            let mut dispatcher = EventDispatcher::new();
    
            let o1 = order.clone();
            dispatcher.register(Handler::new(Priority::Low, "low", move |_| {
                o1.borrow_mut().push("low");
                false
            }));
    
            let o2 = order.clone();
            dispatcher.register(Handler::new(Priority::High, "high", move |_| {
                o2.borrow_mut().push("high");
                false
            }));
    
            let o3 = order.clone();
            dispatcher.register(Handler::new(Priority::Normal, "normal", move |_| {
                o3.borrow_mut().push("normal");
                false
            }));
    
            dispatcher.dispatch(&UiEvent::KeyPress('a'));
    
            assert_eq!(*order.borrow(), vec!["high", "normal", "low"]);
        }
    
        #[test]
        fn test_stop_propagation() {
            let calls = Rc::new(RefCell::new(0));
    
            let mut dispatcher = EventDispatcher::new();
    
            let c1 = calls.clone();
            dispatcher.register(Handler::new(Priority::High, "stopper", move |_| {
                *c1.borrow_mut() += 1;
                true // stop propagation
            }));
    
            let c2 = calls.clone();
            dispatcher.register(Handler::new(Priority::Low, "never_called", move |_| {
                *c2.borrow_mut() += 1;
                false
            }));
    
            let stopped = dispatcher.dispatch(&UiEvent::Scroll(1.0));
    
            assert!(stopped);
            assert_eq!(*calls.borrow(), 1);
        }
    
        #[test]
        fn test_event_types() {
            let mut clicks = 0;
            let mut keys = 0;
    
            let mut handler = Handler::new(Priority::Normal, "counter", move |e| {
                match e {
                    UiEvent::Click { .. } => clicks += 1,
                    UiEvent::KeyPress(_) => keys += 1,
                    _ => {}
                }
                false
            });
    
            handler.handle(&UiEvent::Click { x: 10, y: 20 });
            handler.handle(&UiEvent::KeyPress('x'));
            // Note: we can't easily access clicks/keys from outside due to move
            // This test just verifies the pattern compiles and runs
        }
    
        #[test]
        fn test_empty_dispatcher() {
            let mut dispatcher = EventDispatcher::new();
            let stopped = dispatcher.dispatch(&UiEvent::Click { x: 0, y: 0 });
            assert!(!stopped);
        }
    }

    Deep Comparison

    OCaml vs Rust: Event Handler Pattern

    OCaml

    type ui_event = Click of int * int | KeyPress of char | Scroll of float
    
    type handler = {
      priority: int;
      name: string;
      handle: ui_event -> bool;
    }
    
    let dispatch handlers event =
      List.sort (fun a b -> compare a.priority b.priority) handlers
      |> List.exists (fun h -> h.handle event)
    

    Rust

    pub struct Handler {
        pub priority: Priority,
        pub name: &'static str,
        handle: Box<dyn FnMut(&UiEvent) -> bool>,
    }
    
    impl EventDispatcher {
        pub fn dispatch(&mut self, event: &UiEvent) -> bool {
            for handler in &mut self.handlers {
                if handler.handle(event) { return true; }
            }
            false
        }
    }
    

    Key Differences

  • OCaml: Handlers are simple record types with function fields
  • Rust: Box<dyn FnMut> for mutable closure handlers
  • Both: Support priority ordering and propagation control
  • Rust: FnMut allows stateful handlers
  • Both model event-driven systems with closure-based handlers
  • Exercises

  • Async handlers: Modify EventDispatcher to store Box<dyn FnMut(&UiEvent) -> Pin<Box<dyn Future<Output = bool>>>> and implement an async dispatch loop.
  • Wildcard handler: Add a subscribe_all(handler: impl FnMut(&UiEvent) -> bool + 'static) that registers a handler for every event type regardless of variant.
  • Handler removal by name: Implement remove(name: &str) on EventDispatcher that finds and removes the first handler with a matching name, returning whether one was found.
  • Open Source Repos