# Type Reflection Basics

### Type Reflection Basics

Type reflection means asking questions about a type while Zig is compiling the program.

In Zig, types are values at compile time. That means code can inspect a type, make decisions from it, and use those decisions to generate normal runtime code.

The main tool for this is:

```zig
@typeInfo(T)
```

where `T` is a type.

For example:

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

This asks Zig:

```text
What kind of type is i32?
```

The answer is compile-time information about the type.

#### Types Are Compile-Time Values

In Zig, a type can be passed to a function at compile time.

```zig
fn printTypeName(comptime T: type) void {
    _ = T;
}
```

The parameter:

```zig
comptime T: type
```

means:

```text
T is a type known at compile time
```

You can call it like this:

```zig
printTypeName(i32);
printTypeName(bool);
printTypeName([]const u8);
```

Each call passes a type, not a runtime value.

This idea is important. Zig does not need a separate template language for many generic patterns. It lets you use normal Zig code with compile-time types.

#### `@TypeOf`

The builtin `@TypeOf` gives you the type of an expression.

```zig
const x: i32 = 123;
const T = @TypeOf(x);
```

Here, `T` is the type `i32`.

You can use this in generic code:

```zig
fn sameType(a: anytype, b: @TypeOf(a)) void {
    _ = a;
    _ = b;
}
```

The second parameter must have the same type as the first one.

This works:

```zig
sameType(@as(i32, 1), @as(i32, 2));
```

This does not:

```zig
sameType(@as(i32, 1), @as(u32, 2)); // error
```

`@TypeOf` lets the function express relationships between types.

#### `@typeInfo`

The builtin `@typeInfo` tells you what kind of type something is.

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

The result is a tagged union. That means it has different cases for different kinds of types.

An integer type has integer information.

A pointer type has pointer information.

A struct type has struct information.

An array type has array information.

A function type has function information.

You usually inspect it with `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",
        else => "other",
    };
}
```

Then:

```zig
const a = describe(i32);
const b = describe(f64);
const c = describe([]const u8);
```

At compile time, Zig chooses the correct branch.

#### Why Some Field Names Use `@"struct"`

You may notice this syntax:

```zig
.@"struct"
```

instead of:

```zig
.struct
```

That is because `struct` is a Zig keyword.

When a field name or enum tag has the same spelling as a keyword, Zig uses quoted identifier syntax.

So this:

```zig
.@"struct"
```

means the tag named `struct`.

You may see the same style with other keyword-like names.

#### A Simple Reflection Function

Here is a full example:

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

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

pub fn main() void {
    std.debug.print("{s}\n", .{typeName(i32)});
    std.debug.print("{s}\n", .{typeName(f64)});
    std.debug.print("{s}\n", .{typeName(bool)});
}
```

This prints:

```text
integer
float
boolean
```

The function `typeName` does not inspect runtime values. It inspects compile-time types.

#### Reflecting on Integer Types

Integer types have information such as signedness and bit count.

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

For an integer, the info includes:

```text
signedness
bits
```

A simplified example:

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

fn printIntInfo(comptime T: type) void {
    const info = @typeInfo(T);

    switch (info) {
        .int => |int_info| {
            std.debug.print("bits: {}\n", .{int_info.bits});
            std.debug.print("signedness: {}\n", .{int_info.signedness});
        },
        else => @compileError("expected an integer type"),
    }
}

pub fn main() void {
    printIntInfo(i32);
    printIntInfo(u64);
}
```

The function accepts a type, checks whether it is an integer, then reads integer metadata.

If someone calls:

```zig
printIntInfo(bool);
```

the function reports a compile-time error.

#### Reflecting on Arrays

An array type has a length and a child type.

```zig
const T = [4]u8;
```

This type means:

```text
array of 4 u8 values
```

Reflection can inspect that:

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

fn arrayLength(comptime T: type) usize {
    return switch (@typeInfo(T)) {
        .array => |array_info| array_info.len,
        else => @compileError("expected an array type"),
    };
}

