| <!--* css: "//depot/docs/includes/featuretable.css" *--> | |
| # Limits of Crubit | |
| Crubit aims to make Rust a viable option for engineering teams with large, | |
| pre-existing C++ codebases. There are limits to how well C++ and Rust can | |
| interoperate. Tradeoffs still exist, and there can be tension between competing | |
| goals. And the tools we have for interop, including Crubit, are many years away | |
| from feature-completeness. | |
| Crubit tries to push the envelope with what is possible with C++/Rust interop, | |
| automatically supporting cross-language calls with the minimum of developer | |
| toil. Where this is not possible, Crubit makes it possible (and easy, in most | |
| cases) to work around the mismatch, with documented patterns for the most common | |
| deficiencies. | |
| ## High Level Features | |
| Below is an incomplete overview of features we expect Crubit to support | |
| eventually. (This attempts to cover major axes in which interfaces can present | |
| challenges for interop.) Included in the overview is the short term estimation | |
| of overall difficulty, and long-term confidence levels of feasibility for fully | |
| automatic interop. Note that some of these depend on features outside the direct | |
| control of the Crubit team, such as in the Rust language and compiler. | |
| We also describe, in abbreviated form, some of the patterns one can employ to | |
| work around limitations in Crubit. More detailed information is available in | |
| crubit.rs/cpp/cookbook and crubit.rs/errors. | |
| ### Functions | |
| <!-- mdformat off(wide table) --> | |
| | **Feature** | Expected Interop Effort (2027) | Will Crubit automate this (eventually)? | | |
| | :---------- | :----------------------------- | :-------------------------------------- | | |
| | Call into a non-templated C++ function from Rust, if annotated correctly. | [Light](#light){.😊} | [**Planned: 2027**](#ga){.ga} | | |
| | Call into a templated C++ function | | Depends, see [Generics and Templates](#generics). | | |
| | Call into a non-generic Rust function from C++. | [Fully automatic](#automatic){.😊} | [**Confidence: Very High**](#veryhigh){.😊} | | |
| | Call into a generic Rust function | | Depends, see [Generics and Templates](#generics). | | |
| <!-- mdformat on --> | |
| Most functions will be callable if Crubit is enabled, but it may require more or | |
| different code in the other language. A notable example is functions which | |
| return non-Rust-movable types, which must be invoked differently from Rust than | |
| functions which do not. Or, for Rust calling C++, while you can *call* any | |
| function that is designed to be looked up by | |
| [ADL](https://en.cppreference.com/w/cpp/language/adl.html), you cannot use ADL | |
| to do so. | |
| In the immediate future, there is a long tail of reasons that functions may | |
| *not* get bindings. For example, any unrecognized function attribute will cause | |
| the bindings to be disabled. For the foreseeable future, barring changes to | |
| Rust, we will also require annotations if a function is overloaded, to give it a | |
| unique Rust name. However, we expect unsupported parameter or return types to be | |
| the most common reason that a function does not receive bindings. These reasons | |
| are expected to go away over time, and we aim for something like 95%+ of | |
| non-templated functions receiving bindings by the end of 2027. | |
| <!-- TODO(jeanpierreda): gather statistics --> | |
| Fortunately, missing support for a function is easy to work around. Sometimes, | |
| this can be solved with a simple | |
| [Crubit annotation](http://crubit.rs/cpp/customizing) (such as | |
| crubit.rs/errors/unknown_attribute), or some other minor change to the function | |
| declaration. In the case of the type being unsupported, enabling Crubit on the | |
| target that defines that type will be a common fix. But, at worst, one can wrap | |
| it behind a new function which Crubit does support. The new function can use | |
| fewer attributes, fewer language features, hide unsupported types behind wrapper | |
| types (or void pointers), and so on. | |
| Using C++ as an example: | |
| ```c++ | |
| // This function will always get bindings | |
| inline void DoStuff(void* untyped_out, const void* untyped_in) { | |
| auto* out = reinterpret_cast<UnsupportedType*>(untyped_out); | |
| const auto* in = reinterpret_cast<const OtherUnsupportedType*>(untyped_in); | |
| // arbitrary C++ logic here | |
| out->UnsupportedMethod(*in); | |
| } | |
| ``` | |
| Any C++ feature or type that is not supported in the automatically generated | |
| Rust bindings can be hidden in this way. (crubit.rs/errors/unsupported_type | |
| describes other solutions for the unsupported type case.) | |
| A similar strategy can be employed for Rust. | |
| ### Non-templated C++ Types | |
| <!-- mdformat off(wide table) --> | |
| | **Feature** | Expected Interop Effort (2027) | Will Crubit automate this (eventually)? | | |
| | :---------- | :----------------------------- | :-------------------------------------- | | |
| | Pass any Rust-movable C++ type by pointer or by value, including inside Rust structs. | [Fully automatic](#automatic){.😊} | [**Planned: 2027**](#ga){.ga} | | |
| | Pass any non-Rust-movable C++ type by pointer, including inside Rust structs. | [Fully automatic](#automatic){.😊} | [**Planned: 2027**](#ga){.ga} | | |
| | Pass any non-Rust-movable C++ type by value, including inside Rust structs. | [Fully automatic](#automatic){.😊} | [**Planned: 2027**](#ga){.ga} | | |
| | A lightweight syntax can be used to perform C++ move construction or assignment | [Fully automatic](#automatic){.😊} | [**Planned: 2027**](#ga){.ga} | | |
| <!-- mdformat on --> | |
| C++ types are more complicated than Rust types, and can maintain invariants Rust | |
| does not. For example, they can enforce non-destructibility (via a deleted | |
| destructor), or automatically maintain pinnedness (via implicitly invoked copy | |
| and move constructors). These types will not be as easy to use from Rust, but we | |
| are aiming for near 100% coverage, so that functions can typically receive | |
| bindings. | |
| Bindings can be generated for a type, even if the fields or other subobjects | |
| inside of it do not have bindings. What matters are the high level properties: | |
| the type's layout/ABI, what operations it supports, and so on. So when a type is | |
| not supported by Crubit, it almost always works to wrap it in a new type. | |
| However, more than for functions, we want to make this necessary as little as | |
| possible. Types have a wide blast radius: if a type does not get bindings, then | |
| no function using that type gets bindings. It is more important to support all | |
| types than to support all functions, and we plan to entirely cover the space of | |
| types and fully characterize any cases where they cannot receive bindings. | |
| ### Non-Generic Rust Types | |
| <!-- mdformat off(wide table) --> | |
| | **Feature** | Expected Interop Effort (2027) | Will Crubit automate this (eventually)? | | |
| | :---------- | :----------------------------- | :-------------------------------------- | | |
| | Pass any non-generic Rust type by pointer or by value in C++. | [Fully automatic](#automatic){.😊} | [**Confidence: Very High**](#veryhigh){.😊} | | |
| <!-- mdformat on --> | |
| There are currently no known cases where we cannot in principle support using a | |
| (non-generic) Rust type from C++. | |
| ### Containers and Smart Pointers | |
| <!-- mdformat off(wide table) --> | |
| | **Feature** | Expected Interop Effort (2027) | Will Crubit automate this (eventually)? | | |
| | :---------- | :----------------------------- | :-------------------------------------- | | |
| | Pass all standard containers, smart pointers, and idiomatic vocabulary types between languages, with zero or constant-time overhead bridging between corresponding types. | [Medium](#medium){.😑} | [**Confidence: High**](#high){.😊} | | |
| <!-- mdformat on --> | |
| Every type gets some baseline level of support (and the wording around C++ types | |
| above applies), but these vocabulary types are valuable to special-case to | |
| receive a better Rust API. We do this by rewriting the types in pure Rust. For | |
| example, the C++ template class: | |
| ```c++ {.no-copy} | |
| template <typename T> struct Span {T* ptr; size_t len;}; | |
| ``` | |
| Has a Rust equivalent: | |
| ```rust {.no-copy} | |
| #[repr(C)] pub struct Span<T>(*mut T, usize); | |
| ``` | |
| If the layouts match, then these can be used interchangeably in most places, | |
| even with otherwise no template/generic support. | |
| However, there is a long tail of vocabulary types, and types in the standard | |
| library often have differing implementations, some of which have incompatible | |
| Rust semantics. (GCC's `std::string` is pinned, Clang's is Rust-movable.) | |
| We expect users of Crubit to gravitate towards a set of types that works well in | |
| both languages, especially on multiplatform codebases. | |
| ### Generics and Templates {#generics} | |
| <!-- mdformat off(wide table) --> | |
| | **Feature** | Expected Interop Effort (2027) | Will Crubit automate this (eventually)? | | |
| | :---------- | :----------------------------- | :-------------------------------------- | | |
| | Instantiate C++ class templates from Rust with **concrete** types and constants as template parameters. | [Fully automatic](#automatic){.😊} | [**Planned: 2027**](#ga){.ga} | | |
| | Instantiate C++ function templates from Rust with **concrete** types and constants as template parameters. | [Medium](#medium){.😑} | [**Confidence: Speculative**](#speculative){.🙁} | | |
| | Instantiate C++ function and class templates using types defined in the current crate. | [Heavy](#heavy){.🙁} | [**Confidence: Uncertain**](#uncertain){.🙁} | | |
| | Instantiate C++ function and class templates with **generic** type parameters. Checking of function calls will be deferred to monomorphization time. | [Heavy](#heavy){.🙁} | [**Confidence: Speculative**](#speculative){.🙁} | | |
| | Instantiate generic Rust types and functions from C++. | [Medium](#medium){.😑} | [**Confidence: Uncertain**](#uncertain){.🙁} | | |
| <!-- mdformat on --> | |
| Templates (and generics) are instantiated lazily on use, while Crubit is | |
| designed as an ahead-of-time interface/type transpiler. Ultimately, this | |
| approach hits its limit here, and a new design involving more directly hooking | |
| the compilers together will be necessary to fully support templates and generics | |
| in every circumstance. | |
| Crubit plans to (by the end of 2027) support fully instantiated templates or | |
| generic types: functions which return or accept a `Foo<Bar>`, where `Bar` is not | |
| a template type parameter. Other uses of templates will need to be decomposed | |
| into building blocks that can receive bindings, or else reimplemented in the | |
| other language (as we do with types like `Span`). | |
| ### Abstraction and interfaces | |
| <!-- mdformat off(wide table) --> | |
| | **Feature** | Expected Interop Effort (2027) | Will Crubit automate this (eventually)? | | |
| | :---------- | :----------------------------- | :-------------------------------------- | | |
| | Implement an interface defined by a C++ base class from Rust code. | [Heavy](#heavy){.🙁} | [**Confidence: High**](#high){.😊} | | |
| | Implement a trait defined in Rust from C++ statically. | [Heavy](#heavy){.🙁} | [**Confidence: High**](#high){.😊} | | |
| | Implement a dyn-compatible trait defined in Rust using an abstract base class and virtual dispatch in C++. | [Heavy](#heavy){.🙁} | [**Confidence: Uncertain**](#uncertain){.🙁} | | |
| <!-- mdformat on --> | |
| Rust and C++ have many features that are not just "a function can be called", or | |
| "a type can be used". The way in which you define or dispatch to functions can | |
| be a load bearing part of the interface, and doesn't always have a natural | |
| equivalent on the other side, and Crubit will not support all of these for some | |
| time yet. | |
| The recommended workaround, in most places, is to define a part of your logic in | |
| one language, and then call into the other. For example, write a derived class | |
| in C++, but implement all of its virtual methods by calling into Rust functions. | |
| There is an example of this in | |
| [crubit/examples/cpp/virtual/](https://github.com/google/crubit/tree/main/examples/cpp/virtual/). | |
| ### Safety | |
| <!-- mdformat off(wide table) --> | |
| | **Feature** | Expected Interop Effort (2027) | Will Crubit automate this (eventually)? | | |
| | :---------- | :----------------------------- | :-------------------------------------- | | |
| | Safely call C++ functions from Rust, with lifetime preconditions checked by the Rust borrow checker. | [Light](#light){.😊} (where possible) | Crubit gives x% of functions safe bindings | | |
| | Check lifetime constraints on Rust APIs used from C++. | [Fully automatic](#automatic){.😊} (where possible) | [**Confidence: Speculative**](#speculative){.🙁} | | |
| <!-- mdformat on --> | |
| Rust and C++ have very different stances on memory safety, but both languages do | |
| share a goal to, when reasonable in that language, make it safe to call given | |
| functions. In Rust, this is embedded in the type system. In C++, this often | |
| takes the form of implementation-specific attributes which the compiler can read | |
| to inform what warnings to give. | |
| It is possible to lose safety unless FFI correctly propagates the relevant | |
| information to the other side. And in Rust, incorrectly marking functions as | |
| unsafe can lead to safety fatigue, where most calls are trivially correct, and | |
| the few that aren't have insufficient review. | |
| ### Confidence Levels | |
| Confidence levels: | |
| <a id="ga" class="ga">**Planned: 2027**</a> We plan on finishing this before the | |
| end of 2027, as part of our next major milestone. | |
| <a id="veryhigh" class="😊">**Confidence: Very High**</a> We know we can achieve | |
| this. There may still be caveats for edge cases that require language or | |
| compiler work to fix. | |
| <a id="high" class="😊">**Confidence: High**</a> We think we can achieve this, | |
| but some of the details are not fully designed, and there is always a risk of | |
| "unknown unknowns". May depend on getting features upstream, but we are | |
| reasonably confident we can do so. | |
| <a id="uncertain" class="🙁">**Confidence: Uncertain**</a> There are substantial | |
| unknowns in the "how" that will take some dedicated engineering effort to | |
| resolve. We think there is a path that looks feasible. | |
| <a id="speculative" class="🙁">**Confidence: Speculative**</a> At a high level it | |
| seems like this should be possible, but there are major technical, social, or | |
| resourcing risks that could prevent it from happening. | |
| ### Expected Interop Effort (2027) | |
| <a id="automatic" class="😊">**Fully Automatic Interop**</a> Crubit must be | |
| enabled on targets. | |
| <a id="light" class="😊">**Light**</a> Some declarations need to be annotated. | |
| For example, overloaded functions must have a Rust name specified. | |
| <a id="medium" class="😑">**Medium**</a> A layer of FFI glue code is required, | |
| such as new function or type definitions. The patterns involved are | |
| straightforward, and documented. Expertise is helpful, but not required. | |
| <a id="heavy" class="🙁">**Heavy**</a> Multiple layers of ad-hoc FFI glue are | |
| required, even with Crubit, or else the task may require reimplementing | |
| significant chunks of code in the other language. Some expertise may be | |
| required. | |
| ## Irreconcilable differences | |
| ### Expressiveness | |
| C++ is fundamentally a more expressive language, in terms of accepted code | |
| patterns, than Rust. While Rust will continue to evolve and gain some forms of | |
| expressiveness, many of its strengths draw from its relatively limited | |
| expressiveness in certain areas. Therefore, differences between C++ and Rust | |
| will always exist, and show up in the source code. | |
| Examples of these include: | |
| * Safety annotations | |
| * Implicit invocation of user-defined constructors, conversion operators, and | |
| assignment operators | |
| * Overloading operators in C++ that are not overloadable in Rust | |
| * Text-based macro substitution | |
| Typically, this means that Rust code ends up more verbose than C++ code, though | |
| it can also require adding additional annotations to the C++ to better inform | |
| how to match C++'s very general semantics with Rust's more narrow interfaces. | |
| ### Diverging patterns | |
| Idiomatic patterns are not the same between the two languages, and will continue | |
| to diverge further over time. For example, many more objects are pinned in C++ | |
| than are pinned in Rust, and this will present an ongoing impedance mismatch. | |
| ### Ecosystem considerations | |
| The Rust and C++ languages are limited in how far they can evolve to accommodate | |
| interoperability. Within each community there are priorities besides interop at | |
| play. |