Question about ownership on struct methods with generic types

On Chapter 10-1 of the book the following code is used:

struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c' };

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

I notice that the mixup() definition uses self and other (without references) so I expected that once the method is called, it takes ownership of the data of both p1 and p2 and once it finished executing this objects would be gone. This was in fact the case, The compiler doesn't allow me to use p1 or p2 after calling let p3 = p1.mixup(p2);.

My question is, how would one rewrite the method definition so that it doesn't eat p1 and p2? I tried the following:

impl<T, U> Point<T, U> {
    fn mixup<V, W>(&self, other: &Point<V, W>) -> Point<T, W> {
        Point {
            x: *self.x,
            y: *other_point.y,
        }

    }
}

My logic is that I use references for self and other and then I dereference the attributes (to get the data store in the attributes) when building the new Point . However I got the following errors:

type `T` cannot be dereferenced
type `W` cannot be dereferenced

When you use the . field access operator, somestruct.somefield, it does not “preserve being a reference” (unlike pattern matching). The type of the result is always the type of the field, even if you started by accessing the struct through a reference. So, what you should have written is

fn mixup<V, W>(&self, other: &Point<V, W>) -> Point<T, W> {
    Point {
        x: self.x,
        y: other.y,
    }
}

However, you will then get a “cannot move out of self.x” error, because you haven't arranged any way to copy the values so that both the old points and the new point can have an T and a W. The simplest general approach to this is to require the field types to implement Clone, and use it:

    fn mixup<V, W>(&self, other: &Point<V, W>) -> Point<T, W>
    where
        T: Clone,
        W: Clone,
    {
        Point {
            x: self.x.clone(),
            y: other.y.clone(),
        }
    }

This makes a notably different operation than the original mixup, which doesn't require Clone (but instead discards half the input). When you want to write generic code in Rust, it's a good idea to plan out how your API will be used in the event that a value can't or shouldn't be cloned — if mixup were a commonly used operation, you might provide both versions.

1 Like

. binds tighter than *, so *self.x is parsed as *(self.x). You meant (*self).x (which as kpreid points out is the same as just self.x here).

I don't quite understand what you mean when you say:

However, you will then get a “cannot move out of self.x ” error, because you haven't arranged any way to copy the values so that both the old points and the new point can have an T and a W.

If self.x returns a type T why do I need to "copy" the values? Why can't I assign the return type to a variable? When a function, for example my_func(), returns a result of a given type I can assign such result directly as let x = my_func();, how is that different ?

If self.x returns a type T why do I need to "copy" the values?

It's not a matter of the type matching, but of where the value comes from. You accept a reference to a Point<T, U>, which owns a T, and try to produce a Point<T, W>, which also owns a T. In order for both of these Points to each own a T, a second T must be created, by cloning or by other means.

When a function, for example my_func() , returns a result of a given type I can assign such result directly as let x = my_func(); , how is that different?

It's not different, except that using a function helps highlight where the error is. If you wrote

impl<T, U> Point<T, U> {
    fn get_x(&self) -> T {
        self.x
    }
}

then that, by itself, would be an error, because this code attempts to move the T out of the Point, but it cannot do that because that would leave the Point with no T to be the value of the .x field, and also because &self is an immutable reference so modifying or consuming the point is not permitted.

But if you modify this code to be self.x.clone(), or using explicit function calls and referencing, Clone::clone(&self.x), then it is valid, because clone only needs a reference to self.x to make a copy of the contents of it.

Going back to the example without a separate function, &self.x takes a reference to the field of self, so it is always allowed, but self.x without the & moves the value from the field, so it can only be done when you have self by value, as the original definition of mixup does, so that it may be consumed by the function body — preserving the property that the T is not implicitly duplicated, only moved.

3 Likes

I see, thank you. I am new to the concept of ownership and I got so lost in the type difference between T and &T that I missed that the ownership of the attribute x changes when I call self.x.

Just to be sure I got it right, when I write a method as:

impl<T, U> Point<T, U> {
    fn get_x(&self) -> T {
        self.x
    }
}

self.x here is not a reference to the attribute x of the particular instance of Point that's calling the method, it is the attribute x (event though I defined the method with a reference to self, &self). If instead I wrote &self.x that would be a reference to the attribute x, to the specific value that x has for the specific instance of Point that is calling the method, right?

self.x here is not a reference to the attribute x of the particular instance of Point that's calling the method, it is the attribute x (event though I defined the method with a reference to self, &self ). If instead I wrote &self.x that would be a reference to the attribute x , to the specific value that x has for the specific instance of Point that is calling the method, right?

Yes, but I recommend sticking to the standard terminology: field, not attribute.

Also, the most particular thing about &self.x is that it is a reference to the memory that is storing the value of self.x. An important characteristic of a reference is that it always refers to some memory that exists; you can't have, say, “a reference to the number 238490238448229” without that number having previously been computed and stored somewhere (that is not the reference itself). If a function returns a reference, then, the thing it is referring to existed before that function was called, or was stored into an existing location by that function.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.