# Reflection with `@typeInfo`

### Reflection with `@typeInfo`

Reflection means inspecting a type as data.

In ordinary code, you use a type:

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

You create values of that type:

```zig
const user = User{
    .id = 1,
    .name = "Ada",
};
```

With reflection, you ask questions about the type itself:

What kind of type is it?

Is it a struct?

What fields does it have?

What are the field names?

What are the field types?

In Zig, the main tool for this is `@typeInfo`.

#### The Basic Idea

`@typeInfo(T)` returns compile-time information about a type.

```zig
const info = @typeInfo(User);
```

The value `info` describes the type `User`.

Because types are compile-time values, this inspection happens at compile time.

You usually use `@typeInfo` inside generic functions:

```zig
fn inspect(comptime T: type) void {
    const info = @typeInfo(T);
    _ = info;
}
```

The parameter `T` is a type known during compilation.

#### Type Information Is Tagged

The result of `@typeInfo` is a tagged union.

That means it can describe many different kinds of types, but only one kind at a time.

For example, a type may be:

```text
int
float
bool
pointer
array
struct
enum
union
optional
error union
function
```

So you usually use `switch`:

```zig
fn describe(comptime T: type) []const u8 {
    return switch (@typeInfo(T)) {
        .int => "integer",
        .float => "float",
        .bool => "bool",
        .pointer => "pointer",
        .array => "array",
        .@"struct" => "struct",
        .@"enum" => "enum",
        .@"union" => "union",
        else => "other",
    };
}
```

Notice the quoted names:

```zig
.@"struct"
.@"enum"
.@"union"
```

These are needed because `struct`, `enum`, and `union` are Zig keywords.

#### Inspecting a Struct

Let us inspect this struct:

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

Now write a function that logs its field names at compile time:

```zig
fn logStructFields(comptime T: type) void {
    const info = @typeInfo(T);

    switch (info) {
        .@"struct" => |struct_info| {
            inline for (struct_info.fields) |field| {
                @compileLog(field.name);
            }
        },
        else => @compileError("expected a struct type"),
    }
}

comptime {
    logStructFields(User);
}
```

The compiler prints field names while compiling:

```text
id
name
active
```

No runtime reflection is happening here. The compiler is reading the structure of the type before the program runs.

#### Field Names and Field Types

Each struct field has metadata.

You can inspect the field name and field type:

```zig
fn logStructFieldTypes(comptime T: type) void {
    const info = @typeInfo(T);

    switch (info) {
        .@"struct" => |struct_info| {
            inline for (struct_info.fields) |field| {
                @compileLog(field.name);
                @compileLog(@typeName(field.type));
            }
        },
        else => @compileError("expected a struct type"),
    }
}
```

For the `User` type, this gives information like:

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

This is useful when writing generic tools.

For example, a serializer can inspect fields and generate code that writes each field.

A validator can inspect fields and generate checks.

A command-line parser can inspect config fields and generate flags.

#### Getting a Field Value with `@field`

Reflection becomes more useful when combined with `@field`.

`@field(value, name)` accesses a field by name.

Example:

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

fn printStruct(comptime T: type, value: T) void {
    const info = @typeInfo(T);

    switch (info) {
        .@"struct" => |struct_info| {
            inline for (struct_info.fields) |field| {
                const field_value = @field(value, field.name);
                std.debug.print("{s} = {}\n", .{ field.name, field_value });
            }
        },
        else => @compileError("expected a struct type"),
    }
}
```

Usage:

```zig
const User = struct {
    id: u64,
    active: bool,
};

