`autoclone` variable marker

To clarify: would moving from an autoclone binding a) always clone, or b) clone, except for the last time it is moved, which would just do a move?

I would perhaps describe these options as:

a) When a variable is marked autoclone, the compiler is allowed to call Clone on move (as necessary to make your code compile)

b) When a variable is marked autoclone, the compiler must call Clone on move (regardless of whether it is necessary to make your code compile)

This had me wondering which of these options apply to "copy semantics" in the "abstract machine" sense (because when it comes to actual memcpys in a practical machine sense, after the optimizer is done with your code, I expect the answer is "it depends"). I can't seem to find this explicitly spelled out anywhere, perhaps someone knows if this is defined somewhere, or if this is undefined. My expectation is that it uses option a), which is also what I intuitively had in mind for autoclone variables, though I certainly understand the appeal of the consistency of option b). I think with option a), explicitly dropping would also not be as much of an issue (although if you were to continue to use the variable after the explicit drop, the explicit call to drop would have called Clone and then immediately dropped said clone).

How much would that handle your case? It of course wouldn't solve the auto-clone in non-closure cases.

Speaking for myself, the main places I run into this are with move closures and with async move blocks. I would expect that any solution more specific to move closures would also apply to async move blocks, so such a solution would probably address the main pain-point. I would not mind such a solution, although such solutions as proposed in 2407 seem to only bring a minor improvement in terms of verbosity.

I suppose this autoclone idea came to mind reading this proposal, which proposes a "cheap to clone" marker trait called Capture. Instead of a move closure, one would then define a capture closure:

capture || {
    ...
}

This closure is then allowed to implicitly clone any variable in scope that implements Capture.

That solution seems to place the decision of what is ok to implicitly clone with upstream library authors, the author of a data structure decides whether it can be implicitly cloned. This autoclone solution places the decision with the user: the user decides if this specific variable is ok to clone in this specific scope. Though of course we're talking about implicit compiler behavior, this autoclone idea seems to give more explicit control than the capture idea.

Note that you could require agreement between the author of the data structure and the client user, with an AutoClone marker trait:

pub trait AutoClone: Clone {}

Then, only types that implement AutoClone could be marked autoclone, or else the compiler errors. I suppose such an AutoClone trait could be opt in (requiring the data structure author to implement it explicitly) or opt-out (where it is automatically implemented for any type that implements Clone unless the author specificly provides a "negative impl" for AutoClone).

FWIW, I think I'd rather see where clones are happening rather than getting to some point, seeing a "final "clone", move instead" marker and then having to go back and make sure that the thing isn't cloned "too much" for any perf reasons. Sure, API designs should prefer slices to Vec if read-only access is needed, but…that doesn't always happen. Maybe clippy can help with some "unnecessary clone , use &var instead", but this feels, to me, like optimizing for writers, not readers, of code which is not the greatest balance IME.

I would point out that arguably the same problem exists for Copy types. In fact, I might argue that the problem is worse for Copy types, because no copy marker is required on the variable. While this autoclone idea is not explicit at the expression level, it is explicit at the lexical scope level. The same is not true for Copy, one would have to look up the actual type declaration to verify that the type is Copy, which typically resides in another codebase entirely. I suppose one typically assumes that a type is Copy because you see it being used after move, and with most code we're looking at, the implicit assumption is that we are in fact looking at code that will successfully compile.