Generic, Parametric, and Metaprogramming

Generics allow algorithms and data structures to work with multiple types while retaining useful type constraints.

9.1 Generic programming

Generics allow algorithms and data structures to work with multiple types while retaining useful type constraints.

Checked with tsc --strict: exact-type assertions that firstNumber is number | undefined and firstName is string | undefined compile without errors.

The function works for different element types. The return type correctly allows undefined for an empty array.

function first<T>(items: T[]): T | undefined {
  return items[0];
}

const firstNumber = first([10, 20, 30]); // number | undefined
const firstName = first(["Ada", "Lin"]); // string | undefined

9.2 Parametric polymorphism

A generic function behaves uniformly across a family of types, subject to the operations available through its type parameters. This differs from subtype polymorphism, where a value is used through a common supertype/interface.

9.3 Traits and generic constraints

_Not compiled — no Rust toolchain on the build machine._ T must support ordering and copying. The function returns None for an empty slice. Rust trait bounds express the operations required by the algorithm.

fn largest<T: PartialOrd + Copy>(items: &[T]) -> Option<T> {
    let mut iter = items.iter().copied();
    let mut largest = iter.next()?;

    for item in iter {
        if item > largest {
            largest = item;
        }
    }
    Some(largest)
}

9.4 Macros and code generation

Macros generate or transform code before or during compilation, depending on the language. They can reduce repetition but may complicate error messages and debugging. Prefer ordinary functions unless a macro is needed for syntax-level transformation, compile-time generation, or a language-specific capability.

9.5 Reflection and runtime metadata

Reflection inspects types, members, annotations, or objects at runtime. It supports serializers, dependency injection, test frameworks, and plugins, but can make dependencies implicit and may increase attack surface if arbitrary types or methods are dynamically selected from untrusted input.

9.6 Compile-time versus runtime metaprogramming

Compile-time: macros, templates, generated source, annotation processing, compiler plugins.

Runtime: reflection, dynamic method dispatch, expression evaluation, plugin loading.

Build-time code generation: generate source or assets as part of the build pipeline.

Treat generated code as production code: review it, test it, scan its dependencies, and understand its provenance.

9.7 Template and code-generation safety

Never merge untrusted text directly into executable code, templates, SQL, or shell commands. Separate data from code and use safe APIs. Code generation should use strict schemas, deterministic inputs where possible, and reviewable output.

Exercise

Write a generic function for grouping values by a key. Add constraints only where needed and compare generic code with duplicated implementations.

Quiz

? What does `first<T>(items: T[]): T | undefined` return for `first([10, 20, 30])`, as a type?
+ `number | undefined` :: T is inferred as number; tsc --strict accepted an exact-type assertion of this.
- `number` :: The return type keeps `undefined` for empty arrays.
- `any` :: Generics keep the element type.
- `T` :: T is instantiated as `number` at the call.

? What distinguishes parametric polymorphism from subtype polymorphism?
+ Parametric code works uniformly for any type argument; subtype code works through a common supertype or interface. :: That is the distinction in 9.2.
- They are the same thing. :: They are different mechanisms.
- Parametric polymorphism needs runtime reflection. :: It does not.
- Subtype polymorphism needs macros. :: It does not.

? Which are risks of metaprogramming? (select all that apply)
+ Harder error messages and debugging (macros). :: Generated code is one step removed from the source.
+ Implicit dependencies and larger attack surface (reflection on untrusted input). :: Selecting arbitrary types or methods from input is dangerous.
+ Unreviewed generated code. :: Generated code is production code.
- It always makes programs slower. :: Compile-time
…