zig-skills/references/std-meta.md

11 KiB

std.meta

Comptime type introspection and manipulation utilities. Essential for generic programming, serialization, and metaprogramming.

Quick Reference

Function Purpose
stringToEnum(T, str) Convert string to enum variant
fields(T) Get struct/union/enum or concrete error-set fields
fieldNames(T) Get field names as string slice
fieldInfo(T, field) Get info for specific field
fieldIndex(T, name) Get field index by name
tags(T) Get all enum/error set values
Tag(T) Get tag type of enum/union
activeTag(u) Get active variant of tagged union
eql(a, b) Deep equality comparison
Child(T) Get child type of pointer/array/optional
Elem(T) Get element type of memory span
sentinel(T) Get sentinel value if any
FieldEnum(T) Generate enum from field names
hasFn(T, name) Check if type has function
hasMethod(T, name) Check if type has method

String to Enum Conversion

const std = @import("std");

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

// Convert runtime string to enum
const color = std.meta.stringToEnum(Color, "green");
if (color) |c| {
    // c == Color.green
}

// Returns null for invalid strings
const invalid = std.meta.stringToEnum(Color, "purple");  // null

Uses StaticStringMap for small enums (≤100 variants), inline iteration for larger ones.

Field Introspection

Get All Fields

const Point = struct {
    x: f32,
    y: f32,
    z: f32 = 0,
};

// Get struct fields
const point_fields = std.meta.fields(Point);
// point_fields.len == 3
// point_fields[0].name == "x"
// point_fields[0].type == f32

// Works for unions
const Result = union { ok: u32, err: []const u8 };
const union_fields = std.meta.fields(Result);

// Works for enums
const Status = enum { pending, done };
const enum_fields = std.meta.fields(Status);

// Works for error sets
const MyError = error{ NotFound, Timeout };
const error_fields = std.meta.fields(MyError);

fields cannot enumerate the global anyerror set; use it only with a concrete error set whose members are known.

Get Field Names

const names = std.meta.fieldNames(Point);
// names.* == .{ "x", "y", "z" }

for (names) |name| {
    std.debug.print("{s}\n", .{name});
}

Get Specific Field

// By enum literal
const x_info = std.meta.fieldInfo(Point, .x);
// x_info.name == "x"
// x_info.type == f32
// x_info.default_value_ptr == null

const z_info = std.meta.fieldInfo(Point, .z);
// z_info.default_value_ptr != null (has default 0)

// Get field index
const idx = std.meta.fieldIndex(Point, "y");  // 1
const bad = std.meta.fieldIndex(Point, "w");  // null

Enum/Union Tag Operations

Get Tag Type

const Status = enum(u8) { active = 1, inactive = 2 };
const TagInt = std.meta.Tag(Status);  // u8

const Tagged = union(enum) { a: u32, b: f32 };
const TagEnum = std.meta.Tag(Tagged);  // enum { a, b }

Get Active Tag

const Value = union(enum) { int: i32, float: f32 };

var v = Value{ .int = 42 };
const tag = std.meta.activeTag(v);  // Value.int

switch (tag) {
    .int => std.debug.print("integer\n", .{}),
    .float => std.debug.print("float\n", .{}),
}

Get All Tags

const Color = enum { red, green, blue };
const colors = std.meta.tags(Color);
// colors.* == .{ Color.red, Color.green, Color.blue }

const MyError = error{ A, B };
const errors = std.meta.tags(MyError);
// errors.* == .{ MyError.A, MyError.B }

Type Construction

FieldEnum - Generate Enum from Fields

const Point = struct { x: f32, y: f32 };
const PointField = std.meta.FieldEnum(Point);
// Equivalent to: enum { x, y }

// Iterate fields generically
inline for (std.meta.tags(PointField)) |field| {
    const info = std.meta.fieldInfo(Point, field);
    std.debug.print("{s}: {s}\n", .{ info.name, @typeName(info.type) });
}

For a tagged union, FieldEnum reuses the existing tag enum only when field names match in order and the tag values are consecutive from zero; otherwise it constructs a separate enum.

