Input-Based Dispatch with UseInputDelegate
This chapter shows how one compute component can route to different providers based on input type.
Goal:
- define small handlers per expression node type,
- wire them with
UseInputDelegate, - dispatch enum variants with
MatchWithValueHandlersRef.
If you remember one sentence, use this:
UseInputDelegatechooses a provider by input type.
Vocabulary first
- Input type: the type being computed (for example
Number,Plus, or MathExpr). - Input dispatch table: the
new … { Type: Provider }block insideUseInputDelegate. - Dispatcher provider: a provider that routes enum variants (here:
DispatchEval).
You can say the same idea in different ways:
- input type in -> matching provider out,
- one component, many input-specific handlers,
- the context picks provider by input shape.
Complete example
use cgp::extra::dispatch::MatchWithValueHandlersRef;
use cgp::extra::handler::{CanComputeRef, ComputerRef, ComputerRefComponent, UseInputDelegate};
use cgp::prelude::*;
pub struct Eval;
#[derive(Debug, PartialEq)]
pub struct Number(pub u64);
#[derive(Debug, PartialEq)]
pub struct Plus<Expr> {
pub left: Box<Expr>,
pub right: Box<Expr>,
}
#[derive(Debug, PartialEq, CgpData)]
pub enum MathExpr {
Number(Number),
Plus(Plus<MathExpr>),
}
#[cgp_impl(new EvalNumber)]
impl<Context, Code> ComputerRef<Code, Number> for Context {
type Output = u64;
fn compute_ref(_context: &Context, _code: PhantomData<Code>, Number(value): &Number) -> Self::Output {
*value
}
}
#[cgp_impl(new EvalPlus)]
impl<Context, Code, Expr> ComputerRef<Code, Plus<Expr>> for Context
where
Context: CanComputeRef<Code, Expr, Output = u64>,
{
type Output = u64;
fn compute_ref(context: &Context, code: PhantomData<Code>, Plus { left, right }: &Plus<Expr>) -> Self::Output {
context.compute_ref(code, left) + context.compute_ref(code, right)
}
}
pub struct Interpreter;
delegate_components! {
Interpreter {
ComputerRefComponent:
UseInputDelegate<
new EvalComponents {
MathExpr: DispatchEval,
Number: EvalNumber,
Plus<MathExpr>: EvalPlus,
}
>,
}
}
#[cgp_impl(new DispatchEval)]
impl<Code> ComputerRef<Code, MathExpr> for Interpreter {
type Output = u64;
fn compute_ref(context: &Interpreter, code: PhantomData<Code>, expr: &MathExpr) -> Self::Output {
<MatchWithValueHandlersRef as ComputerRef<Interpreter, Code, MathExpr>>::compute_ref(
context, code, expr,
)
}
}
fn main() {
let interpreter = Interpreter;
let code = PhantomData::<Eval>;
let expr = MathExpr::Plus(Plus {
left: MathExpr::Number(Number(2)).into(),
right: MathExpr::Number(Number(3)).into(),
});
assert_eq!(interpreter.compute_ref(code, &expr), 5);
}
What each part means
ComputerRef<Code, Input>is the compute capability for borrowed input.EvalNumberhandlesNumberinput.EvalPlushandlesPlusinput and recursively computes children. DispatchEvalhandles top-levelMathExprby delegating variant routing.UseInputDelegate<new EvalComponents { … }>is the input dispatch table onInterpreter.
Why DispatchEval is on Interpreter, but others use generic Context
You might notice:
EvalNumberandEvalPlusare implemented for genericContext.DispatchEvalis implemented for concreteInterpreter.
That split is intentional.
EvalNumberandEvalPlusare reusable node handlers. They only need capability bounds likeContext: CanComputeRef<…>, so they can work in many contexts.DispatchEvalis the top-level entry-point router for this specific interpreter wiring. It relies on the context’s configured input-dispatch table forMathExpr, so keeping it onInterpretermakes the assembly point explicit.
Practical pattern:
- make leaf/sub-expression providers generic and reusable,
- make the top-level enum dispatcher context-specific.
How the routing actually works
For interpreter.compute_ref(code, &expr) where expr: MathExpr:
- Input is
MathExpr, soUseInputDelegatepicksDispatchEval. DispatchEvalcallsMatchWithValueHandlersRef, which matches the enum variant.- If variant is
Number, it routes toEvalNumber. - If variant is
Plus, it routes toEvalPlus. EvalPlusrecursively callscompute_refon sub-expressions.
So there are two levels:
- level 1: input-type dispatch (
UseInputDelegate), - level 2: enum-variant dispatch (
MatchWithValueHandlersRef).
When to use MatchWithValueHandlersRef
Use MatchWithValueHandlersRef at the enum-dispatch boundary.
- It belongs in the provider that handles the enum input itself (here:
DispatchEvalforMathExpr). - It does not belong in variant-specific providers (
EvalNumber,EvalPlus), because those already handle concrete input types.
Rule of thumb:
- top-level enum provider ->
MatchWithValueHandlersRef, - variant handlers -> regular provider logic.
And by ownership style:
- use
MatchWithValueHandlerswith by-valueComputer, - use
MatchWithValueHandlersRefwith by-referenceComputerRef.
new EvalComponents explained
In this wiring:
UseInputDelegate<
new EvalComponents {
MathExpr: DispatchEval,
Number: EvalNumber,
Plus<MathExpr>: EvalPlus,
}
>
EvalComponentsis an arbitrary name for the generated input-dispatch table type.- It is not a reserved CGP keyword.
- You can rename it (for example
ExprHandlers,InputTable,EvalByInput).
What must match:
- each key type (
MathExpr,Number,Plus) must match the input type of the provider on the right.
Why the DispatchEval impl uses explicit trait syntax
This line uses fully-qualified syntax on purpose:
<MatchWithValueHandlersRef as ComputerRef<Interpreter, Code, MathExpr>>::compute_ref(...)
Reason: both ComputerRef and CanComputeRef define compute_ref, so explicit syntax avoids method-name ambiguity in current cgp usage.
What is doing the routing?
UseInputDelegateselects a provider by input type.DispatchEvalhandles the top-level enumMathExpr.MatchWithValueHandlersRefpicks the matching variant handler (EvalNumberorEvalPlus).
You can read it as: input type in -> matching provider out.
Why this pattern is useful
- each handler stays tiny and focused,
- adding a new variant means adding one new handler + one wiring line,
- no giant manual
matchfunction is needed in one place.
Practical rule: when behavior depends on input shape, start with UseInputDelegate and keep handlers small.