GOTCHAS.md#
Hard-won lessons, edge cases, and "watch out for" patterns. Organized by domain.
Referenced from AGENTS.md and CONTRIBUTING.md -- read the relevant section before working in that area.
Unsafe Code#
- There must be exactly one
unsafeblock in the entire crate (insrc/map.rs). Do not add new ones without an issue discussion first. #![deny(clippy::undocumented_unsafe_blocks)]is enforced -- everyunsafeblock must have a// SAFETY:comment explaining why the invariants are upheld.- The crate is NOT
#![forbid(unsafe_code)]-- it IS the unsafe boundary. Downstream consumers use#![forbid(unsafe_code)]and depend on this crate to encapsulate the unsafe call.
Clippy Lints#
missing_const_for_fn(nursery, promoted to deny via-D warnings) -- functions insideconstblocks (e.g., compile-timeSend + Syncassertions) must be markedconst fn.option_if_let_else(nursery, promoted to deny via-D warnings) -- preferOption::map_or/map_or_elseovermatchonOptionfor simple transformations.indexing_slicing= warn (promoted to deny via-D warningsin CI) -- direct slice indexing (e.g.,chunk[..n]) is rejected. Use#[allow(clippy::indexing_slicing)]with a justification comment when bounds are provably safe;.get()can cause borrow-checker issues with mutable slices.unseparated_literal_suffix= warn (promoted to deny via-D warningsin CI) -- literal suffixes must use underscore separation (0_u8, not0u8).multiple_crate_versions= warn -- the dependency tree still pulls a duplicategetrandom(v0.3.xviarand_core,v0.4.xviatempfile). The justfilelint-rust/lint-rust-minrecipes pass-A clippy::multiple_crate_versionsafter-D warningsto prevent over-promotion. Do not change the Cargo.toml level todenyorallow. (Before fs4 1.1, the duplicate waswindows-sys; fs4 1.1 andrustixconverged on the samewindows-sys, so that source is gone -- the allow remains forgetrandom.)- The
deny.tomlskiplist no longer needs awindows-sysentry (removed once fs4 1.1 / rustix converged -- cargo-deny flagged it as anunnecessary-skip).cargo deny checkpasses withbans ok; the remaininggetrandomduplication does not trip the bans policy. Re-add a targetedskiponly if a future duplicate actually failscargo deny check. unwrap_used= deny,panic= deny -- these fail the build in library code. Use?or proper error handling.expect_used= warn -- prefer?over.expect()in library code.- Test modules need
#[allow(clippy::unwrap_used, clippy::expect_used)]on themod testsblock. - Full pedantic/nursery/cargo groups are enabled -- new code may trigger unexpected warnings from lint groups you didn't explicitly enable.
uninlined_format_argsis denied (via pedantic) -- use"{var}"not"{}", varin format strings.exit= deny (via nursery) --std::process::exit()in subprocess helper tests needs#[allow(clippy::exit)]on the test function.
Rustdoc#
loadis both a module name (mod load) and a re-exported function (pub use load::load). In doc comments from submodules, link withcrate::load()(parens disambiguate to the function) -- barecrate::loaderrors as ambiguous.cargo doc --document-private-itemsis used in CI. Links to private modules (e.g.,[map]) will error because they resolve only with--document-private-itemsbut break without it. Link to public items instead (e.g.,[map_file]).- Redundant explicit link targets (e.g.,
[map_file](crate::map_file)) are denied. Let rustdoc resolve intra-doc links automatically.
FileData Enum#
FileDatais#[non_exhaustive]-- match arms must include a wildcard. Adding a variant is a non-breaking change.FileData::Mapped(Mmap, File)carries both the memory map and the file handle (for advisory locking). Use..inmatches!patterns (e.g.,matches!(data, FileData::Mapped(..))), not_.FileDatamust implementDebug(required byunwrap_err()in tests and generally expected for public types).- Both
Deref<Target=[u8]>andAsRef<[u8]>are implemented -- consumers should use&*dataordata.as_ref()interchangeably.
CI#
Cargo.lockis gitignored (library crate convention). Do not commit it -- release-plz will refuse to run ifCargo.lockis both committed and gitignored.- Mergify
queue_rulesrequires bothqueue_conditionsandmerge_conditions.merge_methodbelongs onqueue_rules, not thequeueaction. Parallel checks are configured viamerge_queue.max_parallel_checksat the top level, not insidequeue_rules. - The
Cargo.tomlexcludelist controls what ships to crates.io. Keep it comprehensive -- CI config, tooling, and non-essential docs should be excluded. Runcargo package --list --allow-dirtyto audit. - cargo subcommands installed via mise (e.g., cargo-dist) must be invoked as standalone binaries (
dist plan) not cargo subcommands (cargo dist plan) -- cargo can't find mise-managed subcommands. cargo-distplan/build does nothing for a library crate (no binary targets). That's whydist-planis excluded fromjust ci-check.- Mergify merge protections evaluate from the main branch config, not the PR branch.
- The docs workflow builds rustdoc with
--document-private-items-- see Rustdoc section above for link pitfalls. - Always verify pinned action SHAs with
gh api repos/{owner}/{repo}/commits/{sha} --jq '.sha'before using them. Do not fabricate SHAs.
Local CI with act#
actdefaults topushevent -- schedule-only workflows needworkflow_dispatchpassed as the event argument.actDocker containers run as root -- Unix permission tests (e.g.,chmod 000β expectPermissionDenied) false-positive because root bypasses file permission checks.- Use
--container-architecture linux/amd64on Apple Silicon to avoid image pull failures.
Pre-commit Hooks#
mdformatreformats markdown on commit -- if your commit is rejected, re-stage the reformatted files and create a new commit. Do not amend.cargo-sortreorders and aligns keys inCargo.toml-- same pattern: re-stage and recommit.- The
.claude/directory is excluded from mdformat.
Platform / mmap#
fs4::FileExt::try_lock_shared()(fs4 1.x) returnsResult<(), fs4::TryLockError>--Ok(())means the lock was acquired,Err(TryLockError::WouldBlock)means contention, andErr(TryLockError::Error(io))is a genuine I/O failure. Match all three. (fs4 0.x returnedResult<bool>fromfs4::fs_std::FileExt; the trait moved to the crate root and the return type changed in 1.0.)flock()locks do not conflict within the same process on macOS -- lock contention tests must spawn a subprocess (e.g.,python3 -c "import fcntl; ...") to hold the exclusive lock.- Empty files cannot be memory-mapped --
map_file()returns an error for zero-length files. This is a deliberate pre-flight check. - SIGBUS from concurrent file truncation is a known, documented limitation -- it cannot be fully prevented without advisory file locking. It is explicitly out of scope for security reports (see SECURITY.md).
map_file()acquires a shared advisory lock viafs4::FileExt::try_lock_shared()before mapping. Lock contention returnsWouldBlock. The lock is held by theFileinsideFileData::Mappedand released on drop.
Fuzzing#
- The
__fuzzfeature flag exposesread_boundedas#[doc(hidden)] pubfor fuzz targets. Do not use this feature in production or library code. read_boundedispub fninsrc/load.rsbut the module is private β it is only reachable outside the crate when re-exported via#[cfg(feature = "__fuzz")]inlib.rs.- Fuzz targets live in
fuzz/(separate workspace, edition 2021). They require nightly andcargo-fuzz. - The
fuzz/Cargo.tomlusesedition = "2021"(not 2024) becausecargo-fuzz/libfuzzer-sysrequires nightly and edition 2021 avoids compatibility issues. - Property tests (
proptest) run on stable and are part of the normal test suite. Theread_boundedproptest is a unit test insidesrc/load.rs(not intests/) because it needs access to the private function. rust-toolchain.tomloverridesrustup default-- CI workflows that need nightly must setRUSTUP_TOOLCHAIN: nightlyas an env var on the run step, not just install the toolchain.read_boundedneeds#[allow(unreachable_pub)]and#[allow(clippy::missing_errors_doc)]because it'spub(for re-export) in a private module -- clippy flags both even though the function is#[doc(hidden)].#[derive(Arbitrary)]generates code referencingarbitrary::by path --use libfuzzer_sys::arbitrary::{self, Arbitrary}requires theselfimport. Do not remove it; the derive macro will fail without it.
load / load_stdin#
load("-")delegates toload_stdin(Some(1_073_741_824))(1 GiB default cap). Callers needing a custom limit should callload_stdin(Some(n))directly.load_stdin(max_bytes)takesOption<usize>--None= unlimited,Some(n)= hard cap returningInvalidDataon overflow.- The bounded read uses a 1-byte probe at the cap boundary to distinguish exact-fit EOF from genuine overflow.
- Do not call
load("-")in unit tests β it reads real process stdin, which may block or behave inconsistently across test runners. Useread_boundedwith aCursorto test the stdin data path, andresolve_sourceto test the routing logic. - To integration-test
load("-"), spawn the test binary as a subprocess with piped stdin and an env-var guard. The test harness writes its own output to stdout, so use a temp file (not stdout) for child-to-parent data transfer.