The past couple of days I’ve been learning Zig, a relatively new low-level programming language, described by its creator as “a DSL for assembly”, and a potential successor to C.

It has some very interesting language features, not least of which is the ability to run code at COMPILE TIME, to do all sorts of powerful meta-programming, subsuming the need for generics, for example.

Ziggy played for time
Jiving us that we were voodoo
The kids were just crass
He was the nazz
With God-given ass
He took it all too far
But boy, could he play guitar

Ziggy Stardust, David Bowie

As an exercise, here is the code to parse/stringify a set of Zig domain objects to JSON. An interesting challenge was getting this to work with heterogeneous arrays of objects, implemented using Zig Tagged Unions.

Here is the implementation of Tagged Union called Animal, that knows how to parse two concrete types of structs, Dog and Cat. The class attribute in the JSON is used to switch between the two structs. You can see a couple of examples of meta-programming in the use of the std.meta functions, as well as the !@This tag.

const DogBreed = enum {
Spaniel,
Poodle,
JackRussel
};

const Cat = struct {
name: []const u8,
ferocity: ?u32=0,
};

const Dog = struct {
name: []const u8,
dogBreed: DogBreed,
};

const Animal = union(enum) {
Dog: Dog,
Cat: Cat,
pub fn jsonParse(allocator: Allocator, source: anytype, options: std.json.ParseOptions) !@This() {
if (.object_begin != try source.next()) return error.UnexpectedToken;
// key
_ = try source.nextAlloc(allocator, options.allocate.?);
// class value
const classNameToken = try source.nextAlloc(allocator, options.allocate.?);
const className = switch (classNameToken) {
inline .string, .allocated_string => |k| k,
else => unreachable,
};
// std.debug.print("class: {s}\n", .{className});
const classType = std.meta.stringToEnum(std.meta.Tag(@This()), className) orelse return error.UnexpectedToken;
const result = switch(classType) {
.Cat => {
return Animal {
.Cat = try StructParser.parseStructBody(Cat, allocator, source, options),
};
},
.Dog => {
return Animal {
.Dog = try StructParser.parseStructBody(Dog, allocator, source, options),
};
},
};
if (.object_end != try source.next()) return error.UnexpectedToken;
return result;
}
};

Sample JSON:

{
"animals" : [
{
"class" : "Cat",
"name" : "Tabby"
},
{
"class" : "Cat",
"name" : "Tiddles",
"ferocity" : 10
},
{
"class" : "Dog",
"name" : "Fido",
"dogBreed" : "Spaniel"
}
]
}

One of the advantages of Zig is the ability to easily call existing C code from Zig, or to expose Zig functions using C calling conventions. Zig also compiles to WASM, making it easy to integrate with a wide-range of languages and runtimes.

To be continued…