Home > Mobile >  How can I pass a Zig String literal to C
How can I pass a Zig String literal to C

Time:06-14

A Zig String literal is a single-item pointer to a null-terminated byte array, which is perfect to be used as a C's char * String!

However, when I try to use this simple C function from Zig:

int count_bytes(const char *str) {
  int count = 0;
  while(str[count]) {
    count  ;
  }
  return count;
}

Zig caller:

const std = @import("std");
const c = @cImport({
    @cInclude("add.c");
});
const testing = std.testing;

test "should be able to count string length" {
    try testing.expectEqual(0, c.count_bytes(""));
}

I get this error:

./src/main.zig:16:46: error: expected type '[*c]u8', found '*const [0:0]u8'
    try testing.expectEqual(0, c.count_bytes(""));
                                             ^
./src/main.zig:16:46: note: cast discards const qualifier
    try testing.expectEqual(0, c.count_bytes(""));
                                             ^

This article explains about Zig String literals in a similar situation, but I was unable to use the trick to make the String non-const:

test "should be able to count string length" {
    var cstr = "".*;
    try testing.expectEqual(0, c.count_bytes(&cstr));
}

Result is an even stranger error:

./src/main.zig:17:45: error: expected type 'comptime_int', found 'c_int'
    try testing.expectEqual(0, c.count_bytes(&cstr));
                                            ^

I also tried casting the String to a C Pointer as shown in the Zig Docs:

test "should be able to count string length" {
    var cstr: [*c]u8 = &"".*;
    try testing.expectEqual(0, c.count_bytes(cstr));
}

Which also gives an error:

./src/main.zig:16:27: error: expected type '[*c]u8', found '*const [0:0]u8'
    var cstr: [*c]u8 = &"".*;
                          ^
./src/main.zig:16:27: note: cast discards const qualifier
    var cstr: [*c]u8 = &"".*;
                          ^

How can I do this?

EDIT:

I am getting suggestions that do not work with Zig 0.9, which is the latest stable release as I am writing this.

Please try this out first if you think you know a solution... put the C file at src-c/add.c, and the Zig file at src/main.zig, then run this to try:

zig test src/main.zig -I src-c 

CodePudding user response:

error: expected type 'comptime_int', found 'c_int'

It's hard to see because of how zig currently prints the error location, but this error is actually for the second argument to expectEqual, not an error with the arguments to count_bytes

./src/main.zig:17:45: error: expected type 'comptime_int', found 'c_int'
    try testing.expectEqual(0, c.count_bytes(&cstr));
                                            ^

Would be slightly clearer if it was like this:
./src/main.zig:17:45: error: expected type 'comptime_int', found 'c_int'
    try testing.expectEqual(0, c.count_bytes(&cstr));
                               ~~~~~~~~~~~~~~~~~~~~

The arrow is pointing to the (, meaning it's intended to cover the entire call expression

This error is due to the way std.testing.expectEqual infers parameter types.

expectEqual is defined as fn expectEqual(a: anytype, b: @TypeOf(a)) void. This means that the type of b must be the same as the type of a and does not attempt peer type resolution.

In this case, the type of a is comptime_int because that is the default type for integer literals in zig, and then it errors when the second parameter is a runtime integer that cannot be cast to comptime_int

To solve this, you often need to explicitly cast whenever using expectEqual

try testing.expectEqual(@as(c_int, 0), c.count_bytes(""));

Issue #4437 proposes one possible solution to solve this, but no changes have been made yet.

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

I was not able to replicate this error with your provided code - is it possible that a header file incorrectly defines count_bytes as int count_bytes(char *str) instead of int count_bytes(const char *str)?

CodePudding user response:

regarding to the docs you're passing a pointer to N items (*[N]T) or pointer to runtime-known ([]T) instead pass a many-item pointer ([*]T) or a single-item pointer (*T).

so using cstr.ptr or &cstr[0] should work.

test "should be able to count string length" {
    var cstr = "";
    try testing.expectEqual(0, c.count_bytes(cstr.ptr));
}
  •  Tags:  
  • c zig
  • Related