通过FFI将C99`bool`返回Rust的正确类型是什么?

What is the correct type for returning a C99 `bool` to Rust via the FFI?

我和一位同事一直在努力思考如何通过FFI将bool(又称为_Bool)返回给Rust。

我们有想要在Rust中使用的C99代码:

1
2
3
4
bool
myfunc(void) {
   ...
}

我们使用extern C块让Rust了解myfunc

1
2
3
extern"C" {
    fn myfunc() -> T;
}

T应该是什么具体类型?

Rust在libc板条箱中没有c_bool,如果您在互联网上搜索,您会发现人们讨论此问题的各种GitHub问题和RFC,但实际上并没有就这两者达成共识正确且可移植:

  • https://github.com/rust-lang/rfcs/issues/1982#issuecomment-297534238
  • https://github.com/rust-lang/rust/issues/14608
  • https://github.com/rust-lang/rfcs/issues/992
  • https://github.com/rust-lang/rust/pull/46156

据我所知:

  • C99中bool的大小没有定义,除了它必须至少足够大以存储true(1)和false(0)。换句话说,至少一个位长。
  • 它甚至可能有点宽。
  • 它的大小可能是ABI定义的。

该注释建议,如果将C99 bool作为参数传递到函数中或作为返回值传递到函数之外,并且bool小于C int,则将其提升为与一个int。在这种情况下,我们可以说Rust Tu32

好的,但是如果(出于某种原因)C99 bool为64位宽怎么办? u32仍然安全吗?也许在这种情况下,我们截断了4个最高有效字节,这很好,因为4个最低有效字节足以表示truefalse

我的推理正确吗?在Rust获得libc::c_bool之前,您将对T使用什么?为什么对于所有可能大小的C99 bool(> = 1位)它都是安全且可移植的?


从2018年2月1日开始,Rust的布尔值的大小与C的_Bool正式相同。

这意味着bool是在FFI中使用的正确类型。

此答案的其余部分适用于在做出正式决定之前的Rust版本

Until Rust gets a libc::c_bool, what would you use for T and why is it safe and portable for all possible sizes of a C99 bool (>=1 bit)?

正如您已经链接的那样,官方答案仍然是"待定"。这意味着唯一可以保证正确的可能性是:没有。

没错,这可能是令人悲伤的。唯一真正安全的方法是将您的bool转换为已知的固定大小的整数类型(例如u8),以进行FFI。这意味着您需要在两侧封送它。

实际上,我会在FFI代码中继续使用bool。正如人们指出的那样,它神奇地排列在当前广泛使用的所有平台上。如果该语言决定使bool FFI兼容,那您就走了。如果他们决定采取其他措施,如果他们不引入皮棉让我们迅速发现错误,我将感到非常惊讶。

也可以看看:

  • bool是否保证为1个字节?


经过一番思考,我将尝试回答我自己的问题。如果您在以下推理中发现漏洞,请发表评论。

这不是正确的答案-请参阅以下评论

我认为Rust u8对于T始终是安全的。

我们知道C99 bool是足以存储0或1的整数,这意味着它可以自由地是至少1位的无符号整数,或者(如果您觉得很奇怪)是至少2位的有符号整数位。

让我们按情况细分:

  • 如果C99 bool是8位,则Rust u8是完美的。即使在有符号的情况下,最高位也将为零,因为代表0和1永远不需要负2的幂。

  • 如果C99 bool大于Rust u8,则通过"向下转换"为8位大小,我们只会丢弃前导零。因此,这也是安全的。

  • 现在考虑C99 bool小于Rust u8的情况。从C函数返回值时,由于底层调用约定,不可能返回小于1个字节的大小的值。 CC将要求将返回值加载到寄存器或堆栈中的某个位置。由于最小的寄存器或存储器位置是一个字节,因此需要将返回值扩展为零(至少为一个字节)(我相信函数参数也是如此,也必须遵守调用约定) )。如果将值扩展为一个字节的值,则与情况1相同。如果将值扩展为较大的大小,则与情况2相同。