Improving on [*c]u8 when calling a C function that allocates a string?

Hi All, I’m a Zig beginner, and I’ve been learning about using Zig to maintain C code.

I’m stuck on a typing issue, and I’m wondering if anyone can help.

I have a C function whose signature looks like this:

void foo(char** c);

The function allocates and populates a null-terminated string, and the caller is responsible for freeing the memory.

I can call the function from Zig like this:

const std = @import("std");

const lib = @cImport({
    @cInclude("foo.c");
});

pub fn main() !void {
    var x: [*c]u8 = null;
    lib.foo(&x);
    std.c.free(x);
}

But I’d like to avoid the [*c]u8 type, as I know that the output parameter from C is a many-item, null-terminated pointer.

My intuition is to define the type like this:

    var x: [*:0]u8 = undefined;
    lib.foo(&x);

But that gives me a compilation error:

error: expected type '[*c][*c]u8', found '*[*:0]u8'

I’ve tried a few different variations, but the Zig compiler rejects all of them.

Am I missing a more obvious type here?

Maybe something like this:

[*:null]?[*:0]const u8

There was a related discussion on this recently: Null terminated array of strings from C function - #4 by castholm

1 Like

Thanks for the suggestion!

That unfortunately doesn’t compile either:

    var x: [*:null]?[*:0]const u8 = undefined;
    lib.foo(&x);
 error: expected type '[*c][*c]u8', found '[*:null]?[*:0]const u8

I think the issue is that in the other thread, the user was working with an array of strings, whereas in this case, the foo function just populates a single string.

Ok, what about then?:

    var x: ?[*:0]u8 = null;

Your type is correct.

var x: [*:0]u8 = undefined;
lib.foo(@ptrCast(&x));

Ah, that did it. Thanks so much!

1 Like

Hmm, that gives me a segfault:

$ zig build run
Segmentation fault at address 0x0
???:?:?: 0x7ffff7e72075 in ??? (libc.so.6)
Unwind information for `libc.so.6:0x7ffff7e72075` was not available, trace may be incomplete
1 Like

Well, that’s weird. At least you already have a solution.

I ran into this recently too. The issue is that if x: ?*[*:0]u8 was allowed to coerce to y: [*c][*c]u8 = x, then the type system would have to allow y.* = null, violating the guarantee that x.?.* is not null. So you have to add an extra optional x: ?*?[*:0]u8.

It seems that this is a limitation you have to live with when using @cImport, and to get the “correct” types you need to manually declare the signature of foo (which I generally prefer over @cImport anyway).

extern fn foo(c: ?*[*:0]u8) void;
1 Like