Files
pikl/docs/CODING_STANDARDS.md

4.2 KiB

Coding Standards

Rules of the road for pikl-menu. If something's not covered here, use good judgement and keep it consistent with the rest of the codebase.

Error Handling

  • No anyhow. Every error type is a proper enum. No stringly-typed errors, no Box<dyn Error> as a crutch.
  • Use thiserror where it helps (derive Error + Display), but it's not mandatory. Hand-written impls are fine too.
  • Use Result and ? everywhere. Convert between error types with .map_err(YourVariant). Keep it simple, no closures when a variant constructor will do.
  • It's fine to stringify an error when crossing thread/async boundaries (e.g. JoinError to Io), but always wrap it in a typed variant with context.

No Panics

  • No unwrap(), expect(), or panic!() in library or binary code. CI lints enforce this.
  • We know IO and FFI can't be made perfectly safe. The goal is to not add unsafe situations. Handle failures, don't crash through them.

Clippy & Lints

  • Deny clippy::unwrap_used, clippy::expect_used, clippy::panic workspace-wide. These are the guardrails for the no-panic rule.
  • Deny clippy::dbg_macro, clippy::print_stdout, clippy::print_stderr. Use tracing instead.
  • Enable clippy::must_use_candidate. If a function returns a Result, Option, or any value the caller shouldn't silently ignore, it gets #[must_use].
  • Run cargo clippy -- -D warnings in CI. Warnings are errors.

Unsafe Code

  • unsafe blocks need a // SAFETY: comment explaining why it's sound.
  • Keep unsafe surface area minimal. If there's a safe alternative that's not meaningfully worse, use it.

Logging & Diagnostics

  • All logging goes through tracing and tracing-subscriber. No println!, eprintln!, dbg!(), or manual log files.
  • No temp files for logs. No /tmp/pikl-debug.log. None of that.
  • We'll eventually build a debug subscriber tool (think tokio-console style). Design logging with structured events in mind.

Async Conventions

  • Tokio is the async runtime. No mixing in other runtimes.
  • Prefer structured concurrency with JoinSet over fire-and-forget tokio::spawn. If you spawn a task, you should be able to cancel it and know when it's done.
  • Be explicit about cancellation safety. If a future holds state that would be lost on drop, document it. This matters for hooks and IPC especially.
  • Use tokio::select! carefully. Every branch should be cancellation-safe or documented as not.

Public API Surface

  • pikl-core is an embeddable library. Treat its public API like a contract.
  • Don't make things pub unless they need to be. Default to pub(crate) and open up intentionally.
  • Re-export the public interface from lib.rs. Consumers shouldn't need to reach into submodules.
  • Breaking changes to the public API should be deliberate, not accidental side effects of refactoring.

Performance

  • Avoid cloning where possible. Cheap copies (small Copy types, Arc::clone) are fine. Cloning strings and vecs as a convenience? Try harder.
  • Use zero-copy patterns: borrowed slices, Cow, views into existing data.
  • Prefer iterators over collecting into intermediate Vecs. Chain, filter, map. Only collect when you actually need the collected result.

Testing

  • Every module in pikl-core gets unit tests. Every filter, hook lifecycle event, I/O format.
  • Integration tests exercise the real binary with real pipes. No mocked IO at that level.
  • Cross-platform: tests must pass on Linux and macOS. Gate platform-specific tests with #[cfg].
  • When the GUI frontend lands, add snapshot/visual regression tests.
  • Build toward an e2e framework that can drive the full tool.

Dependencies

  • pikl-core stays free of rendering, terminal, and GUI deps. It's an embeddable library.
  • Prefer pure-Rust deps. C deps behind feature flags only (e.g. pcre2).
  • Be intentional about what you pull in. If the standard library does it, use the standard library.

Import Ordering

  • Group imports in this order: std, external crates, workspace crates (pikl_*), then self/super/crate.
  • Blank line between each group.
  • rustfmt handles the rest. Don't fight it.