# Reflection Systems

### Reflection Systems

Reflection means a program can inspect information about types while the program is being compiled or running.

Some languages provide runtime reflection. A program can ask questions like:

```text
What fields does this struct contain?
What is the name of this type?
How many arguments does this function take?
```

In many languages, this information exists at runtime inside the final executable.

Zig takes a different approach.

Zig reflection is mostly compile-time reflection.

That means the compiler can inspect types while compiling the program. This is faster, safer, and simpler than large runtime reflection systems.

The center of Zig reflection is:

```zig
@typeInfo
```

This builtin lets you inspect the structure of a type.

#### Why Reflection Matters

Reflection sounds advanced, but it solves practical problems.

Suppose you want to:

```text
serialize structs to JSON
build command-line parsers
generate debug output
write generic containers
automatically validate data
build ORMs or config systems
```

Without reflection, you often repeat the same information manually.

Example:

```zig
const User = struct {
    id: u64,
    name: []const u8,
    active: bool,
};
```

Then somewhere else:

```zig
// Serialize "id"
// Serialize "name"
// Serialize "active"
```

And somewhere else again:

```zig
// Validate "id"
// Validate "name"
// Validate "active"
```

Reflection lets the compiler inspect the struct fields automatically.

#### Reflection in Zig Happens at Compile Time

This is one of the most important ideas in Zig.

When Zig reflects on a type, it usually does so during compilation, not while the program is running.

That means:

```text
less runtime overhead
smaller binaries
more optimization opportunities
fewer hidden runtime systems
```

Reflection in Zig is not a giant object system with runtime metadata everywhere.

Instead, Zig treats types as compile-time values.

#### The Simplest Reflection Example

Let us inspect a type.

```zig
const std = @import("std");

const User = struct {
    id: u64,
    name: []const u8,
    active: bool,
};

pub fn main() void {
    const info = @typeInfo(User);

    std.debug.print("{}\n", .{info});
}
```

```zig
@typeInfo(User)
```

asks the compiler:

```text
Tell me about the structure of User.
```

The result is a tagged union describing the type.

For a struct, the result contains information like:

```text
field names
field types
field count
layout information
```

#### Understanding `@typeInfo`

`@typeInfo` returns a value of type:

```zig
std.builtin.Type
```

This is a large tagged union describing every kind of Zig type.

Examples:

```text
Struct
Enum
Union
Array
Pointer
Optional
Fn
Int
Float
Vector
```

A type is itself data that the compiler can inspect.

This is a major Zig idea.

#### Inspecting Struct Fields

Now let us inspect the fields inside a struct.

```zig
const std = @import("std");

const User = struct {
    id: u64,
    name: []const u8,
    active: bool,
};

pub fn main() void {
    const info = @typeInfo(User);

    switch (info) {
        .@"struct" => |struct_info| {
            std.debug.print("field count = {}\n", .{
                struct_info.fields.len,
            });

            for (struct_info.fields) |field| {
                std.debug.print(
                    "field: {s}\n",
                    .{field.name},
                );
            }
        },
        else => {},
    }
}
```

Output:

```text
field count = 3
field: id
field: name
field: active
```

This is real reflection.

The compiler is exposing the structure of the type to your code.

#### Why `switch` Is Needed

Remember that `@typeInfo` returns a tagged union.

A type might be:

```text
a struct
an enum
a pointer
a function
an integer
```

So Zig requires you to handle the correct case.

```zig
switch (info) {
    .@"struct" => |struct_info| {
        // struct handling
    },
    else => {},
}
```

This is safer than assuming the type is always a struct.

#### Accessing Field Types

Each field contains more than just a name.

You can inspect the field type too.

```zig
const std = @import("std");

const User = struct {
    id: u64,
    name: []const u8,
    active: bool,
};

pub fn main() void {
    const info = @typeInfo(User);

    switch (info) {
        .@"struct" => |struct_info| {
            for (struct_info.fields) |field| {
                std.debug.print(
                    "{s}: {}\n",
                    .{
                        field.name,
                        field.type,
                    },
                );
            }
        },
        else => {},
    }
}
```

Possible output:

```text
id: u64
name: []const u8
active: bool
```

Now the program knows both:

```text
field names
field types
```

at compile time.

#### Compile-Time Loops with Reflection

Reflection becomes much more powerful when combined with `comptime`.

Example:

```zig
inline for (struct_info.fields) |field| {
    // compile-time loop
}
```

An `inline for` loop runs during compilation.

That means Zig can generate specialized code for every field.

This is how many generic systems work in Zig.

#### Building a Generic Printer

Let us build a tiny generic debug printer.

