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 inon 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 inis a reference toDataand 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 thatDatawasn't actually fully initialized.- Poisoning, but less granular. The called function gets a slot to say whether
Datawas 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 inreference, 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_unwindaware. Monomorphize every use of&move infor whether it's transitively in acatch_unwindor 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_unwindcan 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 inrequireunsafesomewhere early, before a panic can expose uninitialized data. At that point, though, what's the real advantage overMaybeUninitand writingunsafepointer code? - Make
&move innotUnwindSafe, so you can'tcatch_unwindacross&move... exceptUnwindSafeis just a lint and can't be used to enforce safety.- Add a new idea of
UnwindSafethat 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.)