434: Domain-Specific Languages with Macros
Tutorial Video
Text description (accessibility)
This video demonstrates the "434: Domain-Specific Languages with Macros" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Some problem domains have natural syntaxes that are more readable and less error-prone than general-purpose code. Key difference from OCaml: 1. **Token matching**: Rust DSL macros parse token streams with pattern matching; OCaml uses formal grammars (Menhir) or combinator parsers.
Tutorial
The Problem
Some problem domains have natural syntaxes that are more readable and less error-prone than general-purpose code. SQL queries, HTML templates, configuration languages, and test specifications benefit from DSL syntax. macro_rules! can implement simple DSLs within Rust source: query!(SELECT name, age FROM users WHERE "active = true") is safer and more readable than string concatenation. The macro validates structure at compile time (correct SQL keywords, table identifiers) and generates the underlying representation.
DSL macros appear in sqlx's typed queries, html! in Yew, css! in Stylist, and testing DSLs like rstest's table-driven tests.
🎯 Learning Outcomes
macro_rules! can parse domain-specific syntax using identifier and expression fragmentsSELECT $cols FROM $table WHERE $cond pattern matching techniquestringify!($ident) converts macro identifiers to strings for query buildingCode Example
#![allow(clippy::all)]
//! Domain Specific Languages with Macros
//!
//! Creating mini-languages in macros.
/// SQL-like query builder.
#[macro_export]
macro_rules! query {
(SELECT $($col:ident),+ FROM $table:ident) => {{
let cols: Vec<&str> = vec![$(stringify!($col)),+];
format!("SELECT {} FROM {}", cols.join(", "), stringify!($table))
}};
(SELECT $($col:ident),+ FROM $table:ident WHERE $cond:expr) => {{
let cols: Vec<&str> = vec![$(stringify!($col)),+];
format!("SELECT {} FROM {} WHERE {}", cols.join(", "), stringify!($table), $cond)
}};
}
/// HTML-like builder.
#[macro_export]
macro_rules! html {
($tag:ident { $content:expr }) => {
format!("<{}>{}</{}>", stringify!($tag), $content, stringify!($tag))
};
}
#[cfg(test)]
mod tests {
#[test]
fn test_query_simple() {
let q = query!(SELECT name, age FROM users);
assert!(q.contains("SELECT"));
assert!(q.contains("name"));
assert!(q.contains("users"));
}
#[test]
fn test_query_where() {
let q = query!(SELECT id FROM items WHERE "active = true");
assert!(q.contains("WHERE"));
assert!(q.contains("active"));
}
#[test]
fn test_html_div() {
let h = html!(div { "Hello" });
assert_eq!(h, "<div>Hello</div>");
}
#[test]
fn test_html_span() {
let h = html!(span { "World" });
assert_eq!(h, "<span>World</span>");
}
#[test]
fn test_query_multiple_cols() {
let q = query!(SELECT a, b, c FROM table);
assert!(q.contains("a, b, c"));
}
}Key Differences
angstrom, sedlex) handles runtime DSL parsing elegantly; Rust uses nom, pest, or chumsky.macro_rules! capability; both fall back to proc macros / PPX for complex cases.OCaml Approach
OCaml implements DSLs through its parsing infrastructure. The menhir parser generator creates parsers for arbitrary grammars. PPX extensions enable [%sql "SELECT ..."] syntax validated by the PPX. angstrom provides parser combinators for runtime DSL parsing. OCaml's quotations (in camlp5) support <:expr< ... >> syntax for embedding OCaml-like expressions — similar to Rust's quote! but for OCaml ASTs.
Full Source
#![allow(clippy::all)]
//! Domain Specific Languages with Macros
//!
//! Creating mini-languages in macros.
/// SQL-like query builder.
#[macro_export]
macro_rules! query {
(SELECT $($col:ident),+ FROM $table:ident) => {{
let cols: Vec<&str> = vec![$(stringify!($col)),+];
format!("SELECT {} FROM {}", cols.join(", "), stringify!($table))
}};
(SELECT $($col:ident),+ FROM $table:ident WHERE $cond:expr) => {{
let cols: Vec<&str> = vec![$(stringify!($col)),+];
format!("SELECT {} FROM {} WHERE {}", cols.join(", "), stringify!($table), $cond)
}};
}
/// HTML-like builder.
#[macro_export]
macro_rules! html {
($tag:ident { $content:expr }) => {
format!("<{}>{}</{}>", stringify!($tag), $content, stringify!($tag))
};
}
#[cfg(test)]
mod tests {
#[test]
fn test_query_simple() {
let q = query!(SELECT name, age FROM users);
assert!(q.contains("SELECT"));
assert!(q.contains("name"));
assert!(q.contains("users"));
}
#[test]
fn test_query_where() {
let q = query!(SELECT id FROM items WHERE "active = true");
assert!(q.contains("WHERE"));
assert!(q.contains("active"));
}
#[test]
fn test_html_div() {
let h = html!(div { "Hello" });
assert_eq!(h, "<div>Hello</div>");
}
#[test]
fn test_html_span() {
let h = html!(span { "World" });
assert_eq!(h, "<span>World</span>");
}
#[test]
fn test_query_multiple_cols() {
let q = query!(SELECT a, b, c FROM table);
assert!(q.contains("a, b, c"));
}
}#[cfg(test)]
mod tests {
#[test]
fn test_query_simple() {
let q = query!(SELECT name, age FROM users);
assert!(q.contains("SELECT"));
assert!(q.contains("name"));
assert!(q.contains("users"));
}
#[test]
fn test_query_where() {
let q = query!(SELECT id FROM items WHERE "active = true");
assert!(q.contains("WHERE"));
assert!(q.contains("active"));
}
#[test]
fn test_html_div() {
let h = html!(div { "Hello" });
assert_eq!(h, "<div>Hello</div>");
}
#[test]
fn test_html_span() {
let h = html!(span { "World" });
assert_eq!(h, "<span>World</span>");
}
#[test]
fn test_query_multiple_cols() {
let q = query!(SELECT a, b, c FROM table);
assert!(q.contains("a, b, c"));
}
}
Deep Comparison
OCaml vs Rust: macro dsl
See example.rs and example.ml for side-by-side implementations.
Key Points
Exercises
route!(GET "/users/{id}" => handler_fn) that generates a Route { method: Method::Get, path: "/users/{id}", handler: handler_fn } struct. Parse GET/POST/PUT/DELETE as ident fragments.test_cases!( add | a | b | result | 1 | 2 | 3, 4 | 5 | 9 ) that generates individual test functions for each row, asserting add(a, b) == result.config!{ timeout: 30, host: "localhost", port: 8080 } producing a Config { timeout: 30, host: "localhost".to_string(), port: 8080 } value, handling integer and string literals with appropriate type coercion.