【问题标题】:What is the correct type for returning a C99 `bool` to Rust via the FFI?通过 FFI 将 C99 `bool` 返回给 Rust 的正确类型是什么?
【发布时间】:2017-12-07 22:45:52
【问题描述】:

我和一位同事一直在摸索如何通过 FFI 将 bool<stdbool.h>(又名 _Bool)返回给 Rust。

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

bool
myfunc(void) {
   ...
}

我们使用 extern C 块让 Rust 知道 myfunc

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

T 应该是什么具体类型?

Rust 在libc crate 中没有c_bool,如果你在互联网上搜索,你会发现人们讨论这个问题的各种 GitHub 问题和 RFC,但实际上并没有达成任何共识什么是正确和便携的:

据我所知:

  • C99 中bool 的大小未定义,除非它必须至少足够大以存储true (1) 和false (0)。换句话说,至少有一点长。
  • 甚至可以是one bit wide
  • 它的大小可能是ABI defined

This comment 建议如果 C99 bool 作为参数传入函数或作为返回值传出函数,并且 bool 小于 C @ 987654344@ 然后将其提升为与int 相同的大小。在这种情况下,我们可以告诉 Rust Tu32

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

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

【问题讨论】:

  • 在这种情况下,我们可以告诉 Rust Tu32 — 不,你不能因为同样的问题发生:C doesn't define the size of an int other than as a minimum of 16 bits
  • bool 中必须至少有 CHAR_BIT 位,因此至少有 8 个(从 CHAR_BIT >= 8 开始)。链接中的脚注是说boolwidth(定义为不包括填充位的术语)可能是1。
  • Shepmaster:哎呀,我的意思是说'c_int'而不是'u32'。这行得通吗?
  • @EddBarrett 肯定会更好,AFAICT,但 64 位的可能性仍然令人担忧。
  • 牧长:Hrm。我认为我从中得到的是,问题仅在 C99 布尔值大于“T”时才存在。那么接下来,不应该使用最小的无符号 Rust 整数类型是安全的,即 u8 吗?如果 C bool 较大,我们截断。如果它更小,例如1 位,大概这必须扩展为内存单元或寄存器可以寻址的最小整数:一个字节,与 u8 一致。这个推理有什么漏洞吗?

标签: type-conversion rust boolean ffi


【解决方案1】:

截至2018-02-01,Rust 的布尔大小为officially the same as C's _Bool

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


此答案的其余部分适用于官方决定之前的 Rust 版本

在 Rust 获得 libc::c_bool 之前,您会为 T 使用什么?为什么它对于所有可能大小的 C99 布尔值(>=1 位)都安全且可移植?

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

没错,尽管它可能很悲伤。为了 FFI 的目的,唯一真正安全的事情是将您的 bool 转换为已知的固定大小的整数类型,例如 u8。这意味着您需要在两侧编组它。


实际上,我会在我的 FFI 代码中继续使用 bool。正如人们所指出的,它神奇地排列在目前广泛使用的所有平台上。如果该语言决定使bool FFI 兼容,那么您就可以开始了。如果他们做出其他决定,如果他们没有引入 lint 让我们能够快速发现错误,我会非常感到惊讶。

另见:

【讨论】:

  • 我会注意到,出于 FFI 的目的,将自己限制为 C89 可能是有意义的,这比 C99 标准得多。使用 C99 几乎没有什么好处,因此不妨在可移植性方面获得额外优势。在 C89 中,通常的约定是使用 int(或 Rust 中的 libc::c_int)作为返回类型,0 表示成功,负值表示错误。
  • 我们最终使用了int,因为这在两种语言中都有很好的定义。
  • 抱歉 shepmaster,版主将我的编辑移到您的答案中。
  • 等等!是你!
  • @EddBarrett 不是版主,it was me and me
【解决方案2】:

经过深思熟虑,我将尝试回答我自己的问题。如果您能在以下推理中找到漏洞,请发表评论。

这不是正确答案——请参阅下面的 cmets

我认为 Rust u8 对于 T 来说总是安全的。

我们知道 C99 bool 是一个大到足以存储 0 或 1 的整数,这意味着它可以是至少 1 位的无符号整数,或者(如果你觉得奇怪的话)有符号整数至少 2 位。

让我们按情况分解:

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

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

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

【讨论】:

  • 您对表示和调用约定的假设可能不适用于 C 和 Rust 支持的所有平台。例如,如果返回值是在堆栈上而不是通过寄存器传递的,那么您最终可能会获取返回值的 top 字节而不是底部。 u8极有可能工作(考虑到两种语言都支持的平台),但仅使用 bool 并不能保证。
猜你喜欢
  • 2016-03-26
  • 2019-06-15
  • 2020-02-03
  • 2019-12-28
  • 1970-01-01
  • 2021-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多