I have a zig project integrating with a c library (xlib). The library has a method that requires I pass the entire argv array to it as a parameter. In the zig extern definition the expected type of the library function's argv parameter is [*c][*c]u8
.
As I am running linux I am using the non-cross-platform way of getting cli arguments in zig
var argv = std.os.argv;
The zig args are of type [][*:0]u8
. I have 2 questions.
[][*:0]u8
-> [*c][*c]u8
)[*c][*c]u8
mean?Its been a long time since I've worked at this low a level. Am I right in understanding that [*c][*c]u8
is a list of c pointers, where each c pointer is the pointer to the first character of a null-terminated string?
What is the most pragmatic way to cast/build the zig vargs into C vargs ( [][*:0]u8 -> [*c][*c]u8)
@ptrCast
the .ptr
property on the slice:
const argv = std.os.argv;
const c_ptr: [*c][*c]u8 = @ptrCast(argv.ptr);
(I don't think a pointer cast should be necessary here - it should be able to implicitly cast argv.ptr
, but it refuses to implicitly cast the pointer child)
What exactly does [*c][*c]u8 mean? / Am I right in understanding that [*c][*c]u8 is a list of c pointers, where each c pointer is the pointer to the first character of a null-terminated string?
Zig pointers are a little bit confusing. There are pointers, slices, and value arrays with different options and similar syntax:
*u8
: a pointer to a u8 value. Can be dereferenced (value.*
) but not indexed (value[0]
)[*]u8
: an unknown length many-item pointer to a u8 value. Can be indexed but not dereferenced.[*c]u8
: a one-or-many item pointer type that exists for c compatibility only. It can be both dereferenced and indexed.
int myvalue = 0;
int* mypointer = &myvalue;
*mypointer = 1; // allowed
mypointer[0] = 2; // allowed
[*c]
pointers, which allow both uses.[]u8
: a slice, which is a pointer and a length. it is similar to struct {ptr: [*]u8, len: usize}
. It can be indexed but not dereferenced.[N]u8
: a value array type. This is not a pointer, even though it looks like one.Unknown-length pointers and slices can have a sentinel value. [:0]u8
specifies that the slice has a 0
after the last item (slice[slice.len] == 0
). [*:0]u8
specifies that while the length is not known, the item after the last item of the array should be a 0
.
Both regular pointers and unknown-length pointers can cast implicitly to c compatibility pointers.
Now, to the question
[*c]
one or many item pointer containing
[*c]
one or many item pointer containing
You are correct in your interpretation.
Because we know these are meant to be arrays, a better representation in zig would be [*][*:0]u8
, an array of arrays. translate-c
doesn't know this though, so it emits c compatibility pointers.
[*]
unknown-length array pointer containing
[*:0]
unknown-length 0
-sentineled array pointer containing
Because we also know the length of the outer array (with argv), we can combine the pointer [*][*:0]u8
and length usize
into a slice: [][*:0]u8
.
This is why std.os.argv
returns [][*:0]u8
.