Code change:
self.as_operand(block, local_scope, expr) } <ins> /// Returns an operand suitable for use until the end of the current scope expression and /// suitable also to be passed as function arguments. /// /// The operand returned from this function will *not be valid* after an ExprKind::Scope is /// passed, so please do *not* return it from functions to avoid bad miscompiles.  Returns an /// operand suitable for use as a call argument. This is almost always equivalent to /// `as_operand`, except for the particular case of passing values of (potentially) unsized /// types "by value" (see details below). /// /// The operand returned from this function will *not be valid* /// after the current enclosing `ExprKind::Scope` has ended, so /// please do *not* return it from functions to avoid bad /// miscompiles. /// /// # Parameters of unsized types /// /// We tweak the handling of parameters of unsized type slightly to avoid the need to create a /// local variable of unsized type. For example, consider this program: /// /// ```rust /// fn foo(p: dyn Debug) { ... } /// /// fn bar(box_p: Box<dyn Debug>) { foo(*p); } /// ``` /// /// Ordinarily, for sized types, we would compile the call `foo(*p)` like so: /// /// ```rust /// let tmp0 = *box_p; // tmp0 would be the operand returned by this function call /// foo(tmp0) /// ``` /// /// But because the parameter to `foo` is of the unsized type `dyn Debug`, and because it is /// being moved the deref of a box, we compile it slightly differently. The temporary `tmp0` /// that we create *stores the entire box*, and the parameter to the call itself will be /// `*tmp0`: /// /// ```rust /// let tmp0 = box_p; call foo(*tmp0) /// ``` /// /// This way, the temporary `tmp0` that we create has type `Box<dyn Debug>`, which is sized. /// The value passed to the call (`*tmp0`) still has the `dyn Debug` type -- but the way that /// calls are compiled means that this parameter will be passed "by reference", meaning that we /// will actually provide a pointer to the interior of the box, and not move the `dyn Debug` /// value to the stack. /// /// See #68034 for more details. crate fn as_local_call_operand<M>( &mut self, block: BasicBlock, expr: M, ) -> BlockAnd<Operand<'tcx>> where M: Mirror<'tcx, Output = Expr<'tcx>>, { let local_scope = self.local_scope(); self.as_call_operand(block, local_scope, expr) } </ins> /// Compile `expr` into a value that can be used as an operand. /// If `expr` is a place like `x`, this will introduce a /// temporary `tmp = x`, so that we capture the value of `x` at

Related discussion:
text: I think I am basically proposing the same thing as earlier, but with a twist: Today, when building the parameters, we introduce a temporary for each parameter. So if we had we might do: What I am saying is that we could say: If the argument is moved (not copied), and the "place" has outer derefs (e.g., we are passing where is some place), then we assign the place to a temporary and we make the argument to the function be : I don't believe this is observable to end-users, except potentially for when the destructor on runs. I believe that it accepts all the same code we used to accept. It also has nothing specific to or types at all. Does that make sense? I'm very curious to know if anyone sees any problems with it. (One obvious thing: if we ever add , its design would have to accommodate this requirement, but I think that's fine, in fact the complications around getting moves right for precisely this case are one of the reasons we've held off on thus far.)
Can you not reinitialize today? If you move the whole , it is indeed more conservative (and should let us skip unnecessary moves even in cases). But also, do you want to move the locals check from typeck to borrowck? I don't have an intuition for when your approach wouldn't create unsized MIR locals, from the perspective of typeck. However, this is only relevant if we want a stricter feature-gate. At the very least it seems like it would be easier to implement a change only to MIR building, and it should fix problems with for the time being, even without a separate feature-gate.
Hmm, that's a good point, I think do that. Sigh. I knew that seemed too easy. I remember we used to try and prevent that, but we never did it fully, and in the move to NLL we loosened those rules back up, so this compiles: Still, you could do my proposal for unsized types, though it would introduce an incongruity. UPDATE: Oh, wait, I remember now. For unsized types, reinitialization is not possible, because you can't do with an unsized rvalue (we wouldn't know how many bytes to move). So in fact there isn't really the same incongruity.