For tagged unions, returns the existing tag type if compatible.

DeclEnum - Generate Enum from Declarations

const Api = struct {
    pub fn getUser() void {}
    pub fn deleteUser() void {}
};

const ApiMethod = std.meta.DeclEnum(Api);
// Equivalent to: enum { getUser, deleteUser }

Int/Float Type Construction

const U24 = @Int(.unsigned, 24);  // u24
const I7 = @Int(.signed, 7);      // i7
const F32 = std.meta.Float(32);           // f32
const F16 = std.meta.Float(16);           // f16

Tuple Construction

// From type array
const T1 = std.meta.Tuple(&.{ u32, f32, bool }); // deprecated compatibility helper
// Equivalent to: struct { u32, f32, bool }

// From function signature
const T2 = std.meta.ArgsTuple(fn (u32, f32) void);
// Equivalent to: struct { u32, f32 }

Child/Element Types

Child - Direct Child Type

std.meta.Child(*u8)       // u8
std.meta.Child([]u8)      // u8
std.meta.Child([5]u8)     // u8
std.meta.Child(?u8)       // u8
std.meta.Child(@Vector(4, f32))  // f32

Elem - Element Type of Memory Spans

std.meta.Elem([5]u8)      // u8
std.meta.Elem([]u8)       // u8
std.meta.Elem([*]u8)      // u8
std.meta.Elem(*[10]u8)    // u8 (through pointer to array)
std.meta.Elem(?[*]u8)     // u8 (through optional)

Sentinel

const slice_sentinel = std.meta.sentinel([:0]u8).?; // @as(u8, 0)
const ptr_sentinel = std.meta.sentinel([*:0]u8).?;  // @as(u8, 0)
const array_sentinel = std.meta.sentinel([5:0]u8).?; // @as(u8, 0)
std.meta.sentinel([]u8)        // null
std.meta.sentinel([5]u8)       // null

Sentinel Type Construction

// Add sentinel to type
const S1 = std.meta.Sentinel([]u8, 0);   // [:0]u8
const S2 = std.meta.Sentinel([*]u8, 0);  // [*:0]u8

Deep Equality

const std = @import("std");

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

const p1 = Point{ .x = 1, .y = 2 };
const p2 = Point{ .x = 1, .y = 2 };
const p3 = Point{ .x = 1, .y = 3 };

std.meta.eql(p1, p2)  // true
std.meta.eql(p1, p3)  // false

// Works with nested structs, arrays, optionals, error unions
const Complex = struct {
    data: [3]u8,
    opt: ?i32,
};

const a = Complex{ .data = .{ 1, 2, 3 }, .opt = 42 };
const b = Complex{ .data = .{ 1, 2, 3 }, .opt = 42 };
std.meta.eql(a, b)  // true

// Pointers compared by address, not content
std.meta.eql(&p1, &p2)  // false (different addresses)
std.meta.eql(&p1, &p1)  // true

Supported types: structs, arrays, vectors, optionals, error unions, tagged unions, primitives.

Non-packed untagged unions cannot be compared generically because there is no active tag to select a field. Packed unions are the exception handled as their packed representation.

Not supported: untagged unions (compile error).

Type Queries

Check for Function/Method

const S = struct {
    value: u32,
    pub fn method(self: *@This()) void { _ = self; }
};

std.meta.hasFn(S, "method")      // true
std.meta.hasFn(S, "value")       // false (field, not fn)
std.meta.hasFn(S, "missing")     // false

std.meta.hasMethod(S, "method")  // true
std.meta.hasMethod(*S, "method") // true (through pointer)
std.meta.hasMethod([]S, "method") // false (slice, not single pointer)

Check Unique Representation

// True if type has no padding/unused bits
std.meta.hasUniqueRepresentation(u8)   // true
std.meta.hasUniqueRepresentation(u32)  // true
std.meta.hasUniqueRepresentation(i24)  // false (padded to 4 bytes)

