# Reflection

### Reflection

Reflection means inspecting a type from inside the program.

In Zig, reflection happens at compile time.

The main builtin is:

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

`T` must be a type known at compile time.

A small example:

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

fn describe(comptime T: type) void {
    switch (@typeInfo(T)) {
        .int => std.debug.print("integer\n", .{}),
        .float => std.debug.print("float\n", .{}),
        .bool => std.debug.print("boolean\n", .{}),
        .pointer => std.debug.print("pointer\n", .{}),
        .@"struct" => std.debug.print("struct\n", .{}),
        else => std.debug.print("other\n", .{}),
    }
}

pub fn main() void {
    describe(i32);
    describe(f64);
    describe(bool);
    describe([]const u8);
}
```

Typical output:

```text
integer
float
boolean
pointer
```

`@typeInfo` returns a tagged union. Each tag describes one kind of type.

For an integer type, the information includes signedness and bit count.

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

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

    std.debug.print("{s}: {s}, {d} bits\n", .{
        @typeName(T),
        if (info.signedness == .signed) "signed" else "unsigned",
        info.bits,
    });
}

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

Typical output:

```text
i32: signed, 32 bits
u64: unsigned, 64 bits
```

This function assumes `T` is an integer type. If it receives another type, compilation fails.

A safer version checks first:

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

fn printInfo(comptime T: type) void {
    switch (@typeInfo(T)) {
        .int => |info| {
            std.debug.print("{s}: {s}, {d} bits\n", .{
                @typeName(T),
                if (info.signedness == .signed) "signed" else "unsigned",
                info.bits,
            });
        },
        else => {
            std.debug.print("{s}: not an integer\n", .{@typeName(T)});
        },
    }
}
```

The switch separates the cases. Inside the `.int` case, `info` has the integer metadata.

Reflection is especially useful for structs.

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

const Point = struct {
    x: i32,
    y: i32,
};

fn printFields(comptime T: type) void {
    const fields = @typeInfo(T).@"struct".fields;

    inline for (fields) |field| {
        std.debug.print("{s}: {s}\n", .{
            field.name,
            @typeName(field.type),
        });
    }
}

pub fn main() void {
    printFields(Point);
}
```

The output is:

```text
x: i32
y: i32
```

The field list is known at compile time. The `inline for` loop expands once for each field.

Reflection can also enforce rules.

```zig
fn requireField(comptime T: type, comptime name: []const u8) void {
    inline for (@typeInfo(T).@"struct".fields) |field| {
        if (std.mem.eql(u8, field.name, name))
            return;
    }

    @compileError(@typeName(T) ++ " is missing required field");
}
```

Use it in a compile-time block:

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

comptime {
    requireField(User, "id");
}
```

If the field is missing, compilation stops.

This is useful for generic code. A function can accept a struct type and check that it has the fields it needs.

Reflection can inspect enum tags too.

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

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

fn printEnumTags(comptime T: type) void {
    inline for (@typeInfo(T).@"enum".fields) |field| {
        std.debug.print("{s}\n", .{field.name});
    }
}

pub fn main() void {
    printEnumTags(Color);
}
```

The output is:

```text
red
green
blue
```

Reflection should not be the first tool used for every problem.

Use normal code when the shape of the data is already known.

Use reflection when code must depend on the structure of a type.

Good uses include:

```zig
fn fieldCount(comptime T: type) usize {
    return @typeInfo(T).@"struct".fields.len;
}
```

```zig
fn isPointer(comptime T: type) bool {
    return @typeInfo(T) == .pointer;
}
```

```zig
fn enumTagCount(comptime T: type) usize {
    return @typeInfo(T).@"enum".fields.len;
}
```

Reflection in Zig stays inside the language. It does not use a string-based macro language. It does not parse source text. It inspects typed program information after the compiler has understood the code.

That makes it precise, but also strict.

If a function expects a struct, say so by checking the type information.

```zig
fn requireStruct(comptime T: type) void {
    if (@typeInfo(T) != .@"struct")
        @compileError("expected struct type");
}
```

Then other reflection code can assume the struct case.

Exercise 10-17. Write `isInteger(comptime T: type) bool`.

Exercise 10-18. Write `fieldCount(comptime T: type) usize` for struct types.

Exercise 10-19. Print the names of all fields in a struct.

Exercise 10-20. Print the names of all tags in an enum.

