Difficulty with Default trait methods and Specialization

My use case is for advent_of_code_traits. Where I have a trait like

Solution<const Day: u32> {
    type Part1Output;
    type Part2Output;

    fn part1(input: /* we'll get there */) -> Self::Part1Output;
    fn part2(input: /* we'll get there */) -> Self::Part2Output;
}

The const generic parameters effectively mean I have a different trait for each day. The part1 and part2 fns are for each part of the day (in advent of code, each day there is a puzzle with 2 parts).

I'd like my users to implement the Solution trait and have a default run method that will...

  • read input from a file
  • run it through their solutions
  • print a summary to the console

But now we need to talk about parsing:

I'd like to support parsing the text input to a ParsedInput type for each part of a day, user's choice.
I'd also like to support using the same ParsedInput type for both days to avoid duplication.

Here's a trait for different parsing for each part of a day.

trait ParseEachInput<const Day: u32, const Part: u32> {
    type Parsed;

    fn parse(input : &str) -> Self::Parsed;
}

Let me alsot introduce the trait for when the parsing is the same for both parts:

trait ParseInput<const Day: u32> {
    type Parsed;

    fn parse(input : &str) -> Self::Parsed;
}

The difference is an extra const generic parameter Part when parsing Each part differently.

This lets the user implement it twice, once for each part, specifying 1 and then 2 for Part.
e.g. for day 7:

impl ParseEachInput<7, 1>
impl ParseEachInput<7, 2>

Now the actual problem (sorry this is taking so long, I'm honestly kind of mortified how convoluted this is :smiley: )

I can provide a default run implementation (somewhat) simply like this:

    fn run(input: &str) {
        let part1_parsed_input = <Self as ParseEachInput<Day, 1>>::parse_input(input);
        let part2_parsed_input = <Self as ParseEachInput<Day, 2>>::parse_input(input);
        //                                     ^^^^ note the Each
        let part1_output = <Self as Solution<Day>>::part1(&part1_parsed_input);
        let part2_output = <Self as Solution<Day>>::part2(&part2_parsed_input);

But run will always parse the input twice! :frowning:

What I want is a way to specialize the default run impl to be more efficient when it knows ParseInput is implemented (i.e. both parts want to share the same parsed input) like this:

    fn run(input: &str) {
        let parsed_input = <Self as ParseInput<Day>>::parse_input(input);
        //                          ^^^^^^^^^^ no Each

        let part1_output = <Self as Solution<Day>>::part1(&parsed_input);
        let part2_output = <Self as Solution<Day>>::part2(&parsed_input);

        Self::report(part1_output, part2_output);
    }

As it stands I will get a type mismatch when trying to compile the above:

error[E0308]: mismatched types
   --> src/lib.rs:330:59
    |
330 |         let part1_output = <Self as Solution<Day>>::part1(&parsed_input);
    |                                                           ^^^^^^^^^^^^^ expected ParseEachInput::Parsed, found ParseInput::Parsed
    |
    = note: expected reference `&<Self as ParseEachInput<Day, 1_u32>>::Parsed`
               found reference `&<Self as ParseInput<Day>>::Parsed`

This is a lot of code to throw around in a forum post.
There is a repo here and an issue

I saw that feature(specialization) is unsound. I'd be willing to use nightly if min_specialization applies here but I have been struggling to learn how to use it at all.

I would greatly appreciate any help for my admittedly bizarre and largely unnecessary use case :wink: Thank you for indulging me :slight_smile:

Here's one woefully uncommented way. The key parts are:

  • Solution trait has the parse-two-ways ParseInput trait as a super-trait
  • ParseInput::parse2 method also takes output of parse1 (so it can optionally just return it)
    • Thinking more about this now, it should probably be an Option<_>
  • Blanket impl of ParseInput trait for implementers of the parse-one-way SingleParser trait
  • And the default run is reordered to
    • parse part 1
    • run part 1
    • parse part 2
    • run part 2

I considered splitting the parts up with a const IsFirst: bool or something, but didn't pursue it.

1 Like

Thank you, this gives me some ideas. Passing the output of parsing part 1 into the parse method for part2 is not something I would have thought of any time soon :slight_smile: Pretty clever!

It's a shame to require a (seemingly) bizarre signature when you need to implement parsing for part2 separately to part1:

fn parse_part2(input: &str, other: Self::Part1Parsed) -> Self::Part2Parsed

I'll keep thinking if there's possibly a way to hide this implementation detail somehow from the user.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.