13个版本 (4个重大更改)

使用旧的Rust 2015

0.5.3 2016年12月2日
0.5.2 2016年11月20日
0.5.1 2016年10月28日
0.4.1 2016年10月23日
0.1.4 2016年9月13日

2462Rust模式

每月下载 37

MIT 许可证

530KB
13K SLoC

cpp_to_rust

Linux + OS X Windows

cpp_to_rust 自动创建Rust的crates,提供对C++库的API访问。

cpp_to_rust 支持许多语言功能,但一些重要功能尚未实现,因此它绝对不是生产就绪的(有关更多详细信息,请参阅本节)。

cpp_to_rust 主要针对Qt库,并使用一些Qt特定的解决方案,但它可能能够很好地处理其他C++库。一些Qt特定的功能已分离到qt_build_tools

工作原理

生成过程由构建脚本来执行。它包括以下步骤

  1. 如果一个库依赖于 cpp_to_rust,在它们的生成过程中收集的信息将被加载并用于进一步处理。
  2. 执行 clang C++解析器以从库的头文件中提取有关库的类型和方法的信息。
  3. 生成一个具有C兼容接口的C++包装库。库通过包装函数公开每个找到的方法。
  4. 生成crates的Rust代码。使用Rust的FFI支持在crates中提供来自C++包装库的函数。Rust代码还包含所有找到的C++枚举、结构体和类的 enumstruct(包括模板类实例化)。
  5. 构建脚本和 cargo 将所有内容组合成一个单独的crates。
  6. 使用C++库文档和 cpp_to_rust 的处理数据生成crates的全功能文档(示例)。

C++/Rust功能覆盖范围

许多东西直接从C++转换为Rust

  • 基本类型映射到Rust的基本类型(例如 bool)和libc crate提供的类型(例如 libc::c_int)。
  • 固定大小的数值类型(例如 int8_tqint8)映射到Rust的固定大小类型(例如 i8)。
  • 指针、引用和值映射到Rust的相应类型。
  • C++命名空间映射到Rust子模块。
  • C++类和结构体映射到Rust的结构体。这同样适用于在库API中遇到的模板类的所有实例,包括依赖项的模板类。
  • 自由函数映射到自由函数。
  • 类方法映射到结构体的实现。
  • 析构函数映射到 DropCppDeletable 实现。
  • 函数指针类型映射到Rust的等效表示。不支持具有引用或类值的函数指针。
  • QFlags<Enum> 类型转换为Rust自己的类似实现。
  • static_castdynamic_cast 通过相应的特性和Rust提供。

Rust标识符的名称根据Rust的命名约定进行修改。

当无法直接翻译时

  • C++库的每个头文件的内容被放置到一个单独的子模块中。
  • 方法重载通过将参数包装在元组中并创建一个描述每个方法可接受的元组的特性行为来模拟。具有默认参数的方法以相同的方式处理。
  • 从基类继承的方法被直接添加到派生类的包装结构体中。

尚未实现但计划实现

  • 将C++ typedef 转换为Rust类型别名。
  • 为基于C++运算符方法的结构体实现运算符特性(问题)。
  • 如果C++侧存在适用方法,则为结构体实现Debug和Display特性。
  • 为集合实现迭代器特性。
  • 信号和槽API(问题)。
  • 子类化API(问题)。
  • 提供对类公共变量的访问(问题)。
  • 提供枚举到int和回转的转换(用于Qt API)。
  • 支持嵌套在模板类型中的C++类型,如 Class1<T>::Class2

不计划支持

  • 高级模板使用,如具有整数模板参数的类型。
  • 模板部分特化。
  • 模板方法和函数。

平台支持

支持Linux、OS X和Windows。 cpp_to_rust 在以下平台和目标上持续进行测试

  • Ubuntu Trusty x64(稳定-x86_64-unknown-linux-gnu);
  • OS X 10.9.5(稳定-x86_64-apple-darwin);
  • Windows Server 2012 R2 Windows 7 x64 with MSVC 14(稳定-x86_64-pc-windows-msvc)。

依赖关系

  • 稳定的Rust ≥ 1.12。
  • libclang-dev ≥ 3.5(CI使用3.8和3.9)。
  • cmake ≥ 3.0。
  • make 和与使用的Rust工具链兼容的C++编译器。在OS X上,需要命令行开发者工具,但不需要完整的Xcode安装。
  • 要包装的C++库,用于与Rust相同的工具链构建。

如何使用

cpp_to_rust 生成的crates可以作为依赖项添加到您的项目中,就像其他任何库crates一样。我们维护以下crates

(最终所有 Qt 库都将被添加)。

如果您想在其他库上运行 cpp_to_rust,您需要 创建一个 crate设置构建脚本 并编写该构建脚本。示例构建脚本可在 test_assets/ctrt1/crate/build.rs 文件中找到。您也可以使用上面列出的 crate 作为示例。您还需要在 crate 的 lib.rs 文件中编写特定的 include 宏:例如,请参阅 test_assets/ctrt1/crate/src/lib.rs

文档

cpp_to_rust 的 API 仍然不稳定,肯定会发生很大变化。

环境变量

