Internals: Macros
OSUI heavily relies on procedural macros to provide its ergonomic, declarative syntax. The osui-macros crate contains the logic for the #[component] attribute macro and the rsx! function macro. Understanding how these macros work under the hood gives you a deeper insight into OSUI's architecture and capabilities.
Introduction to Procedural Macros
Procedural macros are functions that operate on the Rust syntax tree (Abstract Syntax Tree or AST) during compilation. They receive TokenStreams as input and produce TokenStreams as output, effectively transforming your code. OSUI's macros leverage the following crates:
syn: A parser for Rust's syntax tree. It allows macros to parse inputTokenStreams into structured Rust AST types (likeItemFn,Expr,Path, etc.).quote: A quasiquoting library that makes it easy to generate Rust code (asTokenStreams) from AST fragments.proc-macro2: Provides types likeTokenStreamandIdentthat are compatible withsynandquote, enabling ergonomic manipulation of tokens.
#[component] Attribute Macro (macros/src/lib.rs)
The #[component] macro transforms a Rust function into an OSUI component struct.
Input
It takes an ItemFn (the parsed function definition) as input.
// Original function in user's code
#[component]
pub fn MyComponent(cx: &Arc<Context>, prop1: &String, prop2: &i32) -> View {
// ... function body ...
}
Core Logic
- Parse Function Signature:
- It extracts the function's name (
MyComponent). - It validates the first argument
cx: &Arc<Context>. - It iterates through the remaining arguments (
prop1: &String,prop2: &i32), which become the component's props.
- It extracts the function's name (
- Generate Component Struct: For each prop parameter, it determines the owned type (e.g.,
&StringbecomesString,&i32becomesi32). It then usesquote!to generate a new struct:pub struct MyComponent {
pub prop1: String, // Owned types
pub prop2: i32,
} - Implement
ComponentImpl: It then generates animpl ComponentImpl for MyComponentblock. Thecallmethod of this trait:- Takes
&selfandcx: &Arc<Context>. - Internally calls a generated
Self::componentmethod (which is your original function's body). - Passes
cxand references (&self.prop1,&self.prop2) to the stored props from the generated struct.
impl MyComponent {
// This is your original function, renamed and wrapped
pub fn component(cx: &Arc<Context>, prop1: &str, prop2: &i32) -> View { /* ... body ... */ }
}
impl ComponentImpl for MyComponent {
fn call(&self, cx: &Arc<Context>) -> View {
Self::component(cx, &self.prop1, &self.prop2) // Pass references to owned props
}
} - Takes
Output
The macro replaces the original function with the generated struct, its component method, and the ComponentImpl implementation. This transformation makes MyComponent a valid OSUI component that can be instantiated with props in rsx!.
rsx! Function Macro (macros/src/parse.rs & macros/src/emit.rs)
The rsx! macro is more complex, involving a two-step process: parsing the custom syntax and then emitting standard Rust code.
1. Parsing (macros/src/parse.rs)
The parse module defines an AST (Abstract Syntax Tree) for the rsx! syntax.
RsxRoot: The top-level container, holding a vector ofRsxNodes.RsxNode: An enum representing different types of nodes in thersx!tree:Text(LitStr): For"Hello"literals.Expr(Expr): For@{some_expression}blocks.Component { path: Path, props: Vec<RsxProp>, children: Vec<RsxNode> }: ForMyComponent { prop: val, ... }.Mount(Ident): For!my_mount_hook.If { deps: Vec<Dep>, cond: Expr, children: Vec<RsxNode> }: For@if condition { ... }.For { deps: Vec<Dep>, pat: Pat, expr: Expr, children: Vec<RsxNode> }: For@for item in items { ... }.
RsxProp: Represents aname: valuepair for component props.Dep: Represents a dependency for dynamic blocks (%my_state as my_alias).
The parse::RsxRoot::parse method uses syn's ParseStream to tokenize the rsx! input and build this AST. It intelligently differentiates between text, expressions, component names, and control flow keywords (@if, @for, !).
2. Emitting (macros/src/emit.rs)
The emit module takes the parsed RsxRoot AST and converts it into a TokenStream of standard Rust code that constructs osui::frontend::Rsx objects.
emit_rsx(root: RsxRoot): The entry point, which initializes anosui::frontend::Rsxobject and then iterates through theroot.nodes.emit_node_scope(node: &RsxNode): For eachRsxNode, it generates code that calls the appropriateRsxbuilder method:RsxNode::Text: Emitsr.static_scope(move |scope| { scope.view(...) });which draws text.RsxNode::Expr: Emitsr.child(expression);.RsxNode::Component: Emitsr.static_scope(move |scope| { scope.child(ComponentName { props: ... }, None); });. If children are present, they are recursively emitted into a nestedRsxobject and passed as thechildrenprop.RsxNode::Mount: Emitsmount_hook_instance.mount();.RsxNode::If: Emitsr.dynamic_scope(move |scope| { if condition { ... } else { ... } }, dependencies);. Thedependenciesare converted toVec<Arc<dyn HookDependency>>.RsxNode::For: Similar toIf, emitsr.dynamic_scope(move |scope| { for pattern in expr { ... } }, dependencies);.
Output
The rsx! macro produces a TokenStream that looks something like this (simplified):
// For: rsx! { "Hello" MyComponent { prop: value } }
{
let mut r = osui::frontend::Rsx::new();
r.static_scope(move |scope| {
scope.view(std::sync::Arc::new(move |ctx| {
ctx.draw_text(osui::render::Point { x: 0, y: 0 }, &format!("Hello"))
}));
});
r.static_scope(move |scope| {
scope.child(
MyComponent { prop: value.to_string() }, // Note: Prop value converted to owned type
None,
);
});
r
}
This generated code, when compiled, constructs the osui::frontend::Rsx object that OSUI's runtime can then interpret to build the component tree and render the UI.
Summary
The osui-macros crate plays a pivotal role in shaping OSUI's developer experience. #[component] streamlines component definition, while rsx! provides a powerful, declarative way to compose UIs by transforming custom syntax into efficient runtime calls. These macros are complex but essential for creating a modern, React-like development flow in a TUI environment.
Next: Learn how you can contribute to the OSUI project in Contributing.