What Is a “Handler” in Rust?


handler

Handlers

Understanding Handlers, Dispatch Tables, and Function Pointers

If you’re learning Rust and you keep seeing the word handler, here’s the plain truth:

A handler is just a function that gets called in response to something happening.

That “something” could be:

  • a command name
  • an opcode
  • an HTTP route
  • a button click
  • an MCP tool invocation

The key idea is you don’t call the handler directly.
Some other part of the program (a framework, router, or engine) chooses which handler to call and then calls it for you.


A Minimal Handler Example

Let’s start with a very simple Rust example:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn oper(a: i32, b: i32, op: fn(i32, i32) -> i32) -> i32 {
    op(a, b)
}

fn main() {
    let result = oper(2, 3, add);
    println!("{}", result);
}

Here’s what’s really happening:

  • add is a handler
  • oper is a dispatcher
  • op is a function pointer (a pointer to executable code)

The dispatcher doesn’t care what the handler does.
It only cares that it can call it with (i32, i32) and get back an i32.

That separation is the foundation of handler-based design.


Why This Pattern Exists

Instead of writing logic like this:

if command == "add" {
    add(a, b)
} else if command == "sub" {
    sub(a, b)
}

We move the decision into data, not code.

This leads us to the next concept.


Dispatch Tables: Where Handlers Live

A dispatch table is just a mapping from some key to a handler.

use std::collections::HashMap;

type Handler = fn(i32, i32) -> i32;

fn add(a: i32, b: i32) -> i32 { a + b }
fn sub(a: i32, b: i32) -> i32 { a - b }

fn main() {
    let mut table: HashMap<&str, Handler> = HashMap::new();
    table.insert("add", add);
    table.insert("sub", sub);

    let command = "add";
    let result = table[command](10, 4);

    println!("{}", result);
}

Now the program says:

“When I see the command add, call the add handler.”

This is exactly how:

  • HTTP routers
  • CLI command parsers
  • protocol decoders
  • MCP tool registries

work internally.


Why Use fn for Handlers?

Using fn means:

  • no captured state
  • no heap allocation
  • simple, uniform types
  • easy storage in tables
  • FFI and ABI friendly

That’s why low-level systems, servers, and runtimes still use function-pointer handlers today.

If you later need state or closures, you can upgrade to Fn or Box<dyn Fn>.
But conceptually, it’s still a handler.


The One-Sentence Mental Model

A handler is the function that runs when a specific command, event, or tool is selected — and a dispatch table decides which one gets called.

Once you understand that, a huge amount of Rust infrastructure suddenly makes sense.

Dispatch tables are everywhere:

  • Web servers → map URLs to route handlers
  • Games → map button presses to actions
  • MCP / command systems → map tool names to tool functions
  • Interpreters → map opcodes to operations

Basically, any time your program sees “something happened” and needs to pick a function to run, that’s a dispatch table.

The function name without parentheses is the pointer/reference to the function itself

When you do this in Actix or Axum:

// Axum
let app = Router::new()
    .route("/users", get(get_users))  // ← function pointer!
    .route("/login", post(login));     // ← function pointer!

// Actix
HttpServer::new(|| {
    App::new()
        .route("/users", web::get().to(get_users))  // ← function pointer!
})

You’re passing function pointers to the framework. The framework stores these and calls them later when a request comes in.

The flow:

  1. Setup time: You pass get_users (without ()) → framework stores the pointer
  2. Request time: HTTP request arrives → framework calls get_users() (with ())

Why this matters:

  • The framework needs to control when to call your handler (when a request arrives)
  • It needs to pass the right arguments (Request, extractors, etc.)
  • If you wrote get(get_users()), it would call your function immediately during setup, not when requests arrive!

So yes, every handler registration in web frameworks is using function pointers under the hood.