Rust - trait object
impl Executor is the generic parameter
&dyn Executor and Box<dyn Executor> are trait objects
&dyn Executor is on stack
Box<dyn Executor> is on heap
use std::{error::Error, process::Command};
pub type BoxedError = Box<dyn Error + Send + Sync>;
pub trait Executor {
fn run(&self) -> Result<Option<i32>, BoxedError>;
}
pub struct Shell<'a, 'b> {
cmd: &'a str,
args: &'b [&'a str],
}
impl<'a, 'b> Shell<'a, 'b> {
pub fn new(cmd: &'a str, args: &'b [&'a str]) -> Self {
Self { cmd, args }
}
}
impl<'a, 'b> Executor for Shell<'a, 'b> {
fn run(&self) -> Result<Option<i32>, BoxedError> {
let output = Command::new(self.cmd).args(self.args).output()?;
Ok(output.status.code())
}
}
pub fn execute_generics(cmd: &impl Executor) -> Result<Option<i32>, BoxedError> {
cmd.run()
}
pub fn execute_trait_object(cmd: &dyn Executor) -> Result<Option<i32>, BoxedError> {
cmd.run()
}
#[cfg(test)]
mod tests {
use supper::*;
#[test]
fn shell_shall_work() {
let cmd = Shell::new("ls", &[]);
let result = execute_generics(&cmd).unwrap();
assert_eq!(result, Some(0));
let result = execute_trait_object(&cmd).unwrap();
assert_eq!(result, Some(0));
let boxed = Box::new(cmd);
let result = execute_boxed_trait_object(boxed).unwrap();
assert_eq!(result, Some(0));
}
}
By using trait object, we can avoid having too many generic parameters.
We can also return an instance of a trait by using trait object
if we use Box<dyn Executor> and put trait object on heap, then it is slower due to external memory access