@typeInfo
@typeInfo asks the compiler for structured information about a type.
Example:
const info = @typeInfo(u32);This means:
Tell me what kind of type u32 is.The answer is not a string. It is a compile-time value that describes the type.
For u32, the information says: this is an integer type, it is unsigned, and it has 32 bits.
@typeInfo Takes a Type
This is the normal form:
const info = @typeInfo(T);T must be a type.
For example:
const a = @typeInfo(u8);
const b = @typeInfo(i64);
const c = @typeInfo([]const u8);If you have a value and want to inspect its type, combine @TypeOf and @typeInfo:
const x = 123;
const info = @typeInfo(@TypeOf(x));Read this as:
Get the type of x.
Then get information about that type.A Simple Example
const std = @import("std");
pub fn main() void {
const info = @typeInfo(u32);
switch (info) {
.int => |int_info| {
std.debug.print("integer bits: {}\n", .{int_info.bits});
std.debug.print("signedness: {}\n", .{int_info.signedness});
},
else => {
std.debug.print("not an integer\n", .{});
},
}
}Possible output:
integer bits: 32
signedness: builtin.Signedness.unsignedThe switch checks what kind of type u32 is.
Since u32 is an integer type, the .int branch runs.
Type Information Is Tagged
@typeInfo returns a tagged value.
That means the result has a category, such as:
int
float
bool
pointer
array
struct
enum
union
optional
error_union
function
voidEach category carries different information.
An integer has a bit count and signedness.
A pointer has a child type, alignment, constness, and pointer size.
An array has a child type and length.
A struct has fields and declarations.
So @typeInfo does not return the same shape for every type. You must switch on the type category first.
Inspecting an Integer
const std = @import("std");
fn describeInt(comptime T: type) void {
const info = @typeInfo(T);
switch (info) {
.int => |int_info| {
std.debug.print("bits: {}\n", .{int_info.bits});
std.debug.print("signed: {}\n", .{int_info.signedness == .signed});
},
else => {
std.debug.print("not an integer type\n", .{});
},
}
}
pub fn main() void {
describeInt(u8);
describeInt(i32);
}Possible output:
bits: 8
signed: false
bits: 32
signed: trueThe function takes a type at compile time:
fn describeInt(comptime T: type) voidThat is necessary because type information exists at compile time.
Inspecting an Array
Arrays have a length and a child type.
const std = @import("std");
fn describeArray(comptime T: type) void {
const info = @typeInfo(T);
switch (info) {
.array => |array_info| {
std.debug.print("array length: {}\n", .{array_info.len});
std.debug.print("element size: {}\n", .{@sizeOf(array_info.child)});
},
else => {
std.debug.print("not an array\n", .{});
},
}
}
pub fn main() void {
describeArray([5]u16);
}Possible output:
array length: 5
element size: 2For [5]u16, the array length is 5, and the child type is u16.
Inspecting a Pointer
Pointers carry more information.
const std = @import("std");
fn describePointer(comptime T: type) void {
const info = @typeInfo(T);
switch (info) {
.pointer => |ptr_info| {
std.debug.print("is const: {}\n", .{ptr_info.is_const});
std.debug.print("child size: {}\n", .{@sizeOf(ptr_info.child)});
},
else => {
std.debug.print("not a pointer\n", .{});
},
}
}
pub fn main() void {
describePointer(*u32);
describePointer(*const u32);
}Possible output:
is const: false
child size: 4
is const: true
child size: 4*u32 and *const u32 both point to a u32, but the second one points to data that should not be modified through that pointer.
@typeInfo can see that difference.
Inspecting a Struct
Structs contain fields.
const std = @import("std");
const Point = struct {
x: i32,
y: i32,
};
fn describeStruct(comptime T: type) void {
const info = @typeInfo(T);
switch (info) {
.@"struct" => |struct_info| {
inline for (struct_info.fields) |field| {
std.debug.print("field: {s}, size: {}\n", .{
field.name,
@sizeOf(field.type),
});
}
},
else => {
std.debug.print("not a struct\n", .{});
},
}
}
pub fn main() void {
describeStruct(Point);
}Possible output:
field: x, size: 4
field: y, size: 4Notice the syntax:
.@"struct"struct is a keyword, so this form is used when matching the type information tag.
The inline for matters because struct_info.fields is compile-time information. The compiler unrolls the loop while compiling the program.
Why @typeInfo Is Useful
Most beginner programs do not need reflection.
But @typeInfo becomes useful when writing generic code.
For example, you may want a function that only accepts integer types:
fn requireInteger(comptime T: type) void {
switch (@typeInfo(T)) {
.int => {},
else => @compileError("expected an integer type"),
}
}Then:
requireInteger(u32); // ok
requireInteger([]const u8); // compile errorThis lets you enforce rules at compile time.
A Generic Example
Here is a function that returns the bit count of an integer type:
fn bitCount(comptime T: type) comptime_int {
return switch (@typeInfo(T)) {
.int => |int_info| int_info.bits,
else => @compileError("bitCount expects an integer type"),
};
}Use it like this:
const std = @import("std");
fn bitCount(comptime T: type) comptime_int {
return switch (@typeInfo(T)) {
.int => |int_info| int_info.bits,
else => @compileError("bitCount expects an integer type"),
};
}
pub fn main() void {
std.debug.print("u8 has {} bits\n", .{bitCount(u8)});
std.debug.print("u64 has {} bits\n", .{bitCount(u64)});
}Output:
u8 has 8 bits
u64 has 64 bitsThis function does not run normal runtime inspection. It runs using compile-time type information.
@typeInfo and @Type
The two builtins work together.
@typeInfo takes a type and returns a description.
@Type takes a description and builds a type.
So conceptually:
const info = @typeInfo(u32);
const T = @Type(info);T becomes u32.
In normal beginner code, you rarely need this pair. But it shows a deep Zig idea: types can be inspected, passed around, and constructed at compile time.
Do Not Use It When Simple Code Is Enough
This is simple:
fn add(a: u32, b: u32) u32 {
return a + b;
}Do not replace it with reflection-heavy code unless you have a reason.
@typeInfo is powerful, but it can make code harder to read.
Use it when your code truly needs to react to the shape of a type.
Good uses include:
generic containers
serialization
formatting
binary encoding
compile-time validation
testing helpers
type-safe wrappersPoor uses include making ordinary code clever for no reason.
Key Idea
@typeInfo(T) gives compile-time information about type T.
It lets Zig code inspect integers, pointers, arrays, structs, enums, unions, optionals, functions, and other types.
For beginners, remember this simple rule:
Use @typeInfo when generic code needs to ask, “What kind of type is this?”