Generics and Trait Boundaries
Helix uses static polymorphism where a caller already knows the concrete type, and dynamic polymorphism where runtime composition demands heterogeneity.
Generics at construction and stream boundaries
Application::event_loop<S> is generic over any Stream<Item = io::Result<TerminalEvent>> + Unpin. Jobs::spawn<F> accepts any suitable
future, and replace_or_push<T: Component> accepts a concrete component. These
APIs are easy to inline and do not require users to name a boxed type.
Trait objects in long-lived collections
The compositor cannot have a Vec<TextEditor | Picker | Prompt | ...> enum that
every extension must modify. It stores
Box<dyn Component>.
Jobs similarly erase distinct future and closure types into BoxFuture and
boxed FnOnce callbacks so one queue can hold them.
Associated behavior stays cohesive
Component groups event handling, rendering, cursor selection, sizing, and
identity. Dynamic dispatch is paid at a UI boundary where terminal rendering
dwarfs the call overhead and runtime layering is the actual requirement.
The rule is practical: use generics to accept many implementations; erase the type when values must coexist, cross a channel, or outlive the generic call.