This RFC was previously approved, but later withdrawn
For details see the summary comment.
Summary
-
Change placement-new syntax from:
box (<place-expr>) <expr>instead to:in <place-expr> { <block> }. -
Change
box <expr>to an overloaded operator that chooses its implementation based on the expected type. -
Use unstable traits in
core::opsfor both operators, so that libstd can provide support for the overloaded operators; the traits are unstable so that the language designers are free to revise the underlying protocol in the future post 1.0. -
Feature-gate the placement-
insyntax via the feature nameplacement_in_syntax. -
The overloaded
box <expr>will reuse thebox_syntaxfeature name.
(Note that <block> here denotes the interior of a block expression; i.e.:
<block> ::= [ <stmt> ';' | <item> ] * [ <expr> ]
This is the same sense in which the block nonterminal is used in the
reference manual.)
Motivation
Goal 1: We want to support an operation analogous to C++'s placement new, as discussed previously in Placement Box RFC PR 470.
Goal 2: We also would like to overload our box syntax so that more
types, such as Rc<T> and Arc<T> can gain the benefit of avoiding
intermediate copies (i.e. allowing expressions to install their result
value directly into the backing storage of the Rc<T> or Arc<T>
when it is created).
However, during discussion of Placement Box RFC PR 470, some things became clear:
-
Many syntaxes using the
inkeyword are superior tobox (<place-expr>) <expr>for the operation analogous to placement-new.The proposed
in-based syntax avoids ambiguities such as having to writebox () (<expr>)(orbox (alloc::HEAP) (<expr>)) when one wants to surround<expr>with parentheses. It allows the parser to provide clearer error messages when encounteringin <place-expr> <expr>(clearer compared to the previous situation withbox <place-expr> <expr>). -
It would be premature for Rust to commit to any particular protocol for supporting placement-
in. A number of participants in the discussion of Placement Box RFC PR 470 were unhappy with the baroque protocol, especially since it did not support DST and potential future language changes would allow the protocol proposed there to be significantly simplified.
Therefore, this RFC proposes a middle ground for 1.0: Support the
desired syntax, but do not provide stable support for end-user
implementations of the operators. The only stable ways to use the
overloaded box <expr> or in <place-expr> { <block> } operators will be in
tandem with types provided by the stdlib, such as Box<T>.
Detailed design
-
Add traits to
core::opsfor supporting the new operators. This RFC does not commit to any particular set of traits, since they are not currently meant to be implemented outside of the stdlib. (However, a demonstration of one working set of traits is given in Appendix A.)Any protocol that we adopt for the operators needs to properly handle panics; i.e.,
box <expr>must properly cleanup any intermediate state if<expr>panics during its evaluation, and likewise forin <place-expr> { <block> }(See Placement Box RFC PR 470 or Appendix A for discussion on ways to accomplish this.)
-
Change
box <expr>from built-in syntax (tightly integrated withBox<T>) into an overloaded-boxoperator that uses the expected return type to decide what kind of value to create. For example, ifRc<T>is extended with an implementation of the appropriate operator trait, thenlet x: = box format!;could be a legal way to create an
Rc<String>without having to invoke theRc::newfunction. This will be more efficient for building instances ofRc<T>whenTis a large type. (It is also arguably much cleaner syntax to read, regardless of the typeT.)Note that this change will require end-user code to no longer assume that
box <expr>always produces aBox<T>; such code will need to either add a type annotation e.g. sayingBox<_>, or will need to callBox::new(<expr>)instead of usingbox <expr>. -
Add support for parsing
in <place-expr> { <block> }as the basis for the placement operator.Remove support for
box (<place-expr>) <expr>from the parser.Make
in <place-expr> { <block> }an overloaded operator that uses the<place-expr>to determine what placement code to run.Note: when
<place-expr>is just an identifier,<place-expr> { <block> }is not parsed as a struct literal. We accomplish this via the same means that is used e.g. forifexpressions: we restrict<place-expr>to not include struct literals (see RFC 92).
-
The only stabilized implementation for the
box <expr>operator proposed by this RFC isBox<T>. The question of which other types should support integration withbox <expr>is a library design issue and needs to go through the conventions and library stabilization process.Similarly, this RFC does not propose any stabilized implementation for the
in <place-expr> { <block> }operator. (An obvious candidate forin <place-expr> { <block> }integration would be aVec::emplace_backmethod; but again, the choice of which such methods to add is a library design issue, beyond the scope of this RFC.)(A sample implementation illustrating how to support the operators on other types is given in Appendix A.)
-
Feature-gate the two syntaxes under separate feature identifiers, so that we have the option of removing the gate for one syntax without the other. (I.e. we already have much experience with non-overloaded
box <expr>, but we have nearly no experience with placement-inas described here).
Drawbacks
-
End-users might be annoyed that they cannot add implementations of the overloaded-
boxand placement-inoperators themselves. But such users who want to do such a thing will probably be using the nightly release channel, which will not have the same stability restrictions. -
The currently-implemented desugaring does not infer that in an expression like
box <expr> as Box<Trait>, the use ofbox <expr>should evaluate to someBox<_>. pnkfelix has found that this is due to a weakness in compiler itself (Rust PR 22012).Likewise, the currently-implemented desugaring does not interact well with the combination of type-inference and implicit coercions to trait objects. That is, when
box <expr>is used in a context like this:fn foo(Box<SomeTrait>) { ... } foo(box some_expr());the type inference system attempts to unify the type
Box<SomeTrait>with the return-type of::protocol::Boxed::finalize(place). This may also be due to weakness in the compiler, but that is not immediately obvious.Appendix B has a complete code snippet (using a desugaring much like the one found in the other appendix) that illustrates two cases of interest where this weakness arises.
Alternatives
-
We could keep the
box (<place-expr>) <expr>syntax. It is hard to see what the advantage of that is, unless (1.) we can identify many cases of types that benefit from supporting both overloaded-boxand placement-in, or unless (2.) we anticipate some integration withboxpattern syntax that would motivate using theboxkeyword for placement. -
We could use the
in (<place-expr>) <expr>syntax. An earlier version of this RFC used this alternative. It is easier to implement on the current code base, but I do not know of any other benefits. (Well, maybe parentheses are less "heavyweight" than curly-braces?) -
A number of other syntaxes for placement have been proposed in the past; see for example discussion on RFC PR 405 as well as the previous placement RFC.
The main constraints I want to meet are:
- Do not introduce ambiguity into the grammar for Rust
- Maintain left-to-right evaluation order (so the place should appear to the left of the value expression in the text).
But otherwise I am not particularly attached to any single syntax.
One particular alternative that might placate those who object to placement-
in'sbox-free form would be:box (in <place-expr>) <expr>.
- Do nothing. I.e. do not even accept an unstable libstd-only protocol
for placement-
inand overloaded-box. This would be okay, but unfortunate, since in the past some users have identified intermediate copies to be a source of inefficiency, and proper use ofbox <expr>and placement-incan help remove intermediate copies.
Unresolved questions
This RFC represents the current plan for box/in. However, in the
RFC discussion a number of questions arose, including possible
design alternatives that might render the in keyword unnecessary.
Before the work in this RFC can be unfeature-gated, these questions should
be satisfactorily resolved:
-
Can the type-inference and coercion system of the compiler be enriched to the point where overloaded
boxandinare seamlessly usable? Or are type-ascriptions unavoidable when supporting overloading?In particular, I am assuming here that some amount of current weakness cannot be blamed on any particular details of the sample desugaring.
(See Appendix B for example code showing weaknesses in
rustcof today.) -
Do we want to change the syntax for
in(place) expr/in place { expr }? -
Do we need
inat all, or can we replace it with some future possible feature such asDerefSetor&outetc? -
Do we want to improve the protocol in some way?
- Note that the protocol was specifically excluded from this RFC.
- Support for DST expressions such as
box [22, ..count](wherecountis a dynamic value)? - Protocol making use of more advanced language features?
Appendices
Appendix A: sample operator traits
The goal is to show that code like the following can be made to work in Rust today via appropriate desugarings and trait definitions.
To demonstrate the above, this appendix provides code that runs today; it demonstrates sample protocols for the proposed operators. (The entire code-block below should work when e.g. cut-and-paste into http::play.rust-lang.org )
// (hopefully unnecessary soon with RFC PR 769)
// The easiest way to illustrate the desugaring is by implementing
// it with macros. So, we will use the macro `in_` for placement-`in`
// and the macro `box_` for overloaded-`box`; you should read
// `in_!( (<place-expr>) <expr> )` as if it were `in <place-expr> { <expr> }`
// and
// `box_!( <expr> )` as if it were `box <expr>`.
// The two macros have been designed to both 1. work with current Rust
// syntax (which in some cases meant avoiding certain associated-item
// syntax that currently causes the compiler to ICE) and 2. infer the
// appropriate code to run based only on either `<place-expr>` (for
// placement-`in`) or on the expected result type (for
// overloaded-`box`).
// Note that while both desugarings are very similar, there are some
// slight differences. In particular, the placement-`in` desugaring
// uses `InPlace::finalize(place)`, which is a `finalize` method that
// is overloaded based on the `place` argument (the type of which is
// derived from the `<place-expr>` input); on the other hand, the
// overloaded-`box` desugaring uses `Boxed::finalize(place)`, which is
// a `finalize` method that is overloaded based on the expected return
// type. Thus, the determination of which `finalize` method to call is
// derived from different sources in the two desugarings.
// The above desugarings refer to traits in a `protocol` module; these
// are the traits that would be put into `std::ops`, and are given
// below.
// end of `mod protocol`
// Next, we need to see sample implementations of these traits.
// First, `Box<T>` needs to support overloaded-`box`: (Note that this
// is not the desired end implementation; e.g. the `BoxPlace`
// representation here is less efficient than it could be. This is
// just meant to illustrate that an implementation *can* be made;
// i.e. that the overloading *works*.)
//
// Also, just for kicks, I am throwing in `in HEAP { <block> }` support,
// though I do not think that needs to be part of the stable libstd.
;
// Second, it might be nice if `Rc<T>` supported overloaded-`box`.
//
// (Note again that this may not be the most efficient implementation;
// it is just meant to illustrate that an implementation *can* be
// made; i.e. that the overloading *works*.)
// Third, we want something to demonstrate placement-`in`. Let us use
// `Vec::emplace_back` for that:
// Okay, that's enough for us to actually demonstrate the syntax!
// Here's our `fn main`:
Appendix B: examples of interaction between desugaring, type-inference, and coercion
The following code works with the current version of box syntax in Rust, but needs some sort
of type annotation in Rust as it stands today for the desugaring of box to work out.
(The following code uses cfg attributes to make it easy to switch between slight variations
on the portions that expose the weakness.)
// NOTE: Scroll down to "START HERE"
// (Support traits and impls for examples below.)
Sized>
Sized> *mut T
// START HERE
pub type BoxFn<'a> = ;
// (This one assumes PR 22012 has landed)
You can pass --cfg duh_worksN and --cfg coerce_worksM for suitable
N and M to see them compile. Here is a transcript with those attempts,
including the cases where type-inference fails in the desugaring.
% rustc /tmp/foo6.rs --cfg duh_works1 --cfg coerce_works1
% rustc /tmp/foo6.rs --cfg duh_works1 --cfg coerce_works2
% rustc /tmp/foo6.rs --cfg duh_works2 --cfg coerce_works1
% rustc /tmp/foo6.rs --cfg duh_works1
/tmp/foo6.rs:10:25: 10:41 error: the trait `Place<F>` is not implemented for the type `BP<core::ops::Fn()>` [E0277]
/tmp/foo6.rs:10 let raw_place = ::Place::pointer(&mut place);
^~~~~~~~~~~~~~~~
/tmp/foo6.rs:7:1: 14:2 note: in expansion of box_!
/tmp/foo6.rs:37:64: 37:76 note: expansion site
/tmp/foo6.rs:9:25: 9:41 error: the trait `core::marker::Sized` is not implemented for the type `core::ops::Fn()` [E0277]
/tmp/foo6.rs:9 let mut place = ::BoxPlace::make();
^~~~~~~~~~~~~~~~
/tmp/foo6.rs:7:1: 14:2 note: in expansion of box_!
/tmp/foo6.rs:37:64: 37:76 note: expansion site
error: aborting due to 2 previous errors
% rustc /tmp/foo6.rs --cfg coerce_works1
/tmp/foo6.rs:10:25: 10:41 error: the trait `Place<[_; 0]>` is not implemented for the type `BP<[T]>` [E0277]
/tmp/foo6.rs:10 let raw_place = ::Place::pointer(&mut place);
^~~~~~~~~~~~~~~~
/tmp/foo6.rs:7:1: 14:2 note: in expansion of box_!
/tmp/foo6.rs:52:51: 52:64 note: expansion site
/tmp/foo6.rs:9:25: 9:41 error: the trait `core::marker::Sized` is not implemented for the type `[T]` [E0277]
/tmp/foo6.rs:9 let mut place = ::BoxPlace::make();
^~~~~~~~~~~~~~~~
/tmp/foo6.rs:7:1: 14:2 note: in expansion of box_!
/tmp/foo6.rs:52:51: 52:64 note: expansion site
error: aborting due to 2 previous errors
%
The point I want to get across is this: It looks like both of these cases can be worked around via explicit type ascription. Whether or not this is an acceptable cost is a reasonable question.
- Note that type ascription is especially annoying for the
fn duhcase, where one needs to keep the array-length encoded in the type consistent with the length of the array generated by the expression. This might motivate extending the use of wildcard_within type expressions to include wildcard constants, for use in the array length, i.e.:[T; _].
The fn coerce example comes from uses of the fn combine_structure function in the
libsyntax crate.
The fn duh example comes from the implementation of the Default
trait for Box<[T]>.
Both examples are instances of coercion; the fn coerce example is
trying to express a coercion of a Box<Type> to a Box<Trait>
(i.e. making a trait-object), and the fn duh example is trying to
express a coercion of a Box<[T; k]> (specifically [T; 0]) to a
Box<[T]>. Both are going from a pointer-to-sized to a
pointer-to-unsized.
(Maybe there is a way to handle both of these cases in a generic fashion; pnkfelix is not sufficiently familiar with how coercions currently interact with type-inference in the first place.)