< 返回版块

Mike Tang 发表于 2020-08-20 22:42

「翻译」张汉东 - 20200820

Chromium 项目: 待解决的 Rust 和 C++ 互操性 的问题

本文来自于 Chromium 项目团队的 Rust 实践心得。

Chrome工程师正在尝试Rust。 在可预见的将来,C ++是其代码库中的统治君主,Rust的任何使用都需要与C ++相适应,而不是相反。 这似乎提出了一些C ++ / Rust互操作性挑战,其他人都没有遇到过。

在 Rust 正式成为该项目的一等公民之前,需要解决一些 Rust 和 C++ 的互操性问题。如果不能解决这些问题,那么 Rust 就只能被隔离到 「与代码库交互性不高的叶节点」。因为 C++ 是代码库里的绝对统治者,所以该项目团队更关注 Rust 能否调用现有的 C++代码,而非反过来。

他们认为 Rust 必须满足下面的条件才能调用 C++函数:

  1. 除非已知某些东西不如正常的C ++安全,否则不需要“ unsafe”关键字。

对于Rustacean来说,这是有争议的地方就是,Rust 认为所有C ++都不安全! 但是“不安全”应该是一种非常不好的代码味道。 如果所有C ++调用都需要“不安全”,则将有成千上万次调用,“不安全”关键字将失去其含义。 如果对象只是在Rust和C ++之间来回传递,则必须避免使用“不安全”一词。 应该仅限于真正不安全的Rust代码补丁,以及共享所有权或其他复杂性的C ++互操作性代码。

dtolnay出色的cxx库已经满足了这个特殊的属性。

  1. 一般情况下没有开销。

LTO和跨语言内联原则上已经解决了这一问题。 在某些情况下,在C ++边界处需要开销-特别是在将字符串从C ++传递到Rust时,需要进行UTF检查。 这可以通过在Rust代码中处理&[u8]这样的字符串来解决,直到确实需要字符串操作为止,因此在这里我们不需要任何进一步的创新。

  1. 没有样板文件或重新声明。 没有C ++注释。 理想情况下,没有allowlist。

如果存在C ++ API,Rust应该可以调用它。 就这么简单。 用C ++声明就足够了。 不需要allowlist,Rust中的重新声明或任何Rust 封装。 罕见的异常会存在(例如,重载的函数),在某些情况下,我们希望创建一个惯用的Rust包装器,但总的来说,这不是必需的。

  1. 广泛的类型支持,并且要安全

cxx是用于在C ++和Rust之间安全地交换数据的最新技术。 我们的“base”库公开了1768个API,供Chrome其他部件使用。 这些功能中的1052仅接受cxx已经支持的类型的参数。 计划在短期内为cxx再增加12个(例如,更灵活的切片)。

这大约是我们API的60%,这很好,但不是很好。

如果我们能够将std :: string和类似的字符串类型传递到现有的C ++ API中,则可以再支持12%。 由于内部指针的原因,这些不能在Rust结构中表示,但是当cxx在C ++和Rust端都生成代码时,应该可以在Rust端拥有UniquePtr ,但将其传递给 现有的C ++ API,它按值接受std :: string。

(这听起来很简单,但是当您谈论包含std :: strings的结构(例如url :: Origin)时,它会变得更加复杂。从Rust角度来看,这种结构只能作为UniquePtr 拥有。 ,这将阻止现场访问。可以想象得到解决方案,但需要更多考虑。)

另外约20%是使用指针参数的函数-在我们的例子中,这些参数经常是out参数。 我们需要了解如何以编程方式识别出那些“简单”的参数,并允许Rust安全地填充它们。

好消息是,这只剩下8%的功能,而interop的cxx模型无法支持。 其中大多数都是传递的C ++结构(按值),其中包含原始指针。 这似乎在Rust中基本上是不可解决的,但是它们是如此罕见,以至于我们可以创建逐案的惯用包装器。

