Search code examples
cpointerszig

Zig convert std.os.argv to C type argv


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.

  1. What is the most pragmatic way to cast/build the zig vargs into C vargs ( [][*:0]u8 -> [*c][*c]u8)
  2. What exactly does [*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?


Solution

  • 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.
      • in c, all pointers can be both dereferenced and indexed:
        int myvalue = 0;
        int* mypointer = &myvalue;
        *mypointer = 1; // allowed
        mypointer[0] = 2; // allowed
        
      • when translating c to zig, zig doesn't know if the c pointer is intended to be a single item or an array.
      • this is why it emits [*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
        • u8

    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
        • u8

    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.