Motivation
RVO/DPS is an optimization that avoids allocating unneeded stack temporaries in assignments. For example, in the C code
#include <stdlib.h>
struct big { int data[1<<30]; };
void poke(struct big *b);
void foo() {
struct big *s = malloc(sizeof(*s));
*s = (struct big){{[163]=42}};
poke(s);
}
translates into this assembly code:
pushq %rbx
movabsq $4294967296, %rdi
call malloc
movabsq $4294967296, %rdx
movq %rax, %rbx
movq %rax, %rdi
xorl %esi, %esi
call memset
movl $42, 652(%rbx)
movq %rbx, %rdi
popq %rbx
jmp poke
and no 4GB temporary struct big
is allocated on the stack. As this optimization can reduce the required stack size by arbitrary amounts, it has a significant semantic effect (similarly to tail call optimization).
However, RVO/DPS-translation in Rust is currently relatively fragile and occurs only in restricted circumstances. The in/box
proposal has a higher-level interface to it, but lacks a low-level interface (pnkfelix’s draft implementation access it via a hack). It would be desirable to have a decent low-level interface.
The main reason for the fragility of RVO/DPS is that the destination must not be aliased during the expression’s evaluation - this makes it not work in the *p = φ
case, as raw pointers have no aliasing information.
Detailed Design
Add the become-assignment expression.
Syntax
expr |= '*' 'become' expr '=' expr { ExprBecome(Expr, Expr) }
Typing
forall type T, expr DEST, expr EXPR.
DEST: *mut T, EXPR: T, T: Sized
------------------------------------
*become DEST = EXPR : ()
Semantics
become-assignment assigns the value of its expression to its destination without running destructors on the destination. The memory pointed-to by the destination is considered to be borrowed mutably during the evaluation of the expression (i.e. touching it will cause UB). become-assignment should be translate the expression in DPS style to the destination, and in any case must not allocate large temporaries.
Of course, become-assignment consumes the result of its expression, and is unsafe as it writes to a raw pointer.
Alternatives
We may want to introduce become-assignment for (safe) mutable references (in that case, the mutable borrow of the destination should of course be tracked by borrowck). In that case, we do have to run the destructors on the destination before the expression is evaluated, which would be inconsistent with the raw-pointer version. Also, it is unclear that this would be useful (users, this is your opportunity).
Drawbacks
Increased complexity.
Unresolved Questions
Syntax bikeshedding. Do we want to allow the &mut
form?