Code Story

Chapter #12 – Metaprogramming

macros, reflection, code generation, attributes, compile-time computation

12.0  Prologue

Metaprogramming is code that operates on or generates other code. It reduces boilerplate, enforces conventions, and enables domain-specific abstractions that would otherwise require language extensions. The mechanisms range from simple text substitution (C preprocessor) through structured syntax manipulation (Rust proc macros) to full runtime introspection (Python, C# reflection).

12.1  Macros

A macro transforms syntax before or during compilation. It can generate repetitive code, implement domain-specific syntax, or work around limitations of the type system. // Rust: declarative macro (pattern-based syntax transformation) macro_rules! vec_of_strings { ($($x:expr),*) => { vec![$( $x.to_string() ),*] }; } let v = vec_of_strings!["hello", "world"]; // Rust: println! and format! are built-in declarative macros println!("{} + {} = {}", a, b, a + b); // C: preprocessor macro (textual substitution, no type safety) #define MAX(a, b) ((a) > (b) ? (a) : (b)) int m = MAX(x, y); // expands inline; double-evaluation risk // C++: constexpr function preferred over macros for typed computation Rust macros operate on the token tree, not raw text, so they cannot produce syntactically invalid output. They are hygienic: names introduced inside a macro do not leak into the caller’s scope.

12.2  Reflection

Reflection is the ability of a program to inspect and modify its own structure at runtime: discovering types, fields, methods, and attributes without knowing them at compile time. // C#: System.Reflection Type t = typeof(Article); foreach (var prop in t.GetProperties()) Console.WriteLine(prop.Name); object obj = Activator.CreateInstance(t); // Python: inspect module + built-ins import inspect for name, val in inspect.getmembers(obj): print(name, val) type(obj).__name__ # class name at runtime hasattr(obj, 'summarize') # probe for attribute // Rust: NO runtime reflection // Types are erased after compilation; use proc macros or serde for serialization Rust deliberately omits runtime reflection to preserve zero-cost abstractions and allow dead-code elimination. The std::any::Any trait provides limited runtime type identification, and the serde ecosystem uses proc macros to generate serialization code at compile time.

12.3  Code Generation

Code generation produces source code or intermediate representations automatically from a higher-level description. Rust procedural macros receive a token stream as input and return a new token stream. They run at compile time as plugins loaded by the compiler. Common uses: deriving trait implementations, generating boilerplate, embedding DSLs. // Rust: proc macro (derive macro generates Debug impl) #[derive(Debug, Clone, PartialEq)] struct Point { x: f64, y: f64 } // compiler generates: impl Debug for Point { fn fmt(...) { ... } } // C# Source Generators (Roslyn API; .NET 5+) // Analyzer reads INamedTypeSymbol, emits additional C# source files at compile time // Python: code templates written to .py files, then imported import importlib.util spec = importlib.util.spec_from_file_location("gen", "generated.py") mod = importlib.util.module_from_spec(spec)

12.4  Attributes and Decorators

Attributes (Rust, C#) and decorators (Python) attach metadata or behavior to declarations without modifying the declaration itself. // Rust attributes #[test] // marks function as a test #[allow(dead_code)] // suppresses a lint warning #[cfg(target_os = "linux")] // conditional compilation #[derive(Serialize, Deserialize)] // proc macro: generates serde impl // C# attributes (bracket syntax; read via reflection) [Obsolete("Use NewMethod instead")] [HttpGet("/api/users/{id}")] public ActionResult GetUser(int id) { ... } // Python decorators (syntactic sugar for higher-order function application) @staticmethod @property @functools.lru_cache(maxsize=128) def expensive_computation(self): ... Python decorators execute at class or function definition time and can replace the decorated object with any callable. Rust attributes are processed at compile time by the compiler or by proc macros. C# attributes are inert metadata objects readable at runtime via reflection.

12.5  Compile-Time Computation

Moving computation to compile time reduces runtime cost and enables values computed from program constants to appear in type-level positions (array sizes, template arguments). // Rust: const fn evaluated at compile time const fn factorial(n: u64) -> u64 { if n == 0 { 1 } else { n * factorial(n - 1) } } const FACT_10: u64 = factorial(10); // computed at compile time // C++: constexpr function (C++11) and consteval (C++20, mandatory compile-time) constexpr int factorial(int n) { return n <= 1 ? 1 : n * factorial(n-1); } constexpr int f10 = factorial(10); consteval int must_be_const(int n) { return n * 2; } // compile-time only // C#: const (literals only); static readonly for runtime-initialized fields const int MaxItems = 1024; // must be a compile-time literal // Python: no compile-time evaluation; constants are runtime values Rust’s const fn features grow with each edition; as of Rust 1.65+ many standard-library functions are const fn. The goal is to enable more complex compile-time algorithms without a separate DSL. C++ template metaprogramming (TMP) pioneered compile-time computation with Turing-complete template instantiation, but the technique is famously verbose and produces cryptic errors. Concepts and constexpr/consteval provide cleaner alternatives in modern C++.

12.6  Epilogue

Metaprogramming amplifies productivity when used judiciously. The risks are real: macros that are hard to debug, reflection that breaks refactoring tools, and code generators that produce unmaintainable output. The discipline is to use the most constrained mechanism that solves the problem - a const fn before a proc macro, a proc macro before runtime reflection. The final chapter collects ten project ideas that put these imperative features into practice.

12.7  References

Rust Macros - The Book
Rust Procedural Macros - Reference
C++ constexpr - cppreference
C# Source Generators - Microsoft
Python Decorators - Glossary