Lord Sentox
Lord Sentox

Reputation: 33

Deref struct to tuple requires move

I was writing a Vector class just for fun in rust and thought it would be nice to be able to implement Deref for it, accessing it just like a tuple reference. For instance, a Vec2<f32> could be dereferenced as &(f32, f32). I came up with this:

pub struct Vec2<T> {
    pub x: T,
    pub y: T
}

impl<T> Deref for Vec2<T> {
    type Target = (T, T);

    fn deref(&self) -> &(T, T) {
        &(self.x, self.y)
    }
}

However, since the compiler wants to create the tuple and then reference it, it tries to move out of the struct. If I were to use type Target = (&T, &T) to then return a &(&T, &T) I wold have to use explicit lifetime specifiers, which I don't have because I cannot access the self lifetime.

Now my question is: Is there a way to do this without copying the values? Since I'm often using small types I will probably not really be using Deref anyway, but DerefMut could become useful, I'd imagine.

Upvotes: 2

Views: 1731

Answers (1)

Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 88886

Summary

Right now, there is no way to do that! At least not without using unsafe.


Why?

Consider the return type -> &Foo. It means that the function returns a reference to a Foo that already lives somewhere. In particular, if you have fn deref(&self) -> &Foo it means that the Foo lives at least as long as self, because lifetime elision kicks in and converts it to fn deref<'a>(&'a self) -> &'a Foo.

Now, (T, T) is a type just like Foo. So fn deref(&self) -> &(T, T) means that you return a reference to a tuple of Ts which already lives somewhere. But there is no such tuple! You could create a tuple in the function, but that won't live long enough. Similarly, if you say -> &(&T, &T) you say that you return a reference to a tuple of (reference to T) which already lives somewhere. But again: you don't have a tuple already living somewhere.

So the trait Deref requires you to return a reference to something which already lives exactly like that in self. So with this, it's impossible to do what you want.

Unsafe

You could use the unsafe function mem::transmute(). After all, a struct like yours and a tuple should have exactly the same memory layout, right?

Yes and no. It is probably the case that both memory layouts are the same, but Rust doesn't guarantee it! Rust is free to reorder fields and add padding bytes. And while I suspect that the memory layout for structs, tuple structs and tuples are exactly the same, I can't find a source on that. So without that information in the specification, transmuting is technically not safe.

The future

In the future, HKTs or more rather Generic Associated Types could solve this problem. That is, they wouldn't solve your problem directly, but with GATs, as defined in this RFC, one could redefine the Deref trait. That is:

trait Deref {
    type Target<'a>;
    fn deref(&self) -> Self::Target<'a>;
}

Right now we are forced to put the lifetime "at the outermost level". With this, we can let the type implementing Deref decide. In that case you could write:

impl<T> Deref for Vec2<T> {
    type Target<'a> = (&'a T, &'a T);

    fn deref(&self) -> Self::Target {
        (&self.x, &self.y)
    }
}

This way the lifetime is "on the inside".

But GATs are not even implemented yet, so it will still take some time. Additionally, it's unclear when/how/if the standard library is changed in a backward-incompatible manner which would allow to change Deref.

Upvotes: 3

Related Questions