Description
We need to come up with a final design for how the box
operator should work.
Here is what I personally think would be ideal; unfortunately it involves features which are not yet part of the language.
// just to avoid a name clash with existing `Box`, otherwise the name is up for grabs
trait MakeBox {
type Where = ();
fn make_box<'s, T>(self: &'s out Self<T>, wherein: Where) -> &'s out T;
}
The box
operator would desugar to a call to make_box
(see below). If a value-level argument is required to specify where the box should be allocated, such as for arenas (i.e. box(here) val
syntax), its type is specified by the Where
associated type. Most of the time this defaults to ()
. MakeBox
itself is a higher-kinded trait, implemented for the generic box type itself. (So if you have impl MakeBox for SomeBox
, val: T
, then e.g. box val: SomeBox<T>
.)
make_box
relies on &out
references such as described by RFC PR 98 (see also the comments), which is a reference type which points to an uninitialized value and represents the obligation to initialize it. make_box
takes as argument the obligation to initialize a box, e.g. a SomeBox<T>
, allocates the box itself, and returns to the caller the obligation to initialize the contained value.
We would have impl
s such as:
impl MakeBox for Box { ... }
impl MakeBox for Rc { ... }
impl MakeBox for Gc { ... }
struct Arena { ... }
struct ArenaRef<'s, T> { ... }
impl<'s> MakeBox for ArenaRef<'s, ..> { // partial type application
type Where = &'s mut Arena;
...
}
Given an expression:
box(here) foo
- The type of the resulting box is determined by inference (as with e.g.
from_string()
). - The type of the expression is
SomeBox<Foo>
, wherefoo: Foo
,SomeBox: MakeBox
, andhere: <SomeBox as MakeBox>::Where
. - If
here
is omitted, it defaults to()
, sobox foo
becomesbox(()) foo
. - If it is unconstrained, the box type could potentially default to
Box
. - To specify the desired box type manually, when it can't be inferred from the context, use type ascription and a type wildcard. Currently, this means e.g.
let rc_foo: Rc<_> = box foo;
. When we gain general type ascription syntax, it could instead bebox foo: Rc<_>
. (In each case the result type isRc<Foo>
.)
This latter point is why it is important for the trait to be higher-kinded. If the trait is higher-kinded, it is sufficient to annotate the outer type (the box type), and the type argument can be inferred, because it is the same as the type of the value being box
ed. If MakeBox
were formulated using an associated type instead, as e.g. Deref
, then the type ascription would be required to specify the full type, including the type of the contained value.
Finally, given the above expression:
box(here) foo
This would desugar to:
{
let the_box;
*the_box.make_box(here) = foo;
the_box
}
The second line initializes the_box
using make_box
, and then initializes the contained value with foo
. (foo
here is an arbitrary expression, not necessarily a variable.)