4.2 KiB
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, noBox<dyn Error>as a crutch. - Use
thiserrorwhere it helps (deriveError+Display), but it's not mandatory. Hand-written impls are fine too. - Use
Resultand?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.
JoinErrortoIo), but always wrap it in a typed variant with context.
No Panics
- No
unwrap(),expect(), orpanic!()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::panicworkspace-wide. These are the guardrails for the no-panic rule. - Deny
clippy::dbg_macro,clippy::print_stdout,clippy::print_stderr. Usetracinginstead. - Enable
clippy::must_use_candidate. If a function returns aResult,Option, or any value the caller shouldn't silently ignore, it gets#[must_use]. - Run
cargo clippy -- -D warningsin CI. Warnings are errors.
Unsafe Code
unsafeblocks 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
tracingandtracing-subscriber. Noprintln!,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
JoinSetover fire-and-forgettokio::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-coreis an embeddable library. Treat its public API like a contract.- Don't make things
pubunless they need to be. Default topub(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
Copytypes,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-coregets 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-corestays 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_*), thenself/super/crate. - Blank line between each group.
rustfmthandles the rest. Don't fight it.