Polymorphism and Map operations

Hi,

I am an experienced java developer and new to Rust. Since I started learning Rust I try to write idiomatic Rust but the old habits of the past are still haunting me! :slight_smile:

In the following code snippet, I try to parse a cmd line argument parse and validate it as an existing directory and based on the results create a trait implementation and finally call its method. I use the match early return technique and method chaining. I have also thought to try polymorphism (just for learning) but it backfired. It took me a while to get the map_or_else closure returns to pass compilation. I guess the Box polymorphic return is excessive. I could use an if-else statement to avoid polymorphism. So is there a better way to write this logic? Thank you in advance for your input!

pub trait Printer {
fn print(&self, templates: &[Template], ctx: &Context) -> Result<(), String>;
}

pub struct ConsolePrinter {}

impl Printer for ConsolePrinter { ... }

pub struct FilePrinter {
   dir: PathBuf,
}

impl Printer for FilePrinter { ... }

fn main() {
  ...
  match args.first().map(|dir| PathBuf::from(dir)) {
    Some(path) => if !path.is_dir() {
        return Err(format!("Path is not a directory: {}", path.display()));
    } else if !path.exists() {
        return Err(format!("Directory doesn't exist: {}", path.display()));
    } else { Some(path) },
    None => None,
  }.map_or_else(
    || Box::new(ConsolePrinter::new()) as Box<dyn Printer>,
    |p| Box::new(FilePrinter::new(p)))
    .print(release.templates(), &context)
}

In Rust lightweight non-boxed alternative to polymorphism is enum:

enum Printer {
  FilePrinter(FilePrinter),
  ConsolePrinter(ConsolePrinter),
}

If you need "open" and run-time polymorphism, then Box is usually the right solution (that's essentially what Java does).

If you need runtime polymorphism inside a single scope, you can avoid boxing:

let file_printer;
let console_printer;
let chosen_printer: &dyn Printer;

if random() {
   file_printer = FilePrinter::new();
   chosen_printer = &file_printer;
} else {
   console_printer = ConsolePrinter::new();
   chosen_printer = &console_printer;
}
4 Likes

Thanks for the suggestions! I haven't thought of enum. It's very appropriate for this use case.

1 Like