这里有一些警告:这种分析基于二进制文件导出的符号,而不是源代码分析。 在某些情况下,这些API会被内联函数,模板或宏包装,但此分析会忽略它们。 它还忽略返回值和直接字段访问。 当然,“基本”并不是我们的代码需要调用的唯一一组API-可能是更高级别的函数平均具有更复杂的参数,因此不太可能落入“好的”类别。

  1. 良好且安全的开发体验(Ergonomics)

从Rust代码开始,我们需要能够实例化C ++对象,安全地传递所有权(此处cxx的UniquePtr没有大问题),对其上的调用方法(plain and virtual)。 对于C ++中包含简单的,兼容cxx的字段的“plain old data”类型,我们需要能够操纵这些字段。 大多数都可以使用cxx来实现(尽管我们需要一种方法来从Rust代码中调用对std :: make_unique的类型,而该类型对于Rust是不透明的)。

我们需要它足够平滑,以至于不需要在Rust包装器中包装典型的C ++类型。

到目前为止,一切都很好。 但我们还需要:在C ++标头和构建时规则设置的#define上(在Rust构建时)采取行动,制定调用C ++重载函数和运算符的计划,调用宏(例如LOG(ERROR)<< “ eek”),使模板化的函数和类型可用(可能非常困难,尽管bindgen在这里做得非常好),可能还有许多我们尚未想到的事情。

处理其中某些情况的最佳方法可能是Rust内嵌了一些内联C ++代码(例如cpp crate,但受益于cxx的安全性)。

一个具体的挑战是引用计数对象。 我们需要引用计数在Rust和Chrome方面的裁判之间共享。 这里最大的挑战是如何处理C ++端多个可变引用的普遍性,而又无法执行RefCell :: borrow_mut之类的操作来确保运行时安全。 从Rust的角度来看,我们可能需要将与所有此类引用计数对象的参与标记为真正的“unsafe”。

总的来说,我们认为可以没有Rust类型继承自C ++类型,但是有一个例外:pure virtual observers。 cxx提供了将函数指针从Rust传递到C ++的功能,因此我们很有可能在此处创建包装器类型。 但是,理想情况下,这也能提升开发体验。 这里需要更多调查。

  1. 我们不需要的

我们相信,我们可以避免以下情况:将自引用C ++类型通过值传递给Rust(字符串除外),Rust类型从非纯虚拟C ++类型继承; 可变参数,“安全”引用计数。 (在某些情况下,缺少这些功能会令人烦恼,但希望很少见。)这一切可能是错误的:我们仍有很多事情要做。

我们的计划

我们认为,最困难的部分是想像一种安全的方法来在Rust和C ++之间传递类型。 这需要在Rust和C ++方面自动生成的填充代码。 cxx具有出色的紧急安全性能,已经实现了这一目标。 这就是我们的基本模型。

但是,我们不想为每个API指定一个cxx :: bridge部分。 因此,我们需要使用类似bindgen的工具生成cxx :: bridge。

我们认为不需要更改Rust语言。 某些C ++类型不能由Rust中的值所拥有,例如带有自引用指针的std :: string,但我们相信,即使Rust只能通过指针拥有此类对象,也可以实现良好的C ++互操作性。 我们在这里也可能错了!

目前,Chrome对Rust的投资仍将是一项背景调查(主要针对这些工具和技术的原型制作)。 如果我们确信这种 互操作性是可能的,那么我们将重新考虑Rust在Chrome中的广泛使用,到那时,我们计划努力通过强大的生产质量解决方案来实现这一目标。

期待 Chromium 团队能进一步帮助完善 CXX,解决Rust和C++互操的问题,毕竟,能更方便的复用现有C++代码,对 Rust 发展来说也是很重要的一环。

Reddit 讨论:https://www.reddit.com/r/rust/comments/icpdfm/rust_and_c_interoperability_the_chromium_projects/?utm_medium=android_app&utm_source=share 原文: https://www.chromium.org/Home/chromium-security/memory-safety/rust-and-c-interoperability

评论区

写评论

还没有评论

1 共 0 条评论, 1 页