User Guide

Relevant source files

This guide provides practical instructions for using the handler_table crate in your applications. The guide covers the complete API usage workflow, from initialization through event handling and cleanup. For detailed API documentation, see API Reference, and for comprehensive examples, see Usage Examples. For implementation details and internals, see Implementation Details.

Overview

The HandlerTable<N> provides a lock-free, fixed-size table for registering and invoking event handlers. It is designed for no_std environments where traditional synchronization primitives are unavailable or undesirable. The table uses atomic operations to ensure thread safety without blocking.

Core Usage Pattern:

flowchart TD
Create["HandlerTable::new()"]
Register["register_handler(idx, fn)"]
Handle["handle(idx)"]
Unregister["unregister_handler(idx)"]
Handle2["handle(other_idx)"]

Create --> Register
Handle --> Unregister
Register --> Handle
Register --> Handle2

Sources: README.md(L1 - L32) 

Key Concepts

The HandlerTable<N> operates on several fundamental concepts:

ConceptDescriptionCode Entity
Fixed SizeTable size determined at compile timeHandlerTablegeneric parameter
Event IndexNumeric identifier for each event typeidxparameter in methods
Handler FunctionZero-argument closure or function pointerFnOnce()trait bound
Atomic StorageLock-free concurrent accessInternalAtomicUsizearray

Handler Table Lifecycle


Sources: README.md(L11 - L31) 

Quick Start Example

The basic usage follows a simple pattern of registration, handling, and optional cleanup:

use handler_table::HandlerTable;

// Create a table with 8 slots
static TABLE: HandlerTable<8> = HandlerTable::new();

// Register handlers for specific event indices
TABLE.register_handler(0, || {
    println!("Hello, event 0!");
});
TABLE.register_handler(1, || {
    println!("Hello, event 1!");
});

// Handle events by index
assert!(TABLE.handle(0));  // Returns true, prints message
assert!(!TABLE.handle(2)); // Returns false, no handler registered

// Unregister and retrieve handlers
let handler = TABLE.unregister_handler(1).unwrap();
handler(); // Can still call the retrieved handler

Sources: README.md(L11 - L31) 

API Workflow

The following diagram shows how the main API methods interact with the internal atomic storage:

sequenceDiagram
    participant UserCode as "User Code"
    participant HandlerTableN as "HandlerTable<N>"
    participant AtomicUsizeidx as "AtomicUsize[idx]"

    Note over UserCode,AtomicUsizeidx: "Registration Phase"
    UserCode ->> HandlerTableN: "register_handler(idx, fn)"
    HandlerTableN ->> AtomicUsizeidx: "compare_exchange(0, handler_ptr)"
    alt "Slot Empty"
        AtomicUsizeidx -->> HandlerTableN: "Ok(0)"
        HandlerTableN -->> UserCode: "true (success)"
    else "Slot Occupied"
        AtomicUsizeidx -->> HandlerTableN: "Err(current_ptr)"
        HandlerTableN -->> UserCode: "false (already registered)"
    end
    Note over UserCode,AtomicUsizeidx: "Execution Phase"
    UserCode ->> HandlerTableN: "handle(idx)"
    HandlerTableN ->> AtomicUsizeidx: "load(Ordering::Acquire)"
    AtomicUsizeidx -->> HandlerTableN: "handler_ptr or 0"
    alt "Handler Present"
        HandlerTableN ->> HandlerTableN: "transmute and call handler"
        HandlerTableN -->> UserCode: "true (handled)"
    else "No Handler"
        HandlerTableN -->> UserCode: "false (not handled)"
    end
    Note over UserCode,AtomicUsizeidx: "Cleanup Phase"
    UserCode ->> HandlerTableN: "unregister_handler(idx)"
    HandlerTableN ->> AtomicUsizeidx: "swap(0, Ordering::Acquire)"
    AtomicUsizeidx -->> HandlerTableN: "old_handler_ptr"
    HandlerTableN -->> UserCode: "Some(handler_fn) or None"

Sources: README.md(L16 - L30) 

Core API Methods

The HandlerTable<N> provides four essential methods for handler management:

Initialization

  • HandlerTable::new() - Creates a new empty handler table with all slots initialized to zero

Registration

  • register_handler(idx: usize, handler: impl FnOnce()) - Atomically registers a handler function at the specified index
  • Returns true if registration succeeds
  • Returns false if a handler is already registered at that index

Execution

  • handle(idx: usize) - Invokes the handler registered at the specified index
  • Returns true if a handler was found and executed
  • Returns false if no handler is registered at that index

Cleanup

  • unregister_handler(idx: usize) - Atomically removes and returns the handler at the specified index
  • Returns Some(handler) if a handler was registered
  • Returns None if no handler was registered
flowchart TD
subgraph subGraph1["Internal AtomicUsize Array"]
    slot0["slots[0]: AtomicUsize"]
    slot1["slots[1]: AtomicUsize"]
    slotN["slots[N-1]: AtomicUsize"]
end
subgraph subGraph0["HandlerTable Public API"]
    new["new() -> HandlerTable"]
    register["register_handler(idx, fn) -> bool"]
    handle["handle(idx) -> bool"]
    unregister["unregister_handler(idx) -> Option"]
end

handle --> slot0
handle --> slot1
handle --> slotN
new --> slot0
new --> slot1
new --> slotN
register --> slot0
register --> slot1
register --> slotN
unregister --> slot0
unregister --> slot1
unregister --> slotN

Sources: README.md(L16 - L30) 

Best Practices

Size Selection

Choose the table size N based on your maximum expected number of concurrent event types. The size is fixed at compile time and cannot be changed dynamically.

Index Management

  • Use consistent index values across your application
  • Consider defining constants for event indices to avoid magic numbers
  • Ensure indices are within bounds (0 to N-1)

Handler Design

  • Keep handlers lightweight and fast-executing
  • Avoid blocking operations within handlers
  • Consider handler re-entrancy if the same index might be triggered during execution

Concurrent Usage

  • Multiple threads can safely call any method simultaneously
  • Registration conflicts return false rather than blocking
  • Handler execution is atomic but the handler itself may not be re-entrant

Error Handling

  • Check return values from register_handler() to handle registration conflicts
  • Check return values from handle() to distinguish between handled and unhandled events
  • Use the return value from unregister_handler() to verify successful removal

Sources: README.md(L1 - L32)