Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dispatching

Every “for every genre” call site iterates the GENRES slice declared in core/src/genre.rs:

pub static GENRES: &[&Genre] = &[AKARI, SLITHER, SUDOKU, TAKUZU];

CLI, eframe, and the bench harness all walk this slice; there is no parallel hand-maintained list to keep in sync. Adding a genre means one &'static Genre literal + one append to the slice (see Adding a genre).

The runtime Genre struct answers every per-genre question the harnesses used to out-source: parser, default density, dimension shape, generation pipeline, painter overlays, etc. Call sites dispatch through the fn-pointer fields:

// CLI command dispatch:
let Some(genre) = core::genre::by_name(&args.genre) else {
    bail!("unknown genre: {}", args.genre);
};
let p = (genre.from_janko_text)(&input)?;
let out = (genre.to_puzzlink_url)(&p)?;

// Iterating every genre:
for genre in core::genre::GENRES {
    println!("{} — {}", genre.name, genre.display_name);
}

penmark list-impls walks GENRES and prints genre.name per entry; the technique list is queried through core::algorithm::grader::technique_names(), which is itself genre- neutral now that techniques are object-safe.

The pre-collapse design had a for_each_genre! macro expanded at 11+ call sites across the workspace plus a parallel GenreEntry trait-object slice for runtime iteration. Both deleted. The full rationale lives in penmark/notes/genre-collapse.md.