Same override pattern as connect(): required, no default body, five
completely different bodies.
- BambuGenericPrinter::set_fan_speed_impl() is shared by BambuV1Printer and
BambuV2Printer (composition reuse, same as connect_impl) - both speak
the same MQTT gcode-injection mechanism.
- PrusaLinkPrinter and PrusaSerialPrinter both send the same M106 gcode
but over different transports (HTTP command injection vs. raw serial
bytes) - they share the percent->PWM conversion despite having unrelated
connect() implementations.
- KlipperPrinter uses a hypothetical Moonraker-native endpoint that takes
a percentage directly - no PWM conversion at all, since that math only
applies to the gcode-speaking vendors.
percent_to_pwm() lives in mod.rs as a private free function rather than a
trait default: it's genuine shared logic, but only for the subset of
vendors that need it, which is exactly the case a trait default can't
express cleanly. Visible to bambu.rs/prusa.rs via super:: because Rust's
module privacy reaches into child modules.
Verified with cargo check --all-targets and a full run of
printer_polymorphism: 50% converges on the same PWM value (127) across all
three gcode-based printers, Klipper's native path takes 0.50 directly.