When to borrow self for methods implemented on generics?

I'm working through Chapter 10 of The Book.

In Listing 10-9 and 10-10, the implemented methods borrow self:

impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

but in Listing 10-11, a (seemingly) similar function does not borrow:

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,
        }
    }
}

I'm trying to understand why mixup() doesn't borrow it's params like the previous examples. I'm assuming that the returned Point is a completely new instance, and that self is not mutable here. Would appreciate any clarification.

1 Like

Usually you'll consume self (e.g. fn mixup(self, ...)) when:

  1. the type is either trivial and implements Copy (e.g. u32 or Point), because making a copy of the object and invoking the method can be as fast as/faster than passing around a reference, or
  2. You want to make sure the caller can't keep using the object after calling that method (imagine some sort of DatabaseConnection::graceful_close() method).

Thanks for your time. What's throwing me here is that self was borrowed when Point was known to contain f32 values (Listing 10-10). So that seems like case #1 in your response. But then self was consumed when Point used generics (Listing 10-11). And in that case it's not clear to me whether the types in Point implement Copy.

I believe one of the fundamental differences is that in the former example, we know the fields are f32, and we know f32 is Copy.

I don't think either Point is Copy, but primitive numbers always are. So in distance_from_origin, we can move self.x without owning self.

However, in the generic version, all we've declared is that T and U are some types - they may or may not be Copy. So if we want to make a new point and use self.x in it, then we have to move out of and invalidate self.x. In order to be allowed to invalidate self.x, we must own self (similar logic for other.y and owning other).

So you’re saying:

  1. We can borrow self if we know that the type of the field the function operates on is Copy, but not if we can’t be sure of that.
  2. Defining a custom type that only has fields that implement Copy isn’t sufficient to make an instance of that type Copy.

Is that correct?

I think the book will go into more detail in this later, but this isn't exactly right. Specifically, we need to either own self or have Copy fields if we want to move our fields. In mixup, we do move our fields, so we need one of those two things.

But in another function, you might only need to borrow your field values. In this case, &self is sufficient, even if the fields are non-Copy.

For instance, we can define the following struct and method:

struct PairOfPoints { a: Point<f32>, b: Point<f32> }

impl PairOfPoints {
    fn print_distances(&self) {
        println!("{}", self.a.distance_from_origin());
        println!("{}", self.b.distance_from_origin());
    }
}

Since distance_from_origin is declared as &self, it only borrows self (in essence, it takes in &Point<f32>). So since PairOfPoints only need to borrow its fields, it can take &self as well.

This is correct! Copy is explicitly opt-in.

You need to implement Copy manually, and 99.9% of the time this is done by putting a #[derive(Copy)] attribute above the item's declaration.

If any of the struct's fields don't implement Copy the derive will fail with a compile error.

1 Like

Ah, the move is the key. Thanks, very helpful.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.