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/error 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);

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 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 = std.meta.Int(.unsigned, 24);  // u24
const I7 = std.meta.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 });
// 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

std.meta.sentinel([:0]u8)      // @as(u8, 0)
std.meta.sentinel([*:0]u8)     // @as(u8, 0)
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.

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

// Check if value is error (deprecated: use std.enums.fromInt)
const result = std.math.divTrunc(u8, 5, 0);
std.meta.isError(result)  // true

// Enum from int (deprecated: use std.enums.fromInt)
const Color = enum { red, green, blue };
const c = std.meta.intToEnum(Color, 1) catch unreachable;  // Color.green

TrailerFlags

Memory-efficient optional field storage using bit flags:

const std = @import("std");

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

// 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, @alignOf(@TypeOf(flags).Fields), 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"),
    }
}