Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.