```zig
const std = @import("std");

fn printStruct(value: anytype) void {
    const T = @TypeOf(value);
    const info = @typeInfo(T);

    switch (info) {
        .@"struct" => |struct_info| {
            std.debug.print("{s} {{ ", .{
                @typeName(T),
            });

            inline for (struct_info.fields, 0..) |field, i| {
                if (i != 0) {
                    std.debug.print(", ", .{});
                }

                std.debug.print("{s}=", .{
                    field.name,
                });

                std.debug.print(
                    "{}",
                    .{@field(value, field.name)},
                );
            }

            std.debug.print(" }}\n", .{});
        },
        else => {
            std.debug.print("not a struct\n", .{});
        },
    }
}

const User = struct {
    id: u64,
    name: []const u8,
};

pub fn main() void {
    const user = User{
        .id = 42,
        .name = "Ada",
    };

    printStruct(user);
}
```

Output:

```text
User { id=42, name=Ada }
```

This function works for many struct types automatically.

#### Understanding `@field`

This line is important:

```zig
@field(value, field.name)
```

`@field` accesses a field dynamically using its name.

Normal access:

```zig
value.id
```

Reflection access:

```zig
@field(value, "id")
```

The second form is useful when field names come from reflection data.

#### Getting Type Names

Zig provides:

```zig
@typeName(T)
```

Example:

```zig
std.debug.print("{s}\n", .{
    @typeName(u32),
});
```

Output:

```text
u32
```

For structs:

```text
User
Point
Rectangle
```

This is useful for debugging, logs, serializers, and generated code.

#### Reflection with Enums

Reflection also works for enums.

```zig
const std = @import("std");

const Color = enum {
    red,
    green,
    blue,
};

pub fn main() void {
    const info = @typeInfo(Color);

    switch (info) {
        .@"enum" => |enum_info| {
            for (enum_info.fields) |field| {
                std.debug.print(
                    "{s}\n",
                    .{field.name},
                );
            }
        },
        else => {},
    }
}
```

Output:

```text
red
green
blue
```

The compiler exposes enum metadata too.

#### Reflection with Tagged Unions

Reflection becomes especially powerful with tagged unions.

```zig
const Shape = union(enum) {
    circle: f32,
    rectangle: struct {
        width: f32,
        height: f32,
    },
};
```

Reflection can inspect:

```text
union fields
tags
payload types
```

This allows advanced generic systems.

#### Reflection Is Type-Safe

This is one of Zig’s biggest advantages.

Many runtime reflection systems are string-heavy and weakly typed.

Example from dynamic systems:

```text
"field does not exist"
"method missing"
"wrong runtime type"
```

Zig reflection happens mostly during compilation.

If you use reflection incorrectly, the compiler usually catches it immediately.

Example:

```zig
@field(value, "missing_field")
```

This produces a compile error if the field does not exist.

#### Reflection Does Not Mean Dynamic Typing

This is important.

Zig reflection does not turn Zig into a dynamic language.

Types remain static.

The compiler simply exposes information about those types during compilation.

Reflection in Zig is a compile-time metaprogramming tool.

#### Reflection and Code Generation

Reflection often replaces external code generators.

In some ecosystems, developers write:

```text
schema generators
macro systems
template generators
codegen scripts
```

Zig often avoids this because compile-time reflection can generate specialized code directly.

Example ideas:

```text
automatic serializers
binary parsers
CLI parsers
network packet encoders
database row mappers
```

All from type information.

#### Reflection and Generic Programming

Reflection and generics work together naturally.

Suppose you want:

```text
a generic serializer
a generic validator
a generic equality checker
```

Reflection lets the compiler inspect arbitrary types.

`comptime` lets the compiler generate specialized code for those types.

This is one of the central design patterns in advanced Zig.

#### Reflection Is Not Free

Compile-time reflection has costs.

Heavy metaprogramming can increase:

```text
compile times
compiler memory usage
code complexity
```

Bad reflection systems can become difficult to understand.

Example:

```zig
// Many layers of comptime reflection.
// Hard to debug.
// Hard to read.
```

A good rule:

```text
Use reflection to remove duplication.
Do not use reflection to hide logic.
```

#### Prefer Simple Explicit Code First

Reflection is powerful, but explicit code is often better.

Good use:

```text
automatic serializers
generic containers
debug systems
testing utilities
```

Bad use:

```text
building a giant hidden framework
replacing simple functions with metaprogramming
making code harder to trace
```

Zig values clarity.

Reflection should simplify systems, not obscure them.

#### Reflection and the Zig Philosophy

Reflection in Zig reflects the overall language philosophy.

Zig wants:

```text
explicit behavior
compile-time safety
low runtime cost
simple generated code
```

Instead of large runtime object systems, Zig exposes compile-time type information directly to the programmer.

You can inspect types, generate code, and build abstractions without hiding what the program is doing.

#### A Mental Model

Think of reflection like this:

```text
Types are data.
The compiler can inspect that data.
Your compile-time code can react to that data.
```

This is the foundation of many advanced Zig systems.

When you combine:

```text
@typeInfo
comptime
inline for
@field
@TypeOf
@typeName
```

you gain the ability to write flexible generic systems while still producing efficient static machine code.

