🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Comptime Interfaces

A comptime interface is a set of fields or methods that a type must have to work with a function. The function checks for those members at compile time, so only matching types are accepted.

Reading An Id From Any Type

This program reads the id field from two different types. Both types have an id field, so both readings work.

const std = @import("std");
const User = struct { id: u32, name: []const u8 };
const Order = struct { id: u32, total: u32 };
fn getId(x: anytype) u32 {
    const T = @TypeOf(x);
    if (!@hasField(T, "id")) @compileError("type must have an id field");
    return x.id;
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const u = User{ .id = 7, .name = "ada" };
    const o = Order{ .id = 9, .total = 50 };
    try out.print("{}\n", .{getId(u) + getId(o)});
}

Reading A Name From Any Type

This program reads the name field from a type that has one. The check passes, so the name is printed.

const std = @import("std");
const User = struct { id: u32, name: []const u8 };
fn getName(x: anytype) []const u8 {
    const T = @TypeOf(x);
    if (!@hasField(T, "name")) @compileError("type must have a name field");
    return x.name;
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const u = User{ .id = 7, .name = "ada" };
    try out.print("{s}\n", .{getName(u)});
}

Checking For A Method

This program checks whether two types have an area method. Only the shape type has one.

const std = @import("std");
const Shape = struct {
    fn area(self: Shape) u32 {
        _ = self;
        return 12;
    }
};
const User = struct { id: u32, name: []const u8 };
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    try out.print("{} {}\n", .{ @hasDecl(Shape, "area"), @hasDecl(User, "area") });
}

Example

const std = @import("std");
const Person = struct { name: []const u8, age: u32 };
const Book = struct { name: []const u8, pages: u32 };
fn label(x: anytype) []const u8 {
    const T = @TypeOf(x);
    if (!@hasField(T, "name")) @compileError("type must have a name field");
    return x.name;
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const p = Person{ .name = "ada", .age = 30 };
    const b = Book{ .name = "Dune", .pages = 412 };
    try out.print("{s} {s}\n", .{ label(p), label(b) });
}

Key points

  • Comptime interface: a comptime interface is a set of members that a type must have for a function.
  • A member: a member is a field or a method of a type.
  • A function can accept any type, written as anytype, and check its members at compile time.
  • A type that lacks a required member fails the check, and compilation stops.
  • Two different types can satisfy the same interface, so one function serves both.
  • An interface keeps code flexible, because new types can join by having the same members.

The helper requires a name field, and both types have one. It returns the name from either type. The main function prints both names on one line.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What must a type have to satisfy a comptime interface?