Chrome中的Ruust与C++互操作性

2020-08-20 02:39:36

铬的工程师们正在试验生锈技术。在可预见的未来,C++是我们代码库中的霸主,Rust的任何使用都需要与C++相适应-而不是相反。这似乎带来了一些其他人从未遇到过的C++/Rust互操作性挑战。

在我们的代码库中将拉斯特视为(几乎)一等公民之前,我们需要解决这些问题。如果我们不能解决这些问题,Rust充其量只能被隔离到“叶子节点”,这些节点与我们代码库的其余部分不会有太多交互。如果这就是我们能用Rust做的全部事情,那就让人怀疑额外语言的成本是否合理。

因为C++是统治者,所以我们主要关注新的Rust代码调用现有C++代码的能力,而不是C++到Rust调用的能力。

我们认为Rust能够以满足以下标准的方式调用C++函数非常重要:

没有必要使用“不安全”关键字,除非已知某些东西比正常的C++不安全。对于Rustacean来说,这是有争议的-所有的C++都是不安全的!但是“不安全”应该是一种非常糟糕的代码气味。如果所有C++调用都需要“不安全”,那么就会有数千个这样的调用,“不安全”关键字将失去其意义。在Rust和C++之间简单地来回传递对象时,我们必须避免“不安全”这个词。它应该仅限于真正不安全的Rust代码的补丁,以及存在共享所有权或其他复杂性的C++互操作性代码。Dtolnay出色的CXX库已经满足了这一特殊属性。

一般情况下没有管理费用。LTO和跨语言内联原则上已经解决了这个问题。有些情况下,在C++边界需要开销-特别是,当字符串从C++传递到Rust时需要进行UTF检查。这可以通过在Rust代码中处理诸如&;[U8]之类的字符串来解决,直到真正需要字符串操作为止,所以我们在这里不需要任何进一步的创新。此框处于选中状态。

没有样板或重述。没有C++批注。理想情况下,没有允许列表。如果存在C++API,Rust应该能够调用它。就这么简单。C++中的声明应该足够了。应该不需要允许列表、Rust中的重新声明或任何Rust垫片。很少会出现异常(例如,重载函数),在某些情况下,我们会想要制作一个惯用的Rust包装器,但通常情况下,这应该不是必需的。这不仅仅是审美偏好。我们的代码库很复杂,用额外的注释来污染它会对每个人的工作方式征收一笔很小但很明显的税。

宽型支架-安全可靠。CXX是目前在C++和Rust之间安全交换数据的最新技术。我们的“基础”库公开了1768个API,供Chrome的其他部分使用。这些函数中的1052只接受CXX已经支持的类型的参数。近期计划为CXX再增加12个(例如,更灵活的切片)。这大约是我们API的60%,这是好的,但不是很好。如果我们能够将std::string和类似的字符串类型传递到现有的C++API中,还可以支持另外12%。由于内部指针的原因,这些不能在Rust结构中表示,但是当CXX在C++和Rust端生成代码时,应该可以在Rust端拥有UniquePtr<;CxxString>;,但将其传递到现有的C++API中,该API通过值接受std::string。(这听起来相当简单,但是当您谈到包含std::字符串的结构时,比如url::Origin,就会变得复杂得多。这样的结构只能作为来自Rust端的UniquePtr<;不透明类型>;拥有,这将阻止字段访问。解决方案可以想象,但需要更多思考。)。另外约20%是接受指针参数的函数-在我们的例子中,这些通常是输出参数。我们需要了解如何以编程方式识别那些“简单”的输出参数,并允许Rust安全地填充它们。好消息是,我们只剩下8%的功能不能被互操作的CXX模型支持。其中大多数都在传递C++结构(按值),其中包含原始指针。这似乎在很大程度上无法在铁锈中解决,但它们非常罕见,以至于我们可以创建逐个案例的习惯用法包装器。这里有一些注意事项:此分析基于二进制文件导出的符号,而不是源代码分析。在某些情况下,这些API将由内联函数、模板或宏包装,这一分析忽略了这些。它还忽略返回值和直接字段访问。当然,“base”并不是我们的代码需要调用的唯一一组API-可能是因为更高级别的函数平均而言会有更复杂的参数,所以不太可能落入“好”的桶中。

人体工程学-安全可靠。从Rust代码中,我们需要能够实例化C++对象,安全地传递所有权(这里CXX的UniquePtr没有重大问题),能够调用它们的方法(无论是普通的还是虚拟的)。对于包含简单的、与CXX兼容的字段的C++中的“普通旧数据”类型,我们需要能够操作这些字段。其中大部分已经可以通过CXX实现(尽管我们需要一种从Rust代码调用到std::Make_Unique的方法,对于对于Rust是不透明的类型)。我们需要这足够平滑,这样我们就不需要将典型的C++类型包装在铁锈包装器中。到现在为止还好。但我们还需要:(在Rust构建时)根据我们的C++头和构建时规则设置的#定义采取行动,制定调用C++重载函数和操作符的计划,调用宏(例如log(Error)<;<;“eek”),使模板化函数和类型可用(可能非常困难,尽管bindgen在这里做得非常好),可能还有许多我们还没有想到的事情。处理其中一些情况的最佳方法可能是在Rust中编写一些内联C++代码(类似于CPP机箱,但从CXX的安全性出发)。一个具体的挑战是引用计数的对象。我们需要在铁锈和铬方裁判之间共享引用计数。这里更大的挑战是如何处理C++端流行的多个可变引用,而不能执行像RefCell::borrow_mut这样的操作来确保运行时安全性。可能我们需要将所有这类引用计数的物体标记为对铁锈方真正“不安全”。一般来说,我们认为我们可以不使用继承自C++类型的Rust类型,但有一个例外:纯虚拟观察者。CXX提供了将函数指针从Rust传递到C++的能力,因此我们很有可能在这里创建包装器类型。不过,理想情况下,这会变得符合人体工程学,而且是无缝的。这里需要更多的调查。

我们相信我们可以没有:自引用C++类型通过值传递到Rust中(字符串除外),Rust类型继承自非纯虚拟C++类型;各种参数,“安全”引用计数。(有些情况下,缺少这些功能会很烦人,但希望很少。)。所有这些可能都是错的:我们还有很多东西要学。

我们认为最困难的部分是想象一种在Rust和C++之间传递类型的安全方式。这需要在Rust端和C++端都自动生成填充代码。CXX凭借出色的紧急安全特性已经实现了这一点。这就是我们的基本模型。

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

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

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