Is Rusts `bool` compatible with C99 `_Bool` or C++ `bool`?


#1

Is Rust’s bool guaranteed to be compatible with C99 _Bool or C++ bool? This would allow for slightly nicer FFI APIs.


#2

Don’t believe so. So far as I know, none of C, C++ or Rust define how big a bool is. They could coincidentally agree, or all be completely different.


#3

On any platform Rust supports, a bool is a byte (which is 8 bits), as far as I know. I reckon all the standard C(++) compilers on those platforms agree.


#4

I try never to underestimate the pathological nature of C/C++. Then again, this particular case might be because I spent so long dealing with bools that were four bytes. Or two bytes and stored as 0 / !0 rather than 0 / 1.

Besides, the question was whether it’s guaranteed; I see no such guarantees anywhere. :slight_smile:


#5

That “pathological” design has allowed C to be ported across very different CPUs architectures for decades.


#6

And the structure of influenza has allowed it to spread across very different people and even between species for hundreds (probably thousands) of years. What’s your point? :stuck_out_tongue:

Completely independent of whether or not it was a good idea at the time, the vagaries of C’s basic types are, contemporaneously speaking, more of a pain in the backside than anything.


#7

Previously discussed in issue 14608. I think we just need to update libc and docs to add the c_bool alias.

Rust is not bound by the “vagaries” of the C standard, it already nails down stricter requirements on the basic types.


#8

I agree that the situation with C sizes can be confusing, but you and @leonardo should complain about it elsewhere, let’s keep this on topic, please.


#9

While a bool might take up a byte, in LLVM a bool is i1 and will have a bunch of surprising behaviours. e.g. 2u8 “transmuted” as bool is false and 3u8 “transmuted” as bool is true again. So no, there’s no guarantees on size. LLVM if it pleases could just pack array of bytes into a bit vector, for example.


#10

I thought we stopped emitting i1 precisely because it kept making LLVM do stupid things? Maybe that was something else…


#11

This came up on IRC: we currently store i8 wherever bool is expected, but LLVM’s branch intrinsics expect i1’s so we pass them around in some places. If you have an &bool it’s a pointer to a byte, though.

However it may be desirable to enable some exotic optimizations with booleans that can’t be pointed to. For instance Cell<[bool; 8]> can safely be represented as a byte, in principle. As such, the repr of a boolean is a bit wobbly!


#12

The original question was about connecting Rust code to C and C++ code. I thought the intent of #[repr(C)] was that placing it above a type definition would force it to use the same representation for the type that the local C ABI specifies. That seems like exactly what you want for connecting to foreign code.

While the C and C++ specifications don’t fix the representation of their bool types, Application Binary Interface (ABI) specifications do. An ABI specifies calling conventions and value representations for a given source language being compiled to a given machine language. For example, there’s an x86_64 C++ ABI. The intention is that you should be able to link together object code produced by two different compilers and have it work, as long as they’re adhering to the same ABI. Since you could be passing pointers to data structures around, the ABI ends up specifying exactly how every type is represented: structure layout, alignment, pointer size, and so on.

Naturally, the ABI only covers how functions and values visible outside the object file must behave. The compiler is free to represent values that are never visible to the outside world (say, variables local to a function) in any way it likes.

Rust doesn’t have an ABI: it deliberately hasn’t made a public commitment to the exact representation of its values. This is why the Rustonomicon has to say things like, “Most primitives are generally aligned to their size, although this is platform-specific behavior”, instead of just telling you what the alignments are. In the C/C++ world, one would write “ABI-specific behavior.”

So to the question as posed:

Is Rust’s bool guaranteed to be compatible with C99 _Bool or C++ bool?

The pedantic answer is “No, Rust doesn’t guarantee anything”.

The empirical answer is what Gankro gave: “I know the compiler; it’s always a byte. See if your C ABI agrees.”

And as far as I know, the pragmatic answer is “Try to use #[repr(C)], because if that doesn’t match what the C compiler is doing, it’s Rust bug.”


#13

As far as I know, that affects the type in question, not the ones it contains. So it can control layout, but not representation of the constituent parts.

Putting #[repr(C)] on a struct containing a bool causing it to potentially change size would be bizzare; you’d have a struct containing a bool that isn’t actually a bool any more.


#14

Putting #[repr©] on a struct containing a bool causing it to potentially change size would be bizzare; you’d have a struct containing a bool that isn’t actually a bool any more.

Now that you say it, I’m sure you’re right about this. It makes sense for #[repr(C)] to affect the layout of the struct, but for it to effectively change the types of the members would be strange.

That suggests the better answer: use the types in the libc crate. The idea there is that libc::c_T or libc::T should be a Rust type with a representation equivalent to the C type T.

Unfortunately, libc::c_bool doesn’t exist. That seems like a bug.


#15

repr© does affect the “form” of things beyond layout. At least, this is the case for enums (it suppresses stashing the tag in unused bits).


#16

It turns out this has all been discussed before. There was an RFC about adding libc::c_bool.