Stabilization of `DerefMove` trait

A not so well known fact is that one can dereference a Box and this will consume the Box and return its referent. From what I have learned, this is compiler intrinsic. I've seen similar topics introducing the DerefMove trait to implement into-style deference in std and free the compiler intrinsic. However those topics are quickly diversified and I soon lose track of the current status. First of all, is Box the only struct in std having this special deference behavior? Next question is, what is currently blocking the implementation of DerefMove in stable Rust?

I thought so, too, for some time – but it’s wrong! More on that in a second.

Indeed, dereferencing of &T &mut T and Box<T> is a compiler-built-in operation and doesn’t involve any of the Deref traits.

The main problem is the trait design. The operation is not as simple as an into_inner method with a fn(self) -> Inner type consuming self and returning the inner value.

Yes, Box is the only struct that allows moves out of a dereferencing operation.

Box is not the only type that has a compiler-provided implementation for Deref. &T and &mut T have one, too, but of course you cannot move out of those. This is necessary as the desugarings of the * operator once again contain the * operator, but for &T or &mut T respectively, so you need to have those built-in to avoid infinite recursion.

As mentioned above, finding the right trait design should be the main issue. Some people might also have the feeling that the nicest possible design would/could/should involve some new language features that allow us to write a signature for the trait method without e.g. relying on raw pointers and unsafe code; though last time I thought about it, even with raw pointers and unsafety the design is somewhat nontrivial.


Now onto the nitty-gritty: What does dereferencing a Box do?

Perhaps the best way would be to start with a code-example. You can do this:

fn demo(mut x: Box<String>) { // using `String` as it isn’t `Copy`
    let s: String = *x; // moves out of the box
    *x = String::from("hi"); // box still exists, we can move a new value in
    let _y: Box<String> = x; // while it’s fully filled, we can normally use the Box
}

fn demo2(mut x: Box<(String, String)>) {
    let s1: String = x.0; // partially moving out is also possible
    // (this desugars to `(*x).0` – dereferencing is still relevant)
    drop(s1);

    let s2_ref: &String = &x.1; // non-moved-out part is still usable
    // (this also desugars to `&(*x).1`, so itĘĽs another deref)

    // at the end of this function, when `x` is dropped we drop
    // * not the moved-out first field, as that’s moved-from
    // * but we do drop the second field
    // * and we deallocate the `Box` allocation
}

especially with the issue of supporting partial moves, and supporting implicit drops at the end of a scope that do not execute the full “Drop implementation” (I don’t mean the “real” one as that’s magically letting the compiler do part of the work!) seems a hard thing to express in a trait.

So what does dereferencing a Box do? Writing *x is not like calling an x.into_inner() method. Well, of course writing *x alone on a x: Box<T> doesn’t actually do much of anything yet, and you could be just taking a reference for example as in &*x… but if you do a move, by writing let s = *x; and thus moving out of the dereferenced box will, this will only partially consume of the box. It will consume the inner value, but notably not the whole Box: The box still exists, and you can move a new value into it, and/or have it be dropped at the end of the scope, and only at that point would the Box’s heap memory be freed.

In many ways *x on a Box is very similar to x.a on a struct with exactly one field a. You can move out of the field, and the struct will still exist but be considered partially-moved-out-of. You can even move out only some part of of the a field, e.g. with something like let s = x.a.b;. You can move back into the moved-out-of parts, and/or access the still-valid parts while the struct is partially moved-out-of. The complication of representing the ability to move out and in – even partially – for fields is probably also one of the factors why we don’t support any abstract representation of “fields/properties” through a trait.

17 Likes