Reputation: 80
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 Argument
s.
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
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