blob: 2e8efd4f19ee975b88a757f0325429d37ca0c609 [file] [log] [blame] [view]
# Unstable Features
## Unstable features used by Crubit {#accepted}
For each feature we use, we document the following:
* **Crubit feature:** Under which Crubit feature flags do we use this? If it
is under `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.
* **Use case:** What do we use the feature for?
* **Exit strategy:** What would happen if the feature went away?
### `custom_inner_attributes`
* **Crubit feature:** `supported`
* **Use case:** Used to suppress automatic formatting, to make our golden
tests stable even in the face of e.g. non-idempotency bugs in `rustfmt`.
* **Exit strategy:** Disable `rustfmt` on the golden tests some other way.
### `negative_impls`
* **Crubit feature:** `supported`
* **Use case:** Used to implement `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`).
* **Exit strategy:** For dispatch, we can define a new auto trait (also an
unstable feature) or use specialization (also an unstable feature). For the
fields, this is a compatibility break, and we'd need to add a PhantomData
field to all C++ types to mark them as `!Send`, `!Unpin`, etc.
### `vec_into_raw_parts`
* **Crubit feature:** `experimental`
* **Use case:** Used for conversions of vectors of forward-declared
objects, which is not yet released.
* **Exit strategy:** We could delete this if we had to. Hopefully low-risk,
people love raw parts.
### `extern_types`
* **Crubit feature:** `experimental`
* **Use case:** Used for forward declarations, which have an unknown size.
* **Exit strategy:** Hard to avoid: without this, we get UB for forward
declarations if you use references, due to provenance rules. Don't need the
`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`
* **Crubit feature:** `experimental`
* **Use case:** We need this or an equivalent feature in order to make methods
callable on rvalue references but not regular const references, so as to
support C++ move semantics. Also likely need it for aliasing-safe
references.
* **Exit strategy:** Likely can't live without this feature. Lowish risk, in
that Rust needs user-defined types to be usable as self types (e.g. it
already has this for Rc), although the exact mechanism is as-yet undecided.
### `never_type`
* **Crubit feature:** `experimental`
* **Use case:** Used in a test to demonstrate we don't support it.
* **Exit strategy:** Delete test.
### `c_variadic`
* **Crubit feature:** `experimental`
* **Use case:** Used in a test to demonstrate we don't support it.
* **Exit strategy:** Delete test.
### `abi_vectorcall`
* **Crubit feature:** `experimental`
* **Use case:** Used in a test to test non-C calling conventions.
* **Exit strategy:** Delete test at worst, replace with different non-C
calling convention at best.
### `impl_trait_in_assoc_type`
* **Crubit feature:** `experimental`
* **Use case:** Used for returning `impl Ctor` for nontrivial construction.
* **Exit strategy:** Replace with
[RPITIT](https://github.com/rust-lang/rfcs/pull/3425), which is stable now.
### `allocator_api`
* **Crubit feature:** unreleased
* **Use case:** Used for an ABI-compatible implementation of `std::vector`
that can reuse the C++ allocator.
* **Exit strategy:** Do not release, or release but require that the Rust
global allocator *is* the C++ allocator.
### `cfg_sanitize`
* **Crubit feature:** unreleased
* **Use case:** Used for an ABI-compatible implementation of `std::vector`
that poisons memory in the same way as libc++.
* **Exit strategy:** Do not release, or release but with degraded
AddressSanitizer results that hide bugs.
### `cfg_accessible`, `stmt_expr_attributes`, `proc_macro_hygiene`
* **Crubit feature:** N/A, used internally by Crubit
* **Use case:** Crubit uses rustc as a library, which is subject to
backwards-incompatible changes. `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.
* **Exit strategy:** If `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`
* **Crubit feature:** `supported`
* **Use case:** Better error messages when you get a type error on forward
declarations. `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).
* **Exit strategy:** The `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.
## Unstable features **not** used by Crubit {#rejected}
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:
```rust
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:
```c++
struct Foo {
using MyAlias = int;
};
```
### `disjoint_associated_types`
[`disjoint_associated_types`](https://github.com/rust-lang/rfcs/pull/1672) 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](../cpp/classes_and_structs#trivially_relocatable).
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`](https://mcyoung.xyz/2021/04/26/move-ctors/). See
[`ctor.rs`](https://github.com/google/crubit/blob/main/support/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:
```rust
impl Add<X> for &C {...}
impl Add<Y> for &C {...}
```
But the equivalent using this lazy-construction trait is not:
```rust
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:
```rust
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.