Rather than try to assign punctuation to &move
variants, I'd suggest using contextual keywords (&move in T
, &move out T
, &move inout T
) with a note that this is placeholder syntax that may be changed as the feature is experimented with. Assigning punctuation to it is going to have an uphill battle as it is far from immediately clear what each one means.
(Though maybe &move in
is also confusing, since that's what you'd use for an "out parameter," as you're moving into the reference...)
I'd also recommend dropping field-granularity in/out specification and DerefMove
to future possibilities. &move
on its own actually has merit to stand on without these, and that makes it a smaller more incremental stepping stone.
With those pieces torn out and deffered, I think this might actually have standing as an eRFC. Roughly, the Experimental RFC is a proposition of a design that we (as a community) would like to see experimented on in-tree, but without the expectation that it could be stabilized without another RFC round taking the experience learned into account.
But that also said, you haven't solved the panic problem for &move in
. &move out
is gracefully handled by drop flags, as it's just treated like a stack binding in someone else's scope. &move in
is the problem case, though:
fn uhoh() {
let data: Data;
catch_unwind(|| initialize_data(&move in data));
dbg!(data); // 💥 data is believed to be initialized
}
fn initialize_data(_: &move in Data) {
todo!("I'll get to this later")
}
As far as I can tell, there are only a few options that can make this sound:
- The solution taken by the take_mut crate: unwinds with
&move in
on the stack are "double panic" aborts. This works, but is undesirable, and not a great solution to require in the design of the language. - Poisoning. Basically,
&move in
is a reference toData
and some shadow state, likely equivalent to the drop flags forData
. If the unwind is caught, a new one is started up "whenever" it's noticed thatData
wasn't actually fully initialized.- Poisoning, but less granular. The called function gets a slot to say whether
Data
was properly initialized, or that it wasn't, no more granularity. The called function is responsible for dropping any partially initialized parts of theData
. - Poisoning, but more magic. If a function can be proven to always move into the
&move in
reference, the compiler tags it as such, and omits the extra shadow state. This is probably just an optimization (though an ABI impacting one), but also one that would rarely apply, due to there being no simple way to prove the lack of panics (and most code doing won't panic but statically could operations like integer arithmetic and indexing). - Poisoning, but
catch_unwind
aware. Monomorphize every use of&move in
for whether it's transitively in acatch_unwind
or not. But then again I think the runtime callsmain()
in acatch_unwind
, and each thread is effectively acatch_unwind
, so that's probably moot. - Also I just want to note that "whenever" is not an acceptable specification for when the unwind resumes, and that
catch_unwind
can be hidden behind an arbitrary amount of abstraction, and potentially opaque ones.
- Poisoning, but less granular. The called function gets a slot to say whether
- Make
&move in
requireunsafe
somewhere early, before a panic can expose uninitialized data. At that point, though, what's the real advantage overMaybeUninit
and writingunsafe
pointer code? - Make
&move in
notUnwindSafe
, so you can'tcatch_unwind
across&move
... exceptUnwindSafe
is just a lint and can't be used to enforce safety.- Add a new idea of
UnwindSafe
that can be relied on for soundness. Probably untenable because API design already relies on the fact that everything is required to be sound even in the face of unwinding.
- Add a new idea of
So looking at that... the first eRFC really should only concern itself with &move out
, as that's fairly simple comparatively. It should leave space for &move in
, but not try to solve those problems yet, as solving placement new is a hard problem.
(And in this incremental world, I think we say &move in
=> &move in
, &move out
=> &move
, and &move inout
=> &mut
.)