How to create setter with a trait as parameter

Viewed 90

Just started with Rust. Would like to create a setter that will accept trait as param. To illustrate idea

pub trait Engine {
  fn start() -> bool;
}

struct Car {
  engine: Box<dyn Engine>,
}

impl Car {
  pub fn new() -> Self {
    let engine = Box::new(DummyEngine {});
    Self {
      engine,
    }
  }
  pub fn set_engine(&mut self, engine: &dyn Engine) {
      self.engine = Box::new(engine);
  }
}

The setter code complains:

the trait bound `&dyn Engine: Engine` is not satisfied
required for the cast to the object type `dyn Engine` rustcE0277

Also, how can the dummy default engine be avoided? Let's say a car doesn't need an engine. Should it be wrapped in an Option?

1 Answers

Given the following Engine trait that specifies start() as method instead of an associated function:

pub trait Engine {
  fn start(&mut self) -> bool;
}

Your DummyEngine doesn't look to implement the Engine trait. You could just implement Engine for DummyEngine:

struct DummyEngine;

impl Engine for DummyEngine {
  fn start(&mut self) -> bool { false }
}

This approach – i.e., using a dummy object – would correspond to the Null Object Pattern. However, you could go for Option instead, as you suggested, and define Car as:

struct Car {
  engine: Option<Box<dyn Engine>>,
}

This way, you could implement new() and set_engine() as:

impl Car {
  pub fn new() -> Self {
    Car {
      engine: None,
    }
  }

  pub fn set_engine(&mut self, engine: Box<dyn Engine>) {
    self.engine = Some(engine);
  }
}

You will be passing a Box<dyn Engine> to set_engine() by value. The Box owns the Engine, and it will be moved into the engine field. That is, the Box argument passed is moved into the parameter engine and, this is, in turn, moved into the engine field of Car.


As Car::new() doesn't take any arguments, you may want to implement the Default trait for Car as well:

impl Default for Car {
   fn default() -> Self {
      Car::new()
   }
}
Related