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.