If &move in T
worked just like an uninitialized local let x: T;
, it would be an error to capture it in a closure, move it into a struct, or pass it to a function expecting some U by value. The requirement to initialize could mean that any (non-panic) returns from the function count as a use of the initialized value, so the &move in
must have either been directly initialized or passed to some other function. After doing either, it could be used normally as a &mut
.
For unsafe code you'd need to be able to borrow it as a &mut MaybeUninit<T>
or take a raw pointer, and there would have to be something like a function fn assume_init<T: ?Sized>(x: &move in T)
, which would be found somewhere behind many non-trivial uses.
When unwinding from a function taking some &move in
, it should be considered uninitialized. Any other &move in
references that are in scope are either initialized or not (known statically or via drop flags), and should be dropped if they are.
impl<T> Box<T> {
fn new_with(f: impl Fn(&move in T)) {
let b = Box::new_uninit();
let move_in_ref = b.placeholder_into_move_in(); // &mut MaybeUninit<T> -> &move in
f(move_in_ref);
// Safety:
// *move_in_ref is initialized here, meaning that so is the box it came from
unsafe { b.assume_init() }
}
}
fn main() {
// OK
let x = Box::new_with(|x_ref| { *x_ref = 42; });
// ERROR: x_ref must be initialized before returning
let x = Box::new_with(|x_ref| { });
}
At this point it looks more like a different style of parameter than a proper type. I don't know if the limitations leave it sufficient power. For example, a function initializing only some prefix of a slice would want to (and have to!) return the remaining uninitialized part to the caller, but to do that in a usable way it would have to be part of some tuple or Result, which goes outside of what uninitialized locals can currently do.
Maybe &move in
references could be moved into structs or enums if those compound values were then infected by the same restrictions. However, not being able to pass to a generic function is not so simple once the reference is hidden in a Result or such. Some form of proper linear types along the lines of Pre-RFC: Leave auto-trait for reliable destruction would seem to be needed.
A more complex example that would require returning tuples of &move in
:
// initialize a slice by recursively initializing both halves
fn recursive(v: &move in [String], mut s: String) {
// this would need some new API since [T]::len takes &[T]
let len = v.len();
match v {
[] => (),
[x] => {
if rare_error() {
panic!("oops")
}
*x = s;
}
v => {
// this can't work with the above limitations,
// since how would the function create the tuple to return
let (head,tail) = v.split_at_movein(len/2);
let mut s2 = s.clone();
s.push_str("0");
// panic from this call would drop only s2
recursive(head, s);
s2.push_str("1");
// panic from this call would drop only head
recursive(tail, s2);
}
}
}
let x = Box::<[String;4]>::new_with(|v| {
recursive(v as &move in [String], String::new())
});
assert_eq(*x, ["00","01","10","11"]);