For each feature we use, we document the following:
supported
, then the Rust feature breaking can break Crubit users in general. If it is under experimental
, then it only breaks tests and close partner teams.custom_inner_attributes
supported
rustfmt
.rustfmt
on the golden tests some other way.negative_impls
supported
ctor
/ nontrivial intialization, so that we can dispatch on the existence of the Unpin
trait (which is not possible with PhantomPinned
). Also used so that we can pin/unpin a type without adding a field (which is not possible with PhantomPinned
).!Send
, !Unpin
, etc.vec_into_raw_parts
experimental
extern_types
experimental
forward_declare
crate except for migration from existing C++ code to Rust, where it relies on forward declarations for build performance or cycle-breaking reasons.arbitrary_self_types
experimental
never_type
experimental
c_variadic
experimental
abi_vectorcall
experimental
impl_trait_in_assoc_type
experimental
impl Ctor
for nontrivial construction.allocator_api
std::vector
that can reuse the C++ allocator.cfg_sanitize
std::vector
that poisons memory in the same way as libc++.cfg_accessible
, stmt_expr_attributes
, proc_macro_hygiene
cfg_accessible
makes it much easier to write code which is compatible with two versions of rustc at the same time. stmt_expr_attributes
, proc_macro_hygiene
lets us handle backwards-incompatible changes affecting individual expressions.cfg_accessible
changes during the very same update we are trying to be compatible with, we can't use it in the first place. If it changes in a future version, then the update has already completed successfully, so we can delete the usage (keeping the cfg_accessible
usage that is currently used, and deleting the other one). Same for the rest.unsized_const_params
supported
Symbol<(C<'F'>, C<'o'>, C<'o'>)>
is so substantially worse than Symbol<"Foo">
that Rust will even hide the type entirely, forcing you to read the name from a separate file (which may not be available to you if you were doing a remote build).Symbol<(...)>
approach works and is tested, and we can switch back with a single flag flip. The only impact is a regression on error messages.The following features are ones we'd hypothetically like to use, but do not.
try_trait_v2
Crubit does aim to support the Abseil error type, absl::Status
, which ideally would be usable everywhere a Result
is for ergonomics. For example, we'd expect you to be able to write code like the following:
pub fn read() -> Status { foo()?; }
Where foo
returns a Status
, or even an io::Result
.
For this to work with Status
being exactly the same type as it is in C++, with the same layout, we need to use try_trait_v2
.
Until it is stabilized, or the cost/benefit becomes worthwhile, we can work around it by using Result
, and converting when a Result
is passed or returned by value.
fn_traits
Much like try_traits_v2
, we'd like to support C++ “function objects” which implement the call operator. In Rust, without fn_traits
, the API is awkward: given a foo
of type std::function
, absl::AnyInvocable
, or absl::FunctionRef
, you cannot just write foo()
to call it, because it cannot implement the Fn*
family of traits. This makes these types less convenient to use than they are in C++, requiring something like foo.as_closure()()
to create a closure out of a FunctionRef
or similar.
(Another use case we've examined in the past: in addition to implementing function objects, fn_traits
could also be used to implement overloading, including overloaded function objects. For example, an overloaded function could, instead, be a constant with many different implementations of Fn
, for different parameter types. Though, this ends up looking a bit odd: (mystruct.overloaded)()
)
min_specialization
We have many use cases for min_specialization
, including bindings for C++ templates that have explicit template specialization or partial template specialization. However, they are not yet pressing, and specialization related Rust features are especially risky.
inherent_associated_types
Inherent associated types give a natural Rust spelling of the following C++ type definition:
struct Foo { using MyAlias = int; };
disjoint_associated_types
disjoint_associated_types
was closed, but not necessarily permanently. See also https://github.com/rust-lang/rust/issues/20400.
Not every C++ type supports the Rust move operation. For more on this, see Classes and Structs.
Crubit (in experimental
mode) supports passing and returning these non-Rust-movable C++ objects by value. But since they are not Rust-movable, they cannot literally be returned in Rust by value: pub fn foo() -> X
performs a Rust move of its return type by definition.
Instead, these objects support lazy construction, in the same style as moveit
. See ctor.rs
. This results in, for example, the following API differences:
X is rust-movable | X is not rust-movable |
---|---|
pub fn foo() -> X | pub fn foo() -> impl Ctor<Output=X> |
impl Add<X> for &C | impl<T: Ctor<Output=X>> Add<T> for &C |
The problem comes in with operator overloading: the following is valid:
impl Add<X> for &C {...} impl Add<Y> for &C {...}
But the equivalent using this lazy-construction trait is not:
impl<T: Ctor<Output=X>> Add<T> for &C {...} impl<T: Ctor<Output=Y>> Add<T> for &C {...}
Rust doesn't know that these two are disjoint, meaning that we cannot use the Ctor
trait approach for traits.
An alternative fix would be language support for in-place pinned construction. That would render the Ctor
trait obsolete, and reduce Crubit's needs around trait coherence (as well as negative_impls
, above).
register_tool
We previously used this attribute to allow Crubit to read annotations on types/functions. However, the need for end-users to enable a feature gate and re-declare the tool caused us to migrate away to using #[doc]
attributes.
We use custom attributes so that Crubit can round-trip a type correctly, or to implement automated bridging so that a C++ Status
becomes a Rust Result<(), StatusError>
, or what have you.
super_let
Crubit's ctor.rs
crate uses a syntax like emplace!{let x = ...}
to in-place initialize a pinned value using a C++ constructor. It cannot use the same trick that pin!()
does, because it needs to actually call a function to initialize the memory, and so lifetime extension would not apply: the memory, being passed to a function, would have its lifetime end at the end of the statement.
super_let
would allow us to instead write let x = emplace!(...)
, with the macro expanding to a use of super
to lifetime-extend the storage subexpression.
For example:
macro_rules! emplace { ($expr:expr) => { super($crate::Slot::unsafe_new()) .unsafe_construct($expr) .unsafe_as_pin_unchecked() }; }
Until super_let
is stabilized, the outer emplace!{}
works OK, it's just unexpected and causes code formatting problems.