// Struct with no padding
const Packed = struct { a: u32, b: u32 };
std.meta.hasUniqueRepresentation(Packed)  // true

// Struct with padding
const Padded = struct { a: u32, b: u16 };
std.meta.hasUniqueRepresentation(Padded)  // false

Container Layout

const Auto = struct {};
const Packed = packed struct {};
const Extern = extern struct {};

std.meta.containerLayout(Auto)    // .auto
std.meta.containerLayout(Packed)  // .@"packed"
std.meta.containerLayout(Extern)  // .@"extern"

Alignment

// For pointers, returns the pointed-to alignment (not pointer alignment)
std.meta.alignment(*align(16) u8)  // 16
std.meta.alignment([]align(8) u8)  // 8
std.meta.alignment(u8)             // 1

Declarations

const S = struct {
    pub const value = 42;
    pub fn method() void {}
};

const decls = std.meta.declarations(S);
// decls[0].name == "method" or "value"

Error Handling

// Inspect an error union with ordinary error-union syntax.
const result = std.math.divTrunc(u8, 5, 0);
if (result) |value| {
    _ = value;
} else |err| {
    _ = err;
}

// Checked enum conversion returns an optional.
const Color = enum { red, green, blue };
const c = std.enums.fromInt(Color, 1) orelse return error.InvalidColor;

TrailerFlags

Memory-efficient optional field storage using bit flags:

const std = @import("std");

const Trailer = struct {
    name: []const u8,
    age: u32,
    email: []const u8,
};
const Flags = std.meta.TrailerFlags(Trailer);

// Initialize with some fields active
var flags = Flags.init(.{
    .name = true,
    .age = true,
    .email = false,
});

// Allocate only needed space
const size = flags.sizeInBytes();
const data = try allocator.alignedAlloc(u8, .of(Trailer), size);
defer allocator.free(data);

// Set values
flags.set(data.ptr, .name, "Alice");
flags.set(data.ptr, .age, 30);

// Get values (returns optional)
const name = flags.get(data.ptr, .name);  // ?"Alice"
const email = flags.get(data.ptr, .email); // null

// Set multiple at once
flags.setMany(data.ptr, .{
    .name = "Bob",
    .age = 25,
});

Use case: Allocating objects with many optional components where memory matters.

Generic Programming Patterns

Iterate All Fields

fn printStruct(value: anytype) void {
    const T = @TypeOf(value);
    inline for (std.meta.fields(T)) |field| {
        const v = @field(value, field.name);
        std.debug.print("{s}: {any}\n", .{ field.name, v });
    }
}

Create Default Instance

fn createDefault(comptime T: type) T {
    var result: T = undefined;
    inline for (std.meta.fields(T)) |field| {
        if (field.default_value_ptr) |ptr| {
            @field(result, field.name) = @as(*const field.type, @ptrCast(@alignCast(ptr))).*;
        } else {
            @field(result, field.name) = std.mem.zeroes(field.type);
        }
    }
    return result;
}

Dynamic Field Access

fn getField(comptime T: type, value: T, comptime name: []const u8) ?std.meta.fieldInfo(T, std.meta.stringToEnum(std.meta.FieldEnum(T), name) orelse return null).type {
    const field = std.meta.stringToEnum(std.meta.FieldEnum(T), name) orelse return null;
    return @field(value, @tagName(field));
}

Serialize to JSON-like

fn toJson(value: anytype, writer: anytype) !void {
    const T = @TypeOf(value);
    switch (@typeInfo(T)) {
        .@"struct" => |info| {
            try writer.writeAll("{");
            inline for (info.fields, 0..) |field, i| {
                if (i > 0) try writer.writeAll(",");
                try writer.print("\"{s}\":", .{field.name});
                try toJson(@field(value, field.name), writer);
            }
            try writer.writeAll("}");
        },
        .int, .float => try writer.print("{}", .{value}),
        .pointer => |ptr| if (ptr.size == .slice and ptr.child == u8) {
            try writer.print("\"{s}\"", .{value});
        },
        else => try writer.writeAll("null"),
    }
}