Reflection means inspecting a type from inside the program.
In Zig, reflection happens at compile time.
The main builtin is:
@typeInfo(T)T must be a type known at compile time.
A small example:
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:
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.
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:
i32: signed, 32 bits
u64: unsigned, 64 bitsThis function assumes T is an integer type. If it receives another type, compilation fails.
A safer version checks first:
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.
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:
x: i32
y: i32The field list is known at compile time. The inline for loop expands once for each field.
Reflection can also enforce rules.
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:
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.
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:
red
green
blueReflection 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:
fn fieldCount(comptime T: type) usize {
return @typeInfo(T).@"struct".fields.len;
}fn isPointer(comptime T: type) bool {
return @typeInfo(T) == .pointer;
}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.
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.