🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Comptime Serialization

Comptime serialization is the conversion of a struct into text, guided by its fields at compile time. The field list is known while compiling, so one function can write every field of any struct.

Serializing A Point

This program writes each field of a point on its own line. The point holds 3 and 4.

const std = @import("std");
const Point = struct { x: i32, y: i32 };
fn serialize(out: anytype, s: anytype) !void {
    const T = @TypeOf(s);
    inline for (@typeInfo(T).@"struct".fields) |field| {
        if (field.type == []const u8) {
            try out.print("{s}={s}\n", .{ field.name, @field(s, field.name) });
        } else {
            try out.print("{s}={}\n", .{ field.name, @field(s, field.name) });
        }
    }
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const p = Point{ .x = 3, .y = 4 };
    try serialize(out, p);
}

Serializing A Book

This program writes a book with a text field and a number field. Each field uses the format its type needs.

const std = @import("std");
const Book = struct { title: []const u8, pages: u32 };
fn serialize(out: anytype, s: anytype) !void {
    const T = @TypeOf(s);
    inline for (@typeInfo(T).@"struct".fields) |field| {
        if (field.type == []const u8) {
            try out.print("{s}={s}\n", .{ field.name, @field(s, field.name) });
        } else {
            try out.print("{s}={}\n", .{ field.name, @field(s, field.name) });
        }
    }
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const b = Book{ .title = "Dune", .pages = 412 };
    try serialize(out, b);
}

Serializing A Pet

This program writes a pet with a name and a leg count. The text field and the number field each use their own format.

const std = @import("std");
const Pet = struct { name: []const u8, legs: u32 };
fn serialize(out: anytype, s: anytype) !void {
    const T = @TypeOf(s);
    inline for (@typeInfo(T).@"struct".fields) |field| {
        if (field.type == []const u8) {
            try out.print("{s}={s}\n", .{ field.name, @field(s, field.name) });
        } else {
            try out.print("{s}={}\n", .{ field.name, @field(s, field.name) });
        }
    }
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const p = Pet{ .name = "rex", .legs = 4 };
    try serialize(out, p);
}

Example

const std = @import("std");
const Book = struct { title: []const u8, pages: u32 };
const Point = struct { x: i32, y: i32 };
fn serialize(out: anytype, s: anytype) !void {
    const T = @TypeOf(s);
    inline for (@typeInfo(T).@"struct".fields) |field| {
        if (field.type == []const u8) {
            try out.print("{s}={s}\n", .{ field.name, @field(s, field.name) });
        } else {
            try out.print("{s}={}\n", .{ field.name, @field(s, field.name) });
        }
    }
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const b = Book{ .title = "Dune", .pages = 412 };
    const p = Point{ .x = 3, .y = 4 };
    try serialize(out, b);
    try serialize(out, p);
}

Key points

  • Comptime serialization: comptime serialization is the conversion of a struct into text using its compile-time field list.
  • Serialization: serialization is the act of writing a value in a form that can be stored or sent.
  • Each field becomes one line with its name, an equals sign, and its value.
  • Text fields and number fields need different print formats, and the field type decides which one is used.
  • One generic function can serialize many structs, because the field list comes from the type itself.
  • Serialized text is easy to read, because the field names stay beside their values.

The serializer walks the fields of any struct it is given. It picks a text format for text fields and a number format for the rest. The main function serializes a book and a point with the same function.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

How does a comptime serializer know which fields a struct has?