rust 122 lines · 3 tabs

Modeling and Evaluating a Decision Tree with Recursive Rust Enums

Shared by codesnips Jul 2026
3 tabs
#[derive(Debug, Clone)]
pub enum Outcome {
    Approve,
    Review,
    Reject(String),
}

#[derive(Debug, Clone)]
pub enum Predicate {
    GreaterThan { feature: String, value: f64 },
    LessThan { feature: String, value: f64 },
    Equals { feature: String, value: f64 },
}

#[derive(Debug, Clone)]
pub enum Node {
    Leaf(Outcome),
    Branch {
        test: Predicate,
        yes: Box<Node>,
        no: Box<Node>,
    },
}

impl Node {
    pub fn leaf(outcome: Outcome) -> Node {
        Node::Leaf(outcome)
    }

    pub fn branch(test: Predicate, yes: Node, no: Node) -> Node {
        Node::Branch {
            test,
            yes: Box::new(yes),
            no: Box::new(no),
        }
    }
}
3 files · rust Explain with highlit

A decision tree is a naturally recursive structure: each internal node asks a question and branches into subtrees, while leaves hold a final answer. In Rust that recursion has to be made explicit because a value must have a known, finite size at compile time. This snippet models the tree with recursive enums and shows how to evaluate it against a set of feature inputs.

The Tree types tab defines the core data model. A Node is either a Leaf carrying an Outcome, or a Branch holding a Predicate plus yes and no subtrees. Because a Node can contain another Node, the enum would be infinitely sized, so each child is wrapped in Box<Node> — a heap pointer of known size that breaks the cycle. The Predicate enum captures the comparison to run against a named feature, and small helper constructors like Node::leaf and Node::branch keep call sites readable while hiding the boxing.

The Evaluator tab is where the recursion actually runs. Features is a thin newtype over a HashMap so lookups can return a typed Option<f64>. Node::evaluate walks the tree: on a Leaf it returns a reference to the stored Outcome, and on a Branch it evaluates the Predicate, then recurses into yes or no. Using &self and returning &Outcome means evaluation borrows the tree rather than cloning it, which keeps traversal cheap. Predicate::test isolates the comparison logic and treats a missing feature as false so a partially specified input never panics.

The Demo tab assembles a concrete tree with the constructors and evaluates two feature sets. Pattern matching over Outcome at the end shows how the caller consumes the result. The trade-off of this design is clarity over raw speed: boxing adds an allocation and a pointer hop per node, which is fine for configuration-sized trees but not for millions of nodes in a hot loop. For that, a flattened array-of-nodes with index children would avoid the pointer chasing. For modeling rules, scoring flows, or feature gates, the recursive-enum form reads closest to the problem.


Related snips

Share this code

Here's the card — post it anywhere.

Modeling and Evaluating a Decision Tree with Recursive Rust Enums — share card
Link copied