cpp_to_rust 读取以下环境变量

  • CLANG_SYSTEM_INCLUDE_PATH - clang 系统头文件的路径,例如 /usr/lib/llvm-3.8/lib/clang/3.8.0/include。如果 clang 找不到系统头文件,可能需要设置此变量。通过构建脚本将此目录添加为包含目录有不同的效果,可能不足以解决问题。
  • CPP_TO_RUST_CACHE - 缓存目录的路径。如果设置,cpp_to_rust 将使用此目录来缓存各种数据,如果已缓存,将跳过一些处理步骤。此目录可以并且应该是所有一起处理的 crate 的同一目录。确保在更改(例如,cpp_to_rust 的版本、工具链、C++ 库版本等)时清理或更改缓存目录。
  • CPP_TO_RUST_QUIET - 如果设置,将关闭警告和调试日志级别。
  • 为构建脚本设置的 Cargo 环境变量.

C++ 构建工具和链接器也可能读取其他环境变量,包括 LIBPATHLIBRARY_PATHLD_LIBRARY_PATHDYLD_FRAMEWORK_PATHcpp_to_rust 可能会向这些变量添加新目录,但它无法管理最终链接器的环境,因此您可能需要手动设置它们。

备注

表达库依赖

cpp_to_rust 利用 Rust 的 crate 系统。如果 C++ 库依赖于另一个 C++ 库,生成的 Rust crate 也将依赖于依赖项的 crate 并重用其类型。

文档生成

文档非常重要!cpp_to_rust 生成 rustdoc 注释,包含有关对应 C++ 类型和方法的信息。重载方法具有详细文档,列出了所有可用变体。Qt 文档集成在 rustdoc 注释中。

在堆栈和堆上分配 C++ 对象

cpp_to_rust 支持两种 C++ 对象的分配位置模式。用户可以通过将 AsBoxAsStruct 作为附加参数传递给构造函数和其他以值返回对象的 C++ 函数来选择模式。

AsBox 模式

  1. C++ 对象在 C++ 包装库中使用 new 创建。构造函数调用方式为 new MyClass(args),而返回对象的函数调用方式为 new MyClass(function(args))
  2. new 返回的指针通过 FFI 传递到 Rust 包装器和 CppBox::new。返回给调用者的 CppBox<T>
  3. CppBox 被丢弃时,它调用删除函数,该函数在 C++ 端调用 delete object
  4. 原始指针可以从 CppBox 中移出,并传递给另一个可以拥有该对象的所有权的函数。

AsStruct 模式

  1. 在栈上创建一个未初始化的 Rust 结构体。结构体的大小与 C++ 对象的大小相同。
  2. 将结构体的指针传递给使用 placement new 的 C++ 包装函数。构造函数调用方式为 new(buf) MyClass(args),其中 buf 是 Rust 中创建的结构体的指针。结构体被填充为有效数据。
  3. 调用者保留结构体的所有权,可以使用 BoxVec 将其移动到堆上,传递到其他地方,并获取其引用和指针。
  4. 当结构体被丢弃时,使用 C++ 包装函数调用 C++ 析构函数。结构体本身的内存由 Rust 管理。

AsBox 是在 Rust 中存储 C++ 对象的更通用、更安全的方式,但在处理多个小对象时可能会产生不必要的开销。

AsStruct 更有限且危险。如果对象的指针存储在某处,则不能使用它,因为 Rust 可以在内存中移动结构体,指针可能变得无效。有时也不清楚它们是否被存储。还禁止将这些结构体传递给接受所有权的函数,因为它们会尝试删除它并释放由 Rust 管理的内存。然而,AsStruct 允许避免堆分配,可以用于小型简单结构体和类。

选择这些模式目前由调用者决定,但未来可能会实现某种智能默认值和限制。

可移植性和 API 稳定性问题

假设 C++ 库在所有平台上都具有完全相同的 API,生成的 C++ 和 Rust 代码几乎完全可移植。唯一的问题是使用类的大小,因为它们依赖于平台。

然而,在现实中,C++ 库在不同平台上通常具有不同的 API。即使它们很微妙,也可能导致 Rust API 中的类型和方法更改,以及使用该 crate 的应用程序的后续构建问题。使用 C++ 库的另一个版本也会立即出现问题,因为 C++ 包装器使用库的每个可能的功能,如果缺少任何功能,则肯定无法构建。

在这些问题解决之前,生成 crate 的跨平台使用受到显著限制。一种可能的解决方案在 此处 描述。它将允许生成在任何支持的平台上工作的代码,甚至不需要安装 clang 解析器。

FFI 类型

C++ 包装函数的签名中只能包含与 C 兼容的类型,因此引用和类值被替换为指针,包装函数执行必要的转换到和从原始 C++ 类型。在 Rust 代码中,类型被转换回引用和值。

可执行文件大小

如果 Rust crate 和 C++ 包装库都构建为静态的,则链接器只为使用这些 crate 的最终可执行文件运行一次。它应该能够消除所有未使用的包装函数,并生成一个合理小的文件,它将只依赖于原始 C++ 库。

然而,由于MSVC的链接器无法一次性处理这么多函数,C++封装库目前是为MSVC动态构建的。这导致可执行文件大小增加,并增加了额外的DLL依赖。

贡献

我们始终欢迎贡献!您可以通过不同的方式做出贡献

  • 建议一个可适应的C++库;
  • 问题跟踪器提交错误报告、功能请求或改进建议;
  • cpp_to_rust本身或任何封装库编写测试;
  • 选择带有帮助所需标签或您喜欢的任何问题。

依赖关系

~17MB
~298K SLoC