0

I'm trying to create a trait with some default method implementation. One of the methods has to take an instance of the same type and perform some computations.

Here is the simplistic example of what I'm trying to achieve:

struct A {
    val: f32,
}

trait S {
    fn val(&self) -> f32;

    fn add(&self, other: &Self) -> f32 {
        add(&self, other)
    }
}

impl S for A {
    fn val(&self) -> f32 {
        self.val
    }
}

fn add<T: S>(first: &T, second: &T) -> f32 {
    first.val() + second.val()
}

This fails to compile with the error:

15 | |     fn add(&self, other: &Self) -> f32 {
16 | |         add(&self, other)
   | |                    ^^^^^ expected `&Self`, found type parameter `Self`
17 | |     }

I do not understand the error message, for other is of type &Self not Self, so why does compiler thinks otherwise?

If I change it to reference add(&self, &other) (which doesn't seem right, since other is already a reference type), I get another error:

   |
16 |         add(&self, &other)
   |         ^^^ the trait `S` is not implemented for `&Self`
...
26 | fn add<T: S>(first: &T, second: &T) -> f32 {
   |           - required by this bound in `add`

Is there a way to achieve this with default trait implementation, or this can work only with concrete types (like in this question: How do I implement the Add trait for a reference to a struct?)?

EDIT: If I call it like add(self, other), it tries to send the trait object, but I want to send the objects by reference. And really, I want to send concrete implementations and not trait objects.

error[E0277]: the size for values of type `Self` cannot be known at compilation time
  --> src/main.rs:16:9
   |
16 |         add(self, other)
   |         ^^^ doesn't have a size known at compile-time
Maxim Gritsenko
  • 2,396
  • 11
  • 25
  • 1
    *If I call it like add(self, other), it tries to send the trait object* - how did you come to that conclusion? I ask because I don't think it's correct. The compiler reserves the _possibility_ of `Self` being an unsized object, which would make the `&Self` reference variably-sized. But, as the compiler explains, you can easily opt out of that feature either by requiring `Sized` in the trait. – user4815162342 Sep 16 '21 at 08:00

1 Answers1

2

You're passing &self to add(), which makes it a double reference and the arguments to add() don't agree in types. The T of add is apparently determined by the first argument, which is why the compiler appears to expect the other argument to also have the &&Self parameter. (Read the note at the end of the error message.)

Call it with add(self, other) and it will compile. Note that you'll also need to opt out of the implicit Sized bound on add by adding + ?Sized.

// rest as in your code

trait S {
    fn val(&self) -> f32;

    fn add(&self, other: &Self) -> f32 {
        add(self, other)
    }
}

fn add<T: S + ?Sized>(first: &T, second: &T) -> f32 {
    first.val() + second.val()
}

Playground

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • It seems I misunderstood the meaning of Sized trait, from that the confusion about trait objects. I still don't understand what ?Sized means, but at least I know now what to search for. Thanks. – Maxim Gritsenko Sep 16 '21 at 08:33
  • 1
    *I still don't understand what ?Sized means,* - `T: ?Sized` means that `T` is allowed to be unsized, the `?` removes the implicit `Sized` bound on the type parameter `T`. In this case it allows `add()` to work with trait objects - but doesn't mean that it requires trait objects or that it somehow converts references to trait objects; it will accept references to concrete types (that happen to implement `S`) just fine. – user4815162342 Sep 16 '21 at 08:56