Levente Bokor
Levente Bokor

Reputation: 80

how to store multiple instances of dyn Trait, where Trait has associated types?

I'm learning rust, and a project idea I had was to write a command line parsing framework, that enables the use of commands and options. I have previously done this same project in other languages.

I have defined my option struct as such:

pub struct Arg<T: FromStr> {
    // fields
}

In a command, I wish to store multiple Arg instances, that may have different generic types. To do this, I resorted to creating a non-generic trait for Arg<T>, to then store them as dyn Arguments.

pub trait Argument {
    type Target: FromStr;
    type ParseError;

    // other trait members ...
}

This trait is implemented for Arg<T> as such:

impl<T: FromStr> Argument for Arg<T> {
    type Target = T;
    type ParseError = T::Err;

    // other members ...
}

And inside my Command struct, I wished to store Argument instances like this:

pub struct Command {
    args: Vec<Box<dyn Argument<Target = dyn Any, ParseError = dyn Any>>>
}

This compiles so far. However, when I have an actual Arg<T> instance, I can't seem to cast them to dyn Argument<Target = dyn Any, ParseError = dyn Any>. I realize this approach is flawed for multiple reasons, but I can't find a working solution that would enable the behaviour I'm looking for.

Is there a way to implement this behaviour?

Upvotes: 0

Views: 245

Answers (1)

true equals false
true equals false

Reputation: 793

I guess what you want to do is be able to create a template for how to parse one argument and put several in a vector. To do this, the template should not be something like Box<dyn Argument>, it should be something like Box<dyn ArgumentBuilder>.

Furthermore, the error should probably not be Box<dyn Any>, it should be Box<dyn ErrorTrait> where ErrorTrait defines what you should be able to do with an error. For example, an error should probably implement std::error::Error.

Here is a proof of concept (playground):

use core::marker::PhantomData;
use std::fmt::Debug;
use std::str::FromStr;

/// A parsed arguemnt
#[derive(Debug)]
pub struct Arg<T>
where
    T: Debug,
{
    data: T,
    // other fields
}

/// The trait an `Arg` thing implements (to be abe to put it in a dyn)
pub trait Argument: Debug {
    // other trait members ...
}

impl<T> Argument for Arg<T> where T: Debug {}

/// The thing which creates an `Arg`
pub struct ArgConstructor<T>
where
    T: FromStr + Debug + 'static,
    T::Err: ParsingError,
{
    _t: PhantomData<T>,
}

impl<T> ArgConstructor<T>
where
    T: FromStr + Debug + 'static,
    T::Err: ParsingError,
{
    /// A constructor for easier usage
    fn new() -> Self {
        Self { _t: PhantomData }
    }
}

// Put additional traits you want your errors to implement here
/// The trait errors should implement
pub trait ParsingError: std::error::Error {}

// implement the error trait for all errors for which it is possible
impl<E: std::error::Error> ParsingError for E {}

pub trait ArgumentConstructor {
    fn from_str(&self, s: &str) -> Result<Box<dyn Argument>, Box<dyn ParsingError>>;
}

/// The trait an `ArgConstructor` implements (to be able to put it in a dyn)
impl<T> ArgumentConstructor for ArgConstructor<T>
where
    T: FromStr + Debug + 'static,
    T::Err: ParsingError,
{
    fn from_str(&self, s: &str) -> Result<Box<dyn Argument>, Box<dyn ParsingError>> {
        match T::from_str(s) {
            Ok(a) => Ok(Box::new(Arg { data: a })),
            Err(a) => Err(Box::new(a)),
        }
    }
}

pub struct Command {
    args: Vec<Box<dyn ArgumentConstructor>>,
}

fn main() {
    let command = Command {
        args: vec![
            Box::new(ArgConstructor::<u8>::new()),
            Box::new(ArgConstructor::<i32>::new()),
        ],
    };
    let strings = ["3", "-10"];
    let out: Vec<Box<dyn Argument>> = strings
        .iter()
        .zip(command.args.iter())
        .map(|(s, p)| p.from_str(s).unwrap())
        .collect();
    dbg!(out);
}

Upvotes: 2

Related Questions