Optional generic argument

Thanks for your replies.

As an exercise for me, and also to learn more about Zig, I wanted to try writing a small wrapper for libpq. This isn’t intended as a to-be-used library, but more as an experiment for myself to see how certain things could be implemented when using existing C infrastructure.

First of all, I noticed that the Zig error types do not carry payload. As far as I understand, I need a separate mechnism to do this sort of specific error reporting. I’m not sure what’s the best approach here, but I attempted something like this:

pub const Db = struct {
    const Self = @This();
    pgConn: ?*c.PGconn,
    pub fn connect(params: ConnectParams, msghdl: anytype) DbError!Db {
        const conn = c.PQconnectdb(params.string) orelse return DbError.OutOfMemory;
        if (c.PQstatus(conn) != c.CONNECTION_OK) {
            defer c.PQfinish(conn);
            if (@TypeOf(msghdl) != void) {
                msghdl.errmsg(c.PQerrorMessage(conn));
            }
            return DbError.ConnectError;
        }
        return .{ .pgConn = conn };
    }
};

If the caller doesn’t care about the error message, it’s possible to call Db.connect(…, {}). But when the error message is of interest, the caller may pass some handler that can print or copy the error message. I assumed that I could avoid any extra allocation for the error string if I do it this way, because the error string lives until I call PQfinish.

I think I don’t really need a nullable anytype as nullable is for runtime “nullability”. At compile-time, I guess the right way would be to pass a value of type void (i.e. {}) if I want to indicate that I don’t want diagnostics. But correct me please if this is a bad approach. Maybe there is also an entirely different way of (optionally) reporting the error string on connection failure.

Interesting, I wasn’t aware that null without any specific type information has its own type @TypeOf(null).

Rethinking about this, I don’t think it’s necessary to support .optional. But this makes me wonder, should I use {} (of type void) or null (of type @TypeOf(null)) to indicate that I don’t want diagnostics? Or my own special type like NoDiagnosticsPleaseDiscardAllErrorStrings?

To get back to my simple (dumbed down) example of an optional writer:

const std = @import("std");

const DoNotWrite = struct {};
const doNotWrite = .{};

pub fn foo(optwriter: anytype) !void {
    switch (@TypeOf(optwriter)) {
        void => {}, // is this better?
        @TypeOf(null) => {}, // or this?
        // or something like this:
        DoNotWrite => {},
        @TypeOf(doNotWrite) => {},
        else => try optwriter.writeAll("Hello\n"),
    }
}

pub fn main() !void {
    try foo(std.io.getStdOut());
    try foo({});
    try foo(null);
    try foo(DoNotWrite{});
    try foo(doNotWrite);
}

Which of these variants seem best, when I only want “compile-time optionality”?

Thanks! I’m very happy to have found Zig and I’m eager to take a closer look at it and to understand all its concepts. Its simplicity reminds me a bit of Lua, but with a lot of stuff happening at compile-time and the efficiency of C.

1 Like