Help me find a way to return self

I commented the section of the code where I'm having problems. It's a little long to paste here.

So as you can see calling d.print() causes Dog to introduce itself and its buddy. Calling d.kidnap() will replace it's buddy with a new one. The important bit is to include this functionality while preserving Dog's functions test1() and test2(). The third call site in main() demonstrates that.

This all works. But I am unhappy with it. Line's 54 to 59 is where the problems are. See I would rather return self instead of having to create a whole new dog. When I attempt to do that I get all sorts of conflicting errors about the return type. I want to comment out line 52 and uncomment lines 54 to 59 and change either the return type of the function, the trait function definition, the impl definition of Dog<P> or something to make it work. I think I've tried all possibilities.

You can't return self because self is Dog<P> and your function says it returns Dog<Elephant<P>>. They are different types. You're not "initialising a new Dog", you're initialising a new Dog<Elephant<P>> because you don't already have one.

The code you have uncommented is the correct way to do it.

I've tried changing the signature of kidnap() to return Dog<P>. But that wont work because mismatched types. It obviously needs P but we give it Elephant<P>. We want to impl all Dog types of Dog<P> so it seems like this is impossible to express in Rust, though I hope I'm wrong. We have an impl for Dog<P> and we want to return Dog<P> where P = Elephant specifically.

It can't be Dog<P> because you're constructing a Dog<Elephant<P>>. The types must match exactly. It doesn't matter that Elephant<P> satisfies the constraint on P.

Also, you can't have the body of kidnap determine what P is. The caller decides what P it will use, and your function has to conform to that, and you don't know what P will be ahead of time.

If you're trying to write what you would in Java or C#, then you should stop and read through the Rust Book. Conventional OO designs do not translate well to Rust at all. Trying to do so usually leads to lots of beating your head against a wall and making no progress. You probably want to specifically read about trait objects, which might be closer to what you want. That said, keep in mind: traits are not interfaces (in the Java/C# sense), and trait objects aren't object references (in the Java/C# sense).

3 Likes

I boiled down the problem to a much simpler example

trait Printer {
    fn print();
}

struct Dog<P: Printer> {
    inner: P
}

impl<P: Printer> Printer for Dog<P> {
    fn print() {}
}

impl<P: Printer> Dog<P> {
    fn change_inner(self) {
        self.inner = Mouse{};
    }
}

struct Mouse {
    
}

impl Printer for Mouse {
    fn print() {}
}
error[E0308]: mismatched types
|
15 |         self.inner = Mouse{};
   |                      ^^^^^^^ expected type parameter, found struct `Mouse`
   |
   = note: expected type `P`
              found type `Mouse`

Just to confirm, there is no way to get that fn change_inner() to do that?

You cannot for two different reasons:

  • P is not Mouse. P might be Mouse, but it also might not be Mouse. The compiler will only allow what it can prove is always correct.

  • You cannot change a value's type in-place.

1 Like

I'd try something like this:

struct Dog {
}

struct Mouse {
}

enum MouseOrDog {
    Mouse(Mouse),
    Dog(Dog),
}

struct PrintableAnimal {
    inner: MouseOrDog
}

trait Printer {
    fn print();
}

impl Printer for PrintableAnimal {
    fn print() {
        // match on enum and print from here
    }

    fn change_inner(self) {
        self.inner = MouseOrDog::Mouse(Mouse{});
    }
}

You're no longer changing the type of inner, but rather you have an enum wrapping the value, which allows one of either Dog or Mouse types.

This requires turning a couple other bits inside out, namely by introducing PrintableAnimal, but it does simplify some of the trait impl logic.

(edited to fix mistakes. this still won't compile. I leave that as an exercise)

One more thing. You can still impl Printer for Dog and Mouse then defer to each of those in the impl Printer for PrintableAnimal.

3 Likes