pub fn main() void {
    const n = arrayLength([4]u8);
    std.debug.print("length: {}\n", .{n});
}
```

The result is:

```text
length: 4
```

The array length is known at compile time, so the function can return it as a compile-time-known value.

#### Reflecting on Pointers and Slices

Slices are represented through pointer information.

For example:

```zig
[]const u8
```

is a slice type.

You can inspect whether a pointer type is a slice:

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

fn isSlice(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .pointer => |ptr_info| ptr_info.size == .slice,
        else => false,
    };
}

pub fn main() void {
    std.debug.print("{}\n", .{isSlice([]const u8)});
    std.debug.print("{}\n", .{isSlice(*const u8)});
}
```

This prints:

```text
true
false
```

Both types involve pointers, but they are not the same shape.

```zig
[]const u8
```

is a slice: pointer plus length.

```zig
*const u8
```

is a single-item pointer.

Reflection lets generic code tell the difference.

#### Reflecting on Struct Fields

Reflection is especially useful with structs.

Suppose we have:

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

You can inspect the fields of `User` at compile time.

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

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

fn printStructFields(comptime T: type) void {
    const info = @typeInfo(T);

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

pub fn main() void {
    printStructFields(User);
}
```

This prints:

```text
id
name
active
```

The loop is:

```zig
inline for (struct_info.fields) |field| {
```

It runs at compile time. Zig unrolls the loop while compiling.

This is one of the main reasons reflection matters in Zig. You can inspect a type and generate code from its fields.

#### `inline for` and Reflection

A normal `for` loop runs at runtime.

An `inline for` loop is expanded at compile time.

When you loop over type metadata, you usually need `inline for`.

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

This lets Zig generate code for each field.

For example, a serializer might inspect a struct and generate code to write each field.

A logger might inspect a struct and print field names.

A validation function might check whether required fields exist.

#### Building a Simple Field Counter

Here is a small function that counts fields in a struct:

```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:

```text
3
```

This happens at compile time because `User` is known at compile time.

#### Reflection Helps Generic Code Stay Type-Safe

Reflection is often used with `anytype`.

For example, a generic function may accept many values, but only allow arrays and slices:

```zig
fn lenOf(value: anytype) usize {
    const T = @TypeOf(value);

    return switch (@typeInfo(T)) {
        .array => value.len,
        .pointer => |ptr_info| {
            if (ptr_info.size == .slice) {
                return value.len;
            }

            @compileError("lenOf expects an array or slice");
        },
        else => @compileError("lenOf expects an array or slice"),
    };
}
```

This function works for arrays:

```zig
const a = [_]u8{ 1, 2, 3 };
const n = lenOf(a);
```

It also works for slices:

```zig
const s: []const u8 = "hello";
const m = lenOf(s);
```

But it rejects integers, structs without `.len`, and other unrelated values with a clear compile-time error.

#### Reflection Is Not Runtime Introspection

In many languages, reflection means inspecting objects while the program runs.

Zig reflection is mainly compile-time reflection.

That means:

```text
The compiler inspects types.
The compiler generates code.
The final program runs the generated code.
```

This keeps runtime behavior simple.

There is no heavy runtime reflection system hidden inside every value.

A normal Zig value does not carry a large metadata object around at runtime.

#### Reflection and Public APIs

Reflection is powerful, but it can make code harder to read.

For beginner-level code, prefer explicit types.

Use reflection when it removes real repetition or makes a generic API safer.

Good use:

```text
generic serialization
generic formatting
field validation
type-safe containers
compile-time adapters
```

Poor use:

```text
making simple code clever
hiding ordinary field access
accepting any type when one concrete type would be clearer
```

Zig gives you reflection, but it expects you to use it deliberately.

#### The Main Idea

Type reflection lets Zig code inspect types at compile time.

The common tools are:

```zig
@TypeOf(value)
@typeInfo(T)
```

Use `@TypeOf` when you need the type of an expression.

Use `@typeInfo` when you need details about a type.

Together, they let you write generic code that is still checked by the compiler.

The key mental model is:

```text
Zig reflection happens mostly while compiling, not while running.
```

That is why it fits Zig’s style. It gives you powerful generic programming without turning the runtime into a dynamic reflection system.

