fadedbee
fadedbee

Reputation: 44715

How do I make a caller see the trait implementation of a method?

With the code:

trait Msg {
    fn apply_to_state(&self, state: &mut State);
}

trait ApplicableToStateOne: Msg {
    fn apply_to_state(&self, state: &mut State) {
        match state {
            State::StateOne(s) => {
                self.apply_to_state_one(s) 
            }
            _ => {
                //TODO: return an error
            }
        }
    }
    fn apply_to_state_one(&self, state_one: &mut StateOne);
}

#[derive(Debug, Clone)]
pub struct MsgA {
    pub field_a: u8,
}

impl Msg for MsgA {}
impl ApplicableToStateOne for MsgA {
    fn apply_to_state_one(&self, state_one: &mut StateOne) {
        state_one.one_special += 31; // just a mutability test
    }
}

// this is a stub for receiving different kinds of messages from the network
fn recv() -> Box<dyn Msg> {
    Box::new(MsgA { field_a: 42 })
}

fn main() {
    let mut state = State::StateOne(StateOne { common: 0, one_special: 1 });
    for _ in 0..100 { // this would be loop, but that makes the playground timeout
        let incoming = recv(); // this would block
        incoming.apply_to_state(&mut state)
    }
}

( playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7c89a2bbc765380fc002864e2be80e55 )

the compiler complains:

error[E0046]: not all trait items implemented, missing: `apply_to_state`
  --> src/bin/sandbox6.rs:83:1
   |
2  |     fn apply_to_state(&self, state: &mut State);
   |     -------------------------------------------- `apply_to_state` from trait
...
83 | impl Msg for MsgA {}
   | ^^^^^^^^^^^^^^^^^ missing `apply_to_state` in implementation

In my (obviously deficient) understanding, I would have expected the trait ApplicableToStateOne implementation of apply_to_state to be called.

How can I make this happen?


Update:

More abstractly, this question is about:

  1. receiving a boxed supertrait object from the network, then
  2. finding out which subtrait it has, and finally
  3. calling a method appropriate for the subtrait (perhaps through the supertrait in some way).

This can all be done, rather verbosely, using enums instead of traits, but that adds a hierarchy of enums.

Having a hierarchy of enums is bad because:

Upvotes: 2

Views: 104

Answers (2)

Jmb
Jmb

Reputation: 23264

You can use a generic to implement Msg for everything that implements ApplicableToStateOne:

struct State {}

trait Msg {
    fn apply_to_state(&self, state: &mut State);
}

trait ApplicableToStateOne: Msg {
    fn apply_to_state_one(&self, state: &mut State) {
        todo!();
    }
}

impl<T: ApplicableToStateOne> Msg for T {
    fn apply_to_state(&self, state: &mut State) {
        self.apply_to_state_one (state);
    }
}

#[derive(Debug, Clone)]
pub struct MsgA {
    pub field_a: u8,
}

impl ApplicableToStateOne for MsgA {}
// No need to implement Msg explicitly for MsgA

Playground

Upvotes: 4

Netwave
Netwave

Reputation: 42678

Think of each of the methods to be linked to the Trait instead of the object itself. Check this simpler example:

trait Foo {
    fn foo(&self) -> &'static str;
}

trait FooPrime {
    fn foo(&self) -> &'static str;
}

struct Bar {}

impl Foo for Bar {
    fn foo(&self) -> &'static str {
        "foo"
    }
}

impl FooPrime for Bar {
  fn foo(&self) -> &'static str {
        "foo prime"
    }  
}

fn main() {
    let bar = Bar{};
    println!("{} : {}", bar.foo(), bar.foo());
}

When compiling we get the following error:

   Compiling playground v0.0.1 (/playground)
error[E0034]: multiple applicable items in scope
  --> src/main.rs:25:29
   |
25 |     println!("{} : {}", bar.foo(), bar.foo());
   |                             ^^^ multiple `foo` found
   |
note: candidate #1 is defined in an impl of the trait `Foo` for the type `Bar`
  --> src/main.rs:12:5
   |
12 |     fn foo(&self) -> &'static str {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `FooPrime` for the type `Bar`
  --> src/main.rs:18:3
   |
18 |   fn foo(&self) -> &'static str {
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: disambiguate the associated function for candidate #1
   |
25 |     println!("{} : {}", Foo::foo(&bar), bar.foo());
   |                         ^^^^^^^^^^^^^^
help: disambiguate the associated function for candidate #2
   |
25 |     println!("{} : {}", FooPrime::foo(&bar), bar.foo());
   |                         ^^^^^^^^^^^^^^^^^^^

error[E0034]: multiple applicable items in scope
  --> src/main.rs:25:40
   |
25 |     println!("{} : {}", bar.foo(), bar.foo());
   |                                        ^^^ multiple `foo` found
   |
note: candidate #1 is defined in an impl of the trait `Foo` for the type `Bar`
  --> src/main.rs:12:5
   |
12 |     fn foo(&self) -> &'static str {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `FooPrime` for the type `Bar`
  --> src/main.rs:18:3
   |
18 |   fn foo(&self) -> &'static str {
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: disambiguate the associated function for candidate #1
   |
25 |     println!("{} : {}", bar.foo(), Foo::foo(&bar));
   |                                    ^^^^^^^^^^^^^^
help: disambiguate the associated function for candidate #2
   |
25 |     println!("{} : {}", bar.foo(), FooPrime::foo(&bar));

We need to explicitly tell the compiler which method to use:

fn main() {
    let bar = Bar{};
    println!("{} : {}", Foo::foo(&bar), FooPrime::foo(&bar));
}

Playground

For the same reason in your code the method for the other trait is not used, since it is not part of the previous trait. Here is a link to the book

You may want to use the supertrait functionality for creating a default impl of another trait based on the "parent" one:

trait FooPrime : Foo {
    fn foo(&self) -> &'static str {
        Foo::foo(self)
    }
}

Playground

Upvotes: 0

Related Questions