I'm not proposing that the compiler would make decisions based on the body of push
, but based on some attribute or other marker on the declaration of push
. I know that's vague, but I wanted to leave open the space of possible designs.
Here's a strawman for a more specific design:
Add a new trait as an alternative for the Fn
trait for functions (or function-like objects) that want to allow constructing their arguments in place – call them "placer functions", and call the trait PlacerFn
. (There probably also need to be PlacerFnMut
and PlacerFnOnce
.) In the simplest case, implementations would manually impl this trait (XX: but how to make this work with method syntax?). However, there might be some potential to allow wrapper functions that delegate to placer functions to be written as normal functions with an attribute attached, as long as they satisfy certain conditions and have the compiler generate an impl automatically.
A placer function receives its arguments in stages, through a series of calls: that way, given a function like Vec::insert(&mut self, index: usize, element: T)
, the implementation can receive the index
before returning a place in which to construct the element
(so it could shift existing elements forward, and return a pointer to the newly vacated &vec[index]
). As it happens, when these kinds of functions have more than one argument, they usually take the value as their last argument, so we can simplify things by requiring arguments to be received in order from left to right; this also avoids messing with the order of evaluation on the caller side.
Here's a possible definition of the PlacerFnOnce
trait:
// These names all suck and should be bikeshedded
trait PlacerFnOnce<UpfrontArgs, PlacedArg, RemainingArgs> {
type Output;
type Place: Place<PlacedArg> + PlacerOrNormalFnOnce<RemainingArgs, Output=Output>;
extern "rust-call" fn provide_arguments(self, UpfrontArgs) -> Self::Place;
}
Whereas the Fn trait has one generic parameter, Args
, representing a tuple of all the arguments, this has three, splitting the argument list into three parts:
UpfrontArgs
is a tuple of any arguments that don't need to be constructed in place, but should be passed up front, by value – such as the index
parameter from Vec::insert
.
PlacedArg
is the argument that needs to be constructed in place.
RemainingArgs
is a tuple of the rest of the arguments, if any, which will be provided in a subsequent call. (There always needs to be a subsequent call, even if there are no more arguments: the call will also act as the equivalent of InPlace::finalize
, signaling that the in-place construction has completed successfully.)
[An alternative design could stick with a single Args
parameter and use associated types to determine how to split it into parts; this would help avoid ambiguity, but would make the trait definition look a bit more complicated.]
The return value of provide_arguments
must impl two traits: Place<PlacedArg>
, which is the same as the existing, soon-to-be-deprecated Place
trait (i.e. it provides a pointer to place the argument into), and PlacerOrNormalFnOnce
, for the subsequent call.
PlacerOrNormalFnOnce
is a trait that has blanket impls for types that impl either PlacerFnOnce
or the normal FnOnce
. (This can be achieved with specialization, but would require the lattice rule, or some other mechanism, to handle cases where someone tries to impl both PlacerFnOnce
and FnOnce
on the same type.) The compiler could also use this trait to decide which of PlacerFnOnce
or FnOnce
to use for a function call in the first place.
So the function call desugaring takes three steps:
- Call
provide_arguments
with the upfront arguments to obtain a Place
;
- Get the pointer from the
Place
and construct the argument in place;
- Do a recursive call on the
Place
object itself with the remaining arguments. (This is always done by-value through PlacerOrNormalFnOnce
, even if the original call was through PlacerFn
or PlacerFnMut
, so that the callee can take ownership of the Place
.)
If the Place
itself impls PlacerFnOnce
, that indicates that there's another argument somewhere in the list that needs to be constructed in place, so it goes through the whole process again. On the other hand, if the Place
impls normal FnOnce
, that's the end of the line: it will be called with the remaining arguments and produce the final function return value.
Similarly to the existing <-
behavior, if evaluation of an in-place argument diverges (i.e. it panics, executes break
or return
, etc.), then the Place
will be dropped instead of being passed by value into a method call; the Drop
impl should handle reverting the container to its previous state.
Also note that the PlacerFn*
traits would not be usable as trait objects, at least not unless you provided the Place
associated type up front as part of the trait object type. That's probably not the end of the world; I'd love to see an expansion of object safety in general, but that's a quite separate topic.
TL;DR: basically the same thing under the hood as the existing Placer
, but implicit in function calls.