Using Zig to Call C Code: Strings

Great article. Some notes:

From the language reference, the first line in the C Pointers section:

This type is to be avoided whenever possible. The only valid reason for using a C pointer is in auto-generated code from translating C code.

The reason they exist is because auto-generated code can’t tell if a pointer translated from C should be a single item pointer or a many item pointer. C pointers coerce to single or many pointers, so for code where you know the intention of the pointer you should use the respective single or many pointer. By using C pointers you miss out on enforced null checking and automatic dereferencing using the . operator. You should change this

    const cCopy: [*c]u8 = cString.strdup(str);
    if (cCopy == null) {
        // Maybe we can return a better error by calling std.os.errno(), but for
        // now, return a generic error.
        return error.StrdupFailure;
    }

To this:

    const cCopy: [*:0]u8 = cString.strdup(str) orelse return error.OutOfMemory;

strdup can only fail with ENOMEM so I changed the error. What’s nice about Zig is that control flow statements are expressions, so we can put a return on the right hand side of the orelse here.
And just to note that the function Allocator.dupeZ exists which will copy a slice and append a null terminator if necessary - though using it kinda defeats the point of the last example.

The downside of this revision is that it’s slower than the previous version. I’m allocating and copying memory twice: once in C, and another time in Zig. If this were performance-critical code, perhaps I’d take the speedup by making the caller deal with the C array, but in general, I prefer to let Zig manage my memory.

If you want the performance and to let the allocator manage the memory, you wouldn’t call strdup at all and just copy using the passed in allocator - the call to strdup is completely unnecessary in the last example.

1 Like