1

I have a need for a common interface to some OS-specific code. These implementations are rather large, with not much common between them, so my initial though was to create separate structs for each implementation, both of which implement a common trait to make it agnostic to the implementation.

First off, is this the right way to go? Is there some other means that might make this easier in Rust?

Next, I want to iterate on this, which means storing a reference to a trait, which I haven't been able to get right. If part one of this question is correct, should I be attempting to create an iterator against the common trait, or should I be making OS-specific versions of the iterator as well?

Here's a simple of example of what I'm trying to do:

trait Thing {
    #[cfg(any(target_os = "linux"))]
    fn new() -> Thing {
        LinuxThing {
            value: 1
        }
    }

    #[cfg(any(target_os = "macos"))]
    fn new() -> Thing {
        MacThing {
            value: 2
        }
    }

    fn iter(&self) -> ThingIter {
        ThingIter {
            thing: self
        }
    }

    fn stuff(&self) -> u8;
}

#[cfg(any(target_os = "linux"))]
struct LinuxThing {
    value: u8,
}

#[cfg(any(target_os = "macos"))]
struct MacThing {
    value: u8,
}

#[cfg(any(target_os = "linux"))]
impl Thing for LinuxThing {
    fn stuff(&self) -> u8 {
        self.value
    }
}

#[cfg(any(target_os = "macos"))]
impl Thing for MacThing {
    fn stuff(&self) -> u8 {
        self.value * 2
    }
}

struct ThingIter<'a> {
    thing: &'a Thing,
}

impl<'a> Iterator for ThingIter<'a> {
    type Item = u8;

    fn next(&mut self) -> Option<Self::Item> {
        Some(self.stuff())
    }
}

fn main() {
    let thing = Thing::new();
    println!("Thing: {}", thing.stuff());

    let iter = ThingIter { thing: &thing };
    println!("Next: {}", iter.next());
    println!("Next: {}", iter.next());
    println!("Next: {}", iter.next());
}

This is currently failing with:

error[E0038]: the trait `Thing` cannot be made into an object
  --> test.rs:50:5
   |
50 |     thing: &'a Thing,
   |     ^^^^^^^^^^^^^^^^ the trait `Thing` cannot be made into an object
   |
   = note: method `new` has no receiver

error[E0038]: the trait `Thing` cannot be made into an object
  --> test.rs:10:5
   |
10 |     fn new() -> Thing {
   |     ^^^^^^^^^^^^^^^^^ the trait `Thing` cannot be made into an object
   |
   = note: method `new` has no receiver

error: aborting due to 2 previous errors

I'm a C developer by trade, which means I'm used to just doing whatever I want. I love the concept of Rust, but C is a hard habit to break.

  • It is preferred if you can post separate questions instead of combining your questions into one. That way, it helps the people answering your question and also others hunting for at least one of your questions. It also appears that one of your questions is mostly opinion-based, and as such is not suitable for Stack Overflow. – Shepmaster Aug 05 '18 at 21:04
  • See also [What is the correct way to return an Iterator (or any other trait)?](https://stackoverflow.com/q/27535289/155423), [Trait Object is not Object-safe error](https://stackoverflow.com/q/29985153/155423), [Is it possible to have a constructor function in a trait?](https://stackoverflow.com/q/31897330/155423) – Shepmaster Aug 05 '18 at 21:17
  • Instead of having a struct for each OS which all implement the same trait, could you just have a single struct whose implementation (and the implementations of functions acting on it) depend on the target OS? – Kwarrtz Aug 06 '18 at 05:58

0 Answers0