Skip to content

Macros

No direct TypeScript equivalent — but decorators come close

Section titled “No direct TypeScript equivalent — but decorators come close”

Rust macros have no single TypeScript equivalent. The closest analogies are:

  • TypeScript decorators (@Injectable(), @Column()) — like #[derive(...)], they annotate code and trigger generated behaviour.
  • Template literal types / type-level metaprogramming — like macro_rules!, they operate on syntax before runtime.
  • Babel / esbuild plugins — transformations applied at build time.

The key difference: Rust macros are hygienic and operate on the AST. They are part of the language, not a bolted-on annotation system, and they run at compile time with full access to the compiler’s token tree.

KindSyntaxWhat it does
Declarative (macro_rules!)macro_rules! my_macro { ... }Pattern-matches on token trees and generates code
Procedural (#[derive], #[attribute], function-like)#[derive(Debug, Clone)]Receives an AST node and emits new AST nodes

This lesson focuses on macro_rules! (the simpler kind) and derive macros (which you have already used without thinking about them).

macro_rules! works like a match expression for syntax. Each arm defines a pattern of tokens to match and a template of tokens to emit.

TypeScript
// TypeScript — no direct equivalent.
// Closest: a generic utility function or a build-time code generator.
function repeat<T>(value: T, times: number): T[] {
return Array.from({ length: times }, () => value);
}
console.log(repeat("hi", 3)); // ["hi", "hi", "hi"]
// TypeScript decorators (stage 3) annotate classes at runtime:
// @Injectable() class MyService { ... }
Rust
// macro_rules! — match on syntax, emit code
macro_rules! say_hello {
// arm 1: no arguments
() => {
println!("Hello!");
};
// arm 2: one expression argument
($name:expr) => {
println!("Hello, {}!", $name);
};
}
// #[derive] macro — auto-implement traits at compile time
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
fn main() {
say_hello!();
say_hello!("Rustacean");
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1.clone(); // Clone derived
println!("{:?}", p1); // Debug derived
println!("p1 == p2: {}", p1 == p2); // PartialEq derived
}

vec![1, 2, 3] is itself a macro_rules! macro from the standard library. It expands to code that creates a Vec, calls push for each element, and returns the vector. Without it you would write:

let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);

The println! and format! macros are also declarative macros — they parse the format string at compile time and generate type-safe formatting code.

Every time you write #[derive(Debug, Clone, Serialize, Deserialize)], you are invoking a procedural macro. These are more powerful than macro_rules! because they receive the full AST of your struct or enum and emit arbitrary new code.

DeriveWhat it generates
Debugfmt::Debug impl — enables {:?} printing
Cloneclone() method
PartialEq / Eq== and != operators
PartialOrd / Ord<, >, <=, >=
serde::Serialize / DeserializeJSON (and other format) serialisation
thiserror::ErrorDisplay and From impls for error enums
macro_rules! say_hello {
() => {
println!("Hello!");
};
($name:expr) => {
println!("Hello, {}!", $name);
};
}
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
fn main() {
say_hello!();
say_hello!("Rustacean");
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1.clone();
println!("{:?}", p1);
println!("p1 == p2: {}", p1 == p2);
}
What does `#[derive(Debug)]` do?
Which TypeScript feature is most similar to Rust's `#[derive(...)]`?
What is 'macro hygiene' in Rust?