C interop: expected type `bool` found `c_int`
Radvendii opened this issue · 6 comments
Zig Version
0.13.0-dev.46+3648d7df1
Steps to Reproduce and Observed Behavior
Explanation below, but it's easier to see than explain. I've created a minimal reproduction so you can
git clone https://github.com/Radvendii/zig-scratch
cd zig-scratch
nix develop # or direnv allow
zig build
The error I get
install
└─ install zig-proj
└─ zig build-exe zig-proj Debug native 1 errors
src/main.zig:11:12: error: expected type 'bool', found 'c_int'
c.b = c.true;
~^~~~~
referenced by:
callMain: /nix/store/2sngd0f8ary1zgqcx5r6kaf8ab4w37i1-zig-0.13.0-dev.46+3648d7df1/lib/std/start.zig:511:32
callMainWithArgs: /nix/store/2sngd0f8ary1zgqcx5r6kaf8ab4w37i1-zig-0.13.0-dev.46+3648d7df1/lib/std/start.zig:469:12
remaining reference traces hidden; use '-freference-trace' to see all reference traces
The issue is with #include <stdbool.h>
from c-code, and then referencing values from zig. bool
gets mapped to zig bool
, but true
and false
get mapped to c_int
s, so a bool defined in c code can't be used as a bool when interfacing with that c code.
In particular, the generated zig code looks like
pub const @"bool" = bool;
pub const @"true" = @as(c_int, 1);
pub const @"false" = @as(c_int, 0);
Expected Behavior
It's tricky. I understand why bool
would get mapped to bool
, and I understand why #define false 0
in stdbool.h
would get translated to a c_int
. There's nothing "buggy" about either of those individually. But together they make interfacing with C code that makes use of bools kind of unworkable.
Assigning a c-defined bool to a c-defined bool-type variable seems like it should just work.
Thanks for the recatogrization, I wasn't sure if it was a bug or proposal.
I don't know how the tags work, so maybe this is correct anyways, but I didn't invoke zig translate-c
at any point, this is just from @cImport()
and @cInclude()
I've uncovered even weirder behaviour. In stdbool.h
, we have bool
defined by #define bool _Bool
, so I figured if I overrode this by putting
@cInclude("stdbool.h");
@cUndef("bool");
@cDefine("bool", "int");
To the top of the @cImport()
statement i would force zig to define @"bool" = c_int
so that the example works,
It doesn't work. Still defines @"bool" = bool
. However, wherever bool
is used as part of a struct, it now uses c_int
.
Totally unsure what's going on now. Is mapping C bool
to zig bool
hard-coded? Is re-#define
ing things not supported?
Okay, it seems indeed that this is a general thing. any time you have
const c = @cImport({
@cDefine("foo", "1");
@cUndef("foo");
@CDefine("foo", "2");
});
It's going to correctly apply the second #define
in any C code @cInclude
d later on (replacing foo
with 2
), but if you access that as a variable from zig, it will go with the first definition (c.foo == 1
).
I can't tell how much of this is intentional design vs. bugs. #define
s don't actually have the same semantics as const
declarations, but my intuition is that whatever the last #define
declaration is, that one should determine how it shows up in the zig code, since that's how it would show up in C code with the same set of #include
s and #define
s
More minimal foo.h
:
#define bool _Bool
#define true 1
#define false 0
The last 3 lines of zig translate-c foo.h
:
pub const @"bool" = bool;
pub const @"true" = @as(c_int, 1);
pub const @"false" = @as(c_int, 0);
I have a pretty janky workaround:
@cImport({
@cDefine("true", "(_Bool)1");
@cDefine("false", "(_Bool)0");
@cInclude("stdbool.h");
@cUndef("true");
@cUndef("false");
@cDefine("true", "(_Bool)1");
@cDefine("false", "(_Bool)0");
// other @cInclude()s that use stdbool.h...
});
A couple of points of explanation:
- We must use
(_Bool)1
, not(bool)1
. The latter confuses zig and it just doesn't understand what the 1 is doing there - We must pre-define as well as post-define
true
andfalse
. If we only define it before,stdbool.h
will override, and if we only define it after, zig will use the first definition (fromstdbool.h
) to generate its own idea of whattrue
andfalse
are. - We could also just replicate
stdbool.h
but with altered definitions oftrue
andfalse
if we were so inclined, it's not that long of a file.
It generates
pub const @"true" = @import("std").zig.c_translation.cast(bool, @as(c_int, 1));
pub const @"false" = @import("std").zig.c_translation.cast(bool, @as(c_int, 0));
Which is kind of goofy, but does work.