Skip to content

Commit 6ba62b6

Browse files
committed
finish item35
1 parent 5f77224 commit 6ba62b6

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@
3333
- [第 28 条:在合适的时候使用宏](./chapter_5/item28-use-macros-judiciously.md)
3434
- [第 29 条:遵循 Clippy 的提示](./chapter_5/item29-listen-to-clippy.md)
3535
- [超出 Rust 标准](./chapter_6.md)
36-
36+
- [第 35 条:优先使用bindgen而不是手动FFI映射](./chapter_6/item35-bindgen.md)
3737
[后记](./afterword.md)

src/chapter_6/item35-bindgen.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# 第 35 条:优先使用bindgen而不是手动FFI映射
2+
3+
[第 34 条]讨论了从 Rust 程序调用 C 代码的机制,描述了 C 结构和函数的声明需要有一个等效的 Rust 声明,以允许它们通过 FFI 使用。C 和 Rust 的声明需要保持同步,并且[第 34 条]还警告说工具链不会帮助解决这个问题 —— 不匹配将会被默默忽略,隐藏以后会出现的问题。
4+
5+
让两件事情完全同步听起来像是自动化的好目标,Rust 项目为此提供了正确的工具:bindgen。bindgen 的主要功能是解析 C 头文件并生成相应的 Rust 声明。
6+
[第 34 条]中的一些 C 声明为例:
7+
8+
```c
9+
/* File lib.h */
10+
#include <stdint.h>
11+
12+
typedef struct {
13+
uint8_t byte;
14+
uint32_t integer;
15+
} FfiStruct;
16+
17+
int add(int x, int y);
18+
uint32_t add32(uint32_t x, uint32_t y);
19+
```
20+
21+
`bindgen` 工具可以手动调用(或通过 `build.rs` [构建脚本]调用)以创建相应的 Rust 文件:
22+
23+
```bash
24+
% bindgen --no-layout-tests \
25+
--allowlist-function="add.*" \
26+
--allowlist-type=FfiStruct \
27+
-o src/generated.rs \
28+
lib.h
29+
```
30+
31+
生成的 Rust 代码与[第 34 条]中手工编写的声明是完全相同的:
32+
33+
```rust
34+
/* automatically generated by rust-bindgen 0.59.2 */
35+
36+
#[repr(C)]
37+
#[derive(Debug, Copy, Clone)]
38+
pub struct FfiStruct {
39+
pub byte: u8,
40+
pub integer: u32,
41+
}
42+
extern "C" {
43+
pub fn add(
44+
x: ::std::os::raw::c_int,
45+
y: ::std::os::raw::c_int,
46+
) -> ::std::os::raw::c_int;
47+
}
48+
extern "C" {
49+
pub fn add32(x: u32, y: u32) -> u32;
50+
}
51+
```
52+
53+
并且可以通过源码级别的 [`include!`]引入到 Rust 代码中:
54+
55+
```rust
56+
// Include the auto-generated Rust declarations.
57+
include!("generated.rs");
58+
```
59+
60+
对于任何非最简单的 FFI 声明,请使用 bindgen 为 C 代码生成 Rust 绑定 —— 在这个领域,机器制作的大规模生产代码绝对优于手工精制的声明。如果 C 函数定义发生变化,C 编译器会在 C 声明不再与 C 定义匹配时发出抱怨,但不会有东西抱怨手工编写的 Rust 声明不再与 C 声明匹配;从 C 声明自动生成 Rust 声明可以确保两者保持同步。
61+
62+
这也意味着 bindgen 步骤是理想的候选者,可以包含在 CI 系统([第 32 条])中;如果生成的代码包含在源码控制中,CI 系统可以在新生成的文件与检入的版本不匹配时报错。
63+
64+
当您处理具有大量 API 的现有 C 代码库时,bindgen 工具才能真正发挥其作用。为一个庞大的 `lib_api.h` 头文件创建 Rust 等价物是手动且乏味的,因此容易出错 —— 并且如前所述,许多不匹配错误的类别不会被工具链检测到。bindgen 还拥有一系列选项,允许针对 API 的特定子集(比如,之前展示的 -- `allowlist-function``--allowlist-type` 选项)。
65+
66+
这也允许采用分层方法在 Rust 中暴露现有的 C 库;包装某个 xyzzy 库的一个常见约定是拥有以下内容:
67+
* 一个仅包含 bindgen 生成的代码的 `xyzzy-sys crate` —— 其使用必然是不安全的。
68+
* 一个 `xyzzy crate`,它封装了不安全代码,并提供对底层功能的安全 Rust 访问。
69+
70+
这将在一个层中集中不安全代码,并允许程序的其他部分遵循[第 16 条]的建议。
71+
72+
### 超越C语言
73+
74+
`bindgen` 工具能够处理一些 `C++` 结构,但只是有限的一部分,并且方式有限。为了更好的(尽管仍然有限)集成,可以考虑使用 `cxx crate` 进行 `C++/Rust` 交互操作。`cxx` 不是从 `C++` 声明生成 `Rust` 代码,而是采用从公共模式自动生成 `Rust``C++` 代码的方法,允许更紧密的集成。
75+
76+
#### 注释
77+
[^1]: 示例还使用了 `--no-layout-tests` 选项以保持输出简单;默认情况下,生成的代码将包含 `#[test]` 代码,以检查结构体确实正确布局。
78+
79+
80+
81+
<!-- 参考链接 -->
82+
[第 16 条]: ../chapter_3/item16-unsafe.md
83+
[第 32 条]: https://www.lurklurk.org/effective-rust/ci.html
84+
[第 34 条]: [item5-casts.md](https://www.lurklurk.org/effective-rust/ffi.html)
85+
[构建脚本]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
86+
[`include!` 宏]: https://doc.rust-lang.org/std/macro.include.html

0 commit comments

Comments
 (0)