Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I implemented a trait for another trait but cannot call methods from both traits

Tags:

rust

traits

I have a trait called Sleep:

pub trait Sleep {
    fn sleep(&self);
}

I could provide a different implementation of sleep for every struct, but it turns out that most people sleep in a very small number of ways. You can sleep in a bed:

pub trait HasBed {
    fn sleep_in_bed(&self);
    fn jump_on_bed(&self);
}

impl Sleep for HasBed {
    fn sleep(&self) {
        self.sleep_in_bed()
    }
}

If you're camping, you can sleep in a tent:

pub trait HasTent {
    fn sleep_in_tent(&self);
    fn hide_in_tent(&self);
}

impl Sleep for HasTent {
    fn sleep(&self) {
        self.sleep_in_tent()
    }
}

There are some oddball cases. I have a friend that can sleep standing against a wall, but most people, most of the time, fall into some simple case.

We define some structs and let them sleep:

struct Jim;

impl HasBed for Jim {
    fn sleep_in_bed(&self) {}
    fn jump_on_bed(&self) {}
}

struct Jane;

impl HasTent for Jane {
    fn sleep_in_tent(&self) {}
    fn hide_in_tent(&self) {}
}

fn main() {
    use Sleep;
    let jim = Jim;
    jim.sleep();

    let jane = Jane;
    jane.sleep();
}

Uh-oh! Compile error:

error[E0599]: no method named `sleep` found for type `Jim` in the current scope
  --> src/main.rs:44:9
   |
27 | struct Jim;
   | ----------- method `sleep` not found for this
...
44 |     jim.sleep();
   |         ^^^^^
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `sleep`, perhaps you need to implement it:
           candidate #1: `Sleep`

error[E0599]: no method named `sleep` found for type `Jane` in the current scope
  --> src/main.rs:47:10
   |
34 | struct Jane;
   | ------------ method `sleep` not found for this
...
47 |     jane.sleep();
   |          ^^^^^
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `sleep`, perhaps you need to implement it:
           candidate #1: `Sleep`

This compiler error is strange because if there was something wrong with a trait implementing another trait, I expected to hear about it way back when I did that, not at the very bottom of the program when I try to use the result.

In this example, there are only 2 structs and 2 ways to sleep, but in the general case there are many structs and several ways to sleep (but not as many ways as there are structs).

A Bed is mostly an implementation for Sleep, but in the general case a Bed has many uses and could implement many things.

The only immediately obvious approach is to convert impl Sleep for... into a macro that structs themselves use, but that seems hacky and terrible.

like image 835
Drew Avatar asked Mar 25 '15 13:03

Drew


2 Answers

You need to implement the second trait for objects that implement the first trait:

impl<T> Sleep for T where     T: HasBed, {     fn sleep(&self) {         self.sleep_in_bed()     } } 

Previously, you were implementing Sleep for the trait's type, better expressed as dyn HasBed. See What does "dyn" mean in a type? for more details.

However, this is going to break as soon as you add a second blanket implementation:

impl<T> Sleep for T where     T: HasTent, {     fn sleep(&self) {         self.sleep_in_tent()     } } 

With

error[E0119]: conflicting implementations of trait `Sleep`:   --> src/main.rs:24:1    | 10 | / impl<T> Sleep for T 11 | | where 12 | |     T: HasBed, 13 | | { ...  | 16 | |     } 17 | | }    | |_- first implementation here ... 24 | / impl<T> Sleep for T 25 | | where 26 | |     T: HasTent, 27 | | { ...  | 30 | |     } 31 | | }    | |_^ conflicting implementation 

It's possible for something to implement both HasBed and HasTent. If something were to appear that implemented both, then the code would now be ambiguous. The workaround for this would be specialization, but there's no stable implementation of that yet.

How do you accomplish your goal? I think you have already suggested the current best solution - write a macro. You could also write your own derive macro. Macros really aren't that bad, but they can be unwieldy to write.

Another thing, which may be entirely based on the names you chose for your example, would be to simply embed structs into other structs, optionally making them public. Since your implementation of Sleep basically only depends on the bed / tent, no functionality would be lost by doing this. Of course, some people might feel that breaks encapsulation. You could again create macros to implement a delegation of sorts.

trait Sleep {     fn sleep(&self); }  struct Bed; impl Bed {     fn jump(&self) {} } impl Sleep for Bed {     fn sleep(&self) {} }  struct Tent; impl Tent {     fn hide(&self) {} } impl Sleep for Tent {     fn sleep(&self) {} }  struct Jim {     bed: Bed, } struct Jane {     tent: Tent, }  fn main() {     let jim = Jim { bed: Bed };     jim.bed.sleep(); } 
like image 102
Shepmaster Avatar answered Oct 08 '22 17:10

Shepmaster


We can use associated items here.

pub trait Sleep: Sized {
    type Env: SleepEnv;

    fn sleep(&self, env: &Self::Env) {
        env.do_sleep(self);
    }

    fn get_name(&self) -> &'static str;
}

pub trait SleepEnv {
    fn do_sleep<T: Sleep>(&self, &T);
}

Then, we implement two different sleep environments.

struct Bed;
struct Tent;

impl SleepEnv for Bed {
    fn do_sleep<T: Sleep>(&self, person: &T) {
        println!("{} is sleeping in bed", person.get_name());
    }
}

impl SleepEnv for Tent {
    fn do_sleep<T: Sleep>(&self, person: &T) {
        println!("{} is sleeping in tent", person.get_name());
    }
}

The last piece is the concrete implementations of them.

struct Jim;
struct Jane;

impl Sleep for Jim {
    type Env = Bed;
    fn get_name(&self) -> &'static str {
        "Jim"
    }
}

impl Sleep for Jane {
    type Env = Tent;
    fn get_name(&self) -> &'static str {
        "Jane"
    }
}

Test code:

fn main() {
    let bed = Bed;
    let tent = Tent;

    let jim = Jim;
    let jane = Jane;
    jim.sleep(&bed);
    jane.sleep(&tent);
}
like image 29
F001 Avatar answered Oct 08 '22 18:10

F001