pub fn main() void {
    const user = User{
        .id = 1,
        .active = true,
    };

    printStruct(User, user);
}
```

Output:

```text
id = 1
active = true
```

The loop over fields is compile-time. The actual printing happens at runtime.

That distinction matters.

The compiler generates code similar to:

```zig
std.debug.print("{s} = {}\n", .{ "id", user.id });
std.debug.print("{s} = {}\n", .{ "active", user.active });
```

#### Compile-Time Reflection, Runtime Work

Reflection with `@typeInfo` is usually compile-time reflection.

The compiler uses reflection to generate normal runtime code.

This is different from languages where reflection happens dynamically while the program runs.

In Zig, you normally do not carry a runtime type object around. You inspect the type at compile time and generate direct code.

That gives you two benefits:

The generated runtime code can be fast.

Bad types can be rejected during compilation.

#### Rejecting Unsupported Field Types

Suppose you only want to print fields of simple types.

```zig
fn isPrintable(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .bool, .int, .float => true,
        .pointer => |ptr| ptr.size == .slice and ptr.child == u8,
        else => false,
    };
}
```

Now use it in a struct printer:

```zig
fn requirePrintableStruct(comptime T: type) void {
    const info = @typeInfo(T);

    switch (info) {
        .@"struct" => |struct_info| {
            inline for (struct_info.fields) |field| {
                if (!isPrintable(field.type)) {
                    @compileError("field is not printable: " ++ field.name);
                }
            }
        },
        else => @compileError("expected a struct type"),
    }
}
```

This catches unsupported types at compile time.

If a struct has a field your function cannot handle, the program fails to compile with a clear error.

#### Inspecting Arrays

`@typeInfo` can also inspect arrays.

```zig
fn describeArray(comptime T: type) void {
    switch (@typeInfo(T)) {
        .array => |array_info| {
            @compileLog(array_info.len);
            @compileLog(@typeName(array_info.child));
        },
        else => @compileError("expected an array type"),
    }
}
```

Usage:

```zig
comptime {
    describeArray([4]u8);
}
```

This tells the compiler:

```text
length: 4
child type: u8
```

An array type contains both its length and its element type.

That is why `[4]u8` and `[8]u8` are different types.

#### Inspecting Pointers and Slices

Pointers also have type information.

```zig
fn describePointer(comptime T: type) void {
    switch (@typeInfo(T)) {
        .pointer => |ptr_info| {
            @compileLog(ptr_info.size);
            @compileLog(@typeName(ptr_info.child));
        },
        else => @compileError("expected a pointer type"),
    }
}
```

Examples:

```zig
comptime {
    describePointer(*u8);
    describePointer([]const u8);
}
```

The pointer info tells you whether the type is a single-item pointer, many-item pointer, slice, or C pointer. It also tells you the child type.

For `*u8`, the child type is `u8`.

For `[]const u8`, the child type is also `u8`, but the pointer shape is different because a slice carries a pointer and a length.

#### Inspecting Enums

Enums can also be inspected.

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

Use `@typeInfo`:

```zig
fn logEnumTags(comptime T: type) void {
    switch (@typeInfo(T)) {
        .@"enum" => |enum_info| {
            inline for (enum_info.fields) |field| {
                @compileLog(field.name);
            }
        },
        else => @compileError("expected an enum type"),
    }
}
```

Usage:

```zig
comptime {
    logEnumTags(Color);
}
```

The compiler logs:

```text
red
green
blue
```

This is useful for building enum parsers, enum formatters, and lookup tables.

#### Reflection and Generics

Reflection is most useful when combined with generics.

A generic function can accept any type:

```zig
fn encode(comptime T: type, value: T) void {
    _ = value;

    switch (@typeInfo(T)) {
        .@"struct" => {
            // encode fields
        },
        .int => {
            // encode integer
        },
        .bool => {
            // encode boolean
        },
        else => @compileError("unsupported type"),
    }
}
```

The function body can inspect `T`, choose a path, and generate specialized code.

This is how many Zig libraries avoid runtime reflection while still supporting flexible APIs.

#### Reflection Is Not Magic

`@typeInfo` only tells you what the compiler knows about the type.

It does not inspect arbitrary runtime values.

For example, this makes sense:

```zig
const info = @typeInfo(User);
```

But this does not:

```zig
var user = User{ .id = 1, .name = "Ada", .active = true };
const info = @typeInfo(user);
```

`@typeInfo` expects a type, not a value.

Use:

```zig
const info = @typeInfo(@TypeOf(user));
```

Here, `@TypeOf(user)` gets the type of the value, then `@typeInfo` inspects that type.

#### A Practical Example: Counting Struct Fields

Here is a small useful function:

```zig
fn fieldCount(comptime T: type) usize {
    return switch (@typeInfo(T)) {
        .@"struct" => |struct_info| struct_info.fields.len,
        else => @compileError("expected a struct type"),
    };
}
```

Usage:

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

const count = fieldCount(User);
```

The result is:

```zig
3
```

The compiler knows the answer before runtime.

#### A Practical Example: Checking for a Field

You can check whether a struct has a field with a given name:

```zig
fn hasField(comptime T: type, comptime name: []const u8) bool {
    return switch (@typeInfo(T)) {
        .@"struct" => |struct_info| blk: {
            inline for (struct_info.fields) |field| {
                if (std.mem.eql(u8, field.name, name)) {
                    break :blk true;
                }
            }

            break :blk false;
        },
        else => false,
    };
}
```

Usage:

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

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

const a = hasField(User, "id");
const b = hasField(User, "email");
```

`a` is `true`.

`b` is `false`.

This kind of helper is useful in generic code.

#### Mental Model

`@typeInfo` lets you turn a type into compile-time data.

Then you can inspect that data with normal Zig code.

The usual pattern is:

```zig
fn someGenericFunction(comptime T: type, value: T) void {
    const info = @typeInfo(T);

    switch (info) {
        .@"struct" => |struct_info| {
            inline for (struct_info.fields) |field| {
                // generate field-specific code
            }
        },
        else => @compileError("unsupported type"),
    }
}
```

That is the heart of Zig reflection.

You do not ask the running program to discover types dynamically. You ask the compiler to inspect known types and produce direct code.

