BasicsImpCodeStory_Metaprogramming.html
copyright © James Fawcett
Revised: 05/11/2026
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