426: Function-like Proc Macros
Tutorial Video
Text description (accessibility)
This video demonstrates the "426: Function-like Proc Macros" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. Function-like proc macros look like function calls (`my_macro!(...)`) but unlike `macro_rules!`, they receive their entire argument as a `TokenStream` and can generate arbitrary Rust code. Key difference from OCaml: 1. **Syntax freedom**: Rust function
Tutorial
The Problem
Function-like proc macros look like function calls (my_macro!(...)) but unlike macro_rules!, they receive their entire argument as a TokenStream and can generate arbitrary Rust code. This enables domain-specific languages embedded in Rust source: SQL queries (sql!("SELECT * FROM users WHERE id = $1") with type-checked parameters), HTML templates, CSS-in-Rust, regex patterns compiled at build time. The sql! macro can verify query syntax at compile time and generate typed query structs — impossible with macro_rules!.
Function-like proc macros power quote! itself, pest's grammar macros, type-checked SQL in sqlx, and compile-time regex compilation in regex_lite.
🎯 Learning Outcomes
#[proc_macro] registration and the TokenStream in/out signaturemacro_rules! (full tokenstream access, external crate)$ sigilsCode Example
#![allow(clippy::all)]
//! Function-like Proc Macros
//!
//! Macros invoked like function calls.
/// Example: what sql!("SELECT...") might do
pub fn parse_sql(query: &str) -> Vec<&str> {
query.split_whitespace().collect()
}
/// Example: what html!(<div>Hello</div>) might generate
pub struct HtmlElement {
pub tag: String,
pub content: String,
}
impl HtmlElement {
pub fn new(tag: &str, content: &str) -> Self {
HtmlElement {
tag: tag.to_string(),
content: content.to_string(),
}
}
pub fn render(&self) -> String {
format!("<{}>{}</{}>", self.tag, self.content, self.tag)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_sql() {
let parts = parse_sql("SELECT * FROM users");
assert_eq!(parts, vec!["SELECT", "*", "FROM", "users"]);
}
#[test]
fn test_html_render() {
let el = HtmlElement::new("div", "Hello");
assert_eq!(el.render(), "<div>Hello</div>");
}
#[test]
fn test_html_span() {
let el = HtmlElement::new("span", "World");
assert_eq!(el.render(), "<span>World</span>");
}
#[test]
fn test_sql_count() {
let parts = parse_sql("SELECT COUNT(*) FROM items WHERE active = true");
assert_eq!(parts.len(), 8);
}
#[test]
fn test_html_empty() {
let el = HtmlElement::new("br", "");
assert_eq!(el.render(), "<br></br>");
}
}Key Differences
[%name ...].OCaml Approach
OCaml's equivalent is the [%ppx_name expr] extension point syntax. [%sql "SELECT * FROM users"] with a custom PPX validates SQL at compile time. [%re "regex"] compiles regular expressions. OCaml's ppxlib extension points provide the same power as function-like proc macros, with the same compile-time validation benefits. The ocaml-re library uses this for compile-time regex compilation.
Full Source
#![allow(clippy::all)]
//! Function-like Proc Macros
//!
//! Macros invoked like function calls.
/// Example: what sql!("SELECT...") might do
pub fn parse_sql(query: &str) -> Vec<&str> {
query.split_whitespace().collect()
}
/// Example: what html!(<div>Hello</div>) might generate
pub struct HtmlElement {
pub tag: String,
pub content: String,
}
impl HtmlElement {
pub fn new(tag: &str, content: &str) -> Self {
HtmlElement {
tag: tag.to_string(),
content: content.to_string(),
}
}
pub fn render(&self) -> String {
format!("<{}>{}</{}>", self.tag, self.content, self.tag)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_sql() {
let parts = parse_sql("SELECT * FROM users");
assert_eq!(parts, vec!["SELECT", "*", "FROM", "users"]);
}
#[test]
fn test_html_render() {
let el = HtmlElement::new("div", "Hello");
assert_eq!(el.render(), "<div>Hello</div>");
}
#[test]
fn test_html_span() {
let el = HtmlElement::new("span", "World");
assert_eq!(el.render(), "<span>World</span>");
}
#[test]
fn test_sql_count() {
let parts = parse_sql("SELECT COUNT(*) FROM items WHERE active = true");
assert_eq!(parts.len(), 8);
}
#[test]
fn test_html_empty() {
let el = HtmlElement::new("br", "");
assert_eq!(el.render(), "<br></br>");
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_sql() {
let parts = parse_sql("SELECT * FROM users");
assert_eq!(parts, vec!["SELECT", "*", "FROM", "users"]);
}
#[test]
fn test_html_render() {
let el = HtmlElement::new("div", "Hello");
assert_eq!(el.render(), "<div>Hello</div>");
}
#[test]
fn test_html_span() {
let el = HtmlElement::new("span", "World");
assert_eq!(el.render(), "<span>World</span>");
}
#[test]
fn test_sql_count() {
let parts = parse_sql("SELECT COUNT(*) FROM items WHERE active = true");
assert_eq!(parts.len(), 8);
}
#[test]
fn test_html_empty() {
let el = HtmlElement::new("br", "");
assert_eq!(el.render(), "<br></br>");
}
}
Deep Comparison
OCaml vs Rust: proc macro function like
See example.rs and example.ml for side-by-side implementations.
Key Points
Exercises
json!({ "key": "value", "num": 42 }) as a function-like macro (or simulate with macro_rules!) that validates the JSON structure at compile time and generates a serde_json::Value constructor.meters!(5.3) that expands to Meters(5.3f64) and kilometers!(1.2) to Kilometers(1.2f64). Use function-like proc macros (or macro_rules!) to make unit conversion values ergonomic.uuid!("550e8400-e29b-41d4-a716-446655440000") that validates the UUID format at compile time and generates a [u8; 16] constant. Compile-error on malformed input.