通过 FFI 将 C99 的 `bool` 类型正确返回给 Rust,应该使用哪种类型?

16

我和同事一直在考虑如何通过 FFI 从 C99 代码中返回 <stdbool.h> (即 _Bool) 到 Rust 中的 bool

我们想要在 Rust 中使用以下 C99 代码:

bool
myfunc(void) {
   ...
}

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

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

具体来说,T 应该是什么类型?

Rust 的 libc 库中没有 c_bool 类型。如果你在互联网上搜索,你会发现有各种 GitHub 问题和 RFC,人们在讨论这个问题,但并没有就正确性和可移植性达成共识:

据我所知:
- C99中的bool大小未定义,除了必须足够大以存储true(1)和false(0)之外。换句话说,至少为一个比特长。 - 它甚至可能是一位宽。 - 它的大小可能是ABI定义的这条评论表明,如果将C99 bool作为参数传递到函数中或作为返回值传递出函数,并且bool小于C int,则会将其提升为与int相同的大小。在这种情况下,我们可以告诉Rust T是u32。
好吧,但是如果(由于某种原因)C99 bool宽度为64位呢? u32仍然安全吗?也许在这种情况下,我们截断最高的4个字节,这是可以接受的,因为最低的4个字节已经足以表示true和false。

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


在这种情况下,我们不能告诉 Rust Tu32 — 因为同样的问题也会出现:C 并没有定义 int 的大小,除了最小为 16 位 - Shepmaster
1
一个 bool 类型至少要有 CHAR_BIT 位,因此至少为 8 位(因为 CHAR_BIT >= 8)。链接中的脚注指出 bool宽度,这个术语定义时不包括填充位,可能是 1。 - user2357112
Shepmaster: 哎呀,我想说的是'c_int'而不是'u32'。这样可以吗? - Edd Barrett
@EddBarrett 我认为这肯定会更好,但是64位的可能性仍然令人担忧。 - Shepmaster
Shepmaster: 嗯。我认为我从中得出的结论是,只有当C99 bool大于'T'时才存在问题。因此,接下来,使用最小的无符号Rust整数类型应该是安全的,即u8?如果C bool更大,我们就截断它。如果它更小,例如1位,那么这应该被扩展到内存单元或寄存器可以寻址的最小整数:一个字节,这与u8相一致。这个推理有什么漏洞吗? - Edd Barrett
2个回答

13
截至2018-02-01,Rust的bool类型大小已正式与C语言的_Bool相同
这意味着在FFI中使用bool是正确的类型。
这篇答案的其余部分适用于 Rust 官方做出决定之前的版本。
在 Rust 获得 libc::c_bool 之前,您会使用什么作为 T?为什么对于 C99 bool 的所有可能大小(>=1 bit)都是安全且可移植的?
正如您已经链接到的那样,官方答案仍然是“待定”。这意味着唯一保证正确的可能性是:没有任何东西。
没错,尽管这可能很令人沮丧。唯一真正安全的方法是将您的布尔值转换为已知的固定大小整数类型(例如 u8),以便进行 FFI。这意味着您需要在两侧进行编组。
实际上,在我的FFI代码中,我会继续使用bool。正如人们所指出的那样,它在目前广泛使用的所有平台上都能神奇地对齐。如果语言决定使bool与FFI兼容,那么你就可以放心使用。如果他们决定使用其他东西,我会非常惊讶,如果他们没有引入lint来帮助我们快速捕捉错误。

另请参见:


我想指出,为了FFI的目的,限制自己使用C89可能会更有趣,因为它比C99标准化得多。使用C99没有太大的收益,因此可以在可移植性方面获得额外的优势。在C89中,通常约定使用int(或Rust中的libc :: c_int)作为返回类型,其中0表示成功,负值表示错误。 - Matthieu M.
我们最终使用了 int,因为它在两种语言中都是明确定义的。 - Edd Barrett
抱歉,shepmaster,一位版主将我的编辑移动到了你的答案中。 - Edd Barrett
等等!是你啊! - Edd Barrett
@EddBarrett 不是管理员,是我 - Shepmaster
@EddBarrett的回答不应该出现在问题中。将答案放在问题中会破坏分别对它们进行赞和踩的能力。这也会让人们不愿意提供答案,因为如果答案已经在问题中了,你为什么还要给它点赞呢?如果您愿意,您也可以编辑下面的答案以反映当前的情况。 - Shepmaster

0
经过深思熟虑,我决定尝试回答自己的问题。如果您能找到以下推理中的漏洞,请评论告知。
这不是正确答案 - 请参见下面的评论。
我认为 Rust 的 u8 对于 T 总是安全的。
我们知道 C99 的布尔值是一个足以存储0或1的整数,这意味着它可以自由地成为至少1位无符号整数,或(如果你感觉怪异)至少2位有符号整数。
让我们按情况分析:
  1. 如果C99的bool是8位,则Rust的u8非常适合。即使在有符号的情况下,由于表示0和1从不需要负的二次幂,因此顶部位将为零。

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

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


2
你正在做关于表示和调用约定的假设,这些假设可能不适用于C和Rust支持的所有平台。例如,如果返回值是通过堆栈而不是寄存器传递的,则可能会抓取返回值的顶部字节而不是底部。 u8 极有可能工作(考虑到两种语言支持的平台),但它并不比仅使用bool更有保证。 - trent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接