Pre-RFC: Move references

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"]);
2 Likes