How to combine different algorithms in one executable

111 Views Asked by At

To learn Rust, I have started to implement some of the Project Euler problems. Now I want to take the next step and create a console based user interface, which has the ability for running all or only specific problems. Another requirement is that the user should be able to pass optional parameters only to a specific problem.

My current solution is to have a Trait ProjectEulerProblem that declares for example run(). With that I can do something like this:

fn main() {
    let args: Args = Args::docopt().decode().unwrap_or_else(|e| e.exit());

    let problems: Vec<Box<problems::ProjectEulerProblem>> = vec![
        box problems::Problem1,
        box problems::Problem2
    ];

    match args.flag_problem {
        Some(x) => println!("Result of problem: {} is {}", x, problems[x-1].run()),
        None    => println!("No problem number given.")
    }
}

My question is, is there a way to get rid of the explicit problems vector initialization, maybe by using macros? Alternative ideas for implementing my application like described above are also welcome.

2

There are 2 best solutions below

0
On BEST ANSWER

You can use a macro with repetition to generate your list without having to type out the full path and name every time.

macro_rules! problem_vec(
    ($( $prob:tt ),*) => ({
        &[
            $(
                &concat_idents!(Proble, $prob),
            )*
        ]
    });
);
const PROBLEMS: &'static[&'static ProjectEulerProblem] = problem_vec!(m1, m2);

Note, you cannot simply use indices, because the concat_idents macro requires an identifier and numbers are not identifiers. concat_idents is also only available on nightly. On stable you need to give the entire struct name:

macro_rules! problem_vec(
    ($( $prob:ident ),*) => ({
        &[
            $(
                &problems::$prob,
            )*
        ]
    });
);

const PROBLEMS: &'static [&'static problems::ProjectEulerProblem] = problem_vec!(
    Problem1, Problem2
);

PlayPen

0
On

My mashup crate lets you define a concise way to build the problems array as problems![1, 2]. This approach works with any Rust version >= 1.15.0.


#[macro_use]
extern crate mashup;

mod problems {
    pub trait ProjectEulerProblem {
        fn run(&self);
    }

    pub struct Problem1;
    impl ProjectEulerProblem for Problem1 {
        fn run(&self) {
            println!("running Project Euler problem 1");
        }
    }

    pub struct Problem2;
    impl ProjectEulerProblem for Problem2 {
        fn run(&self) {
            println!("running Project Euler problem 2");
        }
    }
}

macro_rules! problems {
    ($($number:tt),*) => {{
        // Use mashup to define a substitution macro `m!` that replaces every
        // occurrence of the tokens `"Problem" $number` in its input with the
        // concatenated identifier `Problem $number`.
        mashup! {
            $(
                m["Problem" $number] = Problem $number;
            )*
        }

        // Invoke the substitution macro to build a slice of problems. This
        // expands to:
        //
        //     &[
        //         &problems::Problem1 as &problems::ProjectEulerProblem,
        //         &problems::Problem2 as &problems::ProjectEulerProblem,
        //     ]
        m! {
            &[
                $(
                    &problems::"Problem" $number as &problems::ProjectEulerProblem,
                )*
            ]
        }
    }}
}

fn main() {
    for p in problems![1, 2] {
        p.run();
    }
}