Skip to content

Drop third-party dependencies from the default build (v1.1.0) - #17

Open
jamesgober wants to merge 2 commits into
mainfrom
deps/zero-third-party-core
Open

jamesgober wants to merge 2 commits into
mainfrom
deps/zero-third-party-core

Conversation

@jamesgober

Copy link
Copy Markdown
Owner

Do not merge, tag or publish yet: owner decisions needed (see below).

Summary

With default features, error-forge pulled 8 third-party crates into every dependent's normal build, while the dependents (throttle-net, rate-net, better-bucket and the rest) use only the ForgeError trait. Both always-on dependencies are now gone. The default graph is error-forge plus its own error-forge-derive, which has no dependencies of its own. No public item was added to, removed from or changed in error-forge, and default = [] is unchanged.

What needed it (1.0.2) Crates Replacement
define_errors! constructor names (fn [<$variant:lower>]) pastey ~130-line proc macro in error-forge-derive on proc_macro alone (error_forge::__private::paste!, internal). syn/quote/proc-macro2 move behind a new default-on derive feature of the derive crate; error-forge depends on it with default features off
recovery::CircuitBreaker internal state parking_lot, lock_api, parking_lot_core, scopeguard, smallvec, cfg-if, windows-link/libc std::sync::Mutex via PoisonError::into_inner, so it still never panics. The success path now takes the lock once instead of twice

All other third-party deps (serde, log, tracing, async-trait, rand, and syn for derive) were already behind opt-in features and are unchanged.

Evidence

cargo tree -p error-forge -e normal, default features, excluding self

Target v1.0.2 this branch
Windows x86_64 8 third-party 1 in-house (error-forge-derive, 0 deps)
Linux x86_64 8 third-party 1 in-house
macOS aarch64 8 third-party 1 in-house
all features (Windows) 36 29

Clean debug build of a dependent (Windows, CARGO_INCREMENTAL=0, median of 5): -j32 1.09 s → 0.78 s; -j1 2.67 s → 0.99 s.

CircuitBreaker::execute (release, no-op closure, ns per call):

Win Win, 8 threads Linux Linux, 8 threads
1.0.2 (parking_lot, 2 locks) 41.2 346 38.9 136
branch with parking_lot (1 lock) 33.8 260 30.6 101
this branch (std, 1 lock) 34.2 182 31.3 122

Uncontended, std and parking_lot are equal. Under heavy synthetic contention parking_lot is about 20 ns/call better on Linux and about 80 ns/call worse on Windows. Next to the I/O a breaker guards, neither difference matters, so it doesn't meet the "necessary and measurably better" bar. Compared with 1.0.2, the branch is faster everywhere.

Gates

  • cargo fmt --all -- --check
  • clippy -D warnings on 1.95 and 1.99: all-features, default, no-default, derive crate alone
  • Tests: all 9 CI matrix combos, plus error-forge-derive, async log tracing jitter, and all-features (140, incl. trybuild UI) on Windows 1.95. Linux 1.99 all-features: 140
  • MSRV 1.81.0: build plus 139 tests (UI skipped, as in CI); minimal-versions emulation builds on 1.81
  • RUSTDOCFLAGS=-D warnings cargo doc
  • cargo audit --deny warnings clean; cargo deny check ok (same warnings as main)
  • cargo semver-checks -p error-forge --baseline-version 1.0.2: 196 pass, no semver update required. The tool can't check proc-macro crates, so error-forge-derive was reviewed by hand: it only adds a default-on derive feature and a hidden macro
  • cargo publish --workspace --dry-run: both crates package and verify
  • New CI gate: the default dependency graph must contain no third-party crate (--target all)

New tests: tests/paste_test.rs covers constructor naming (acronyms, r# variants, non-ASCII, digits) and the macro's modifiers and nesting. tests/ui/fail/paste_bad_modifier.rs covers each error path. A Type variant gives the same expected identifier, found keyword error as 1.0.2 (checked against the published crate).

Decisions needed from the owner

  1. STABILITY.md contract. The crate's STABILITY.md (binding per DIRECTIVES D-5) said "Removing any of the always-on deps requires a 2.0.0 bump". It also listed "adding a runtime dependency that is not gated behind an opt-in feature" as breaking; error-forge-derive goes from optional to always-on. VERSIONING.md, which is authoritative across the portfolio, classes neither change as breaking: the "would user code still compile?" test passes, and semver-checks agrees. The 1.0.2 removal of thiserror set the same precedent. This PR narrows those clauses to third-party dependencies and public-API exposure, with a paragraph explaining why. Please confirm this is MINOR rather than MAJOR.
  2. Version and schedule. The PR bumps to 1.1.0: the derive crate gains a feature, and the perf gains are worth calling out. The 2.0 plan already reserves 1.1.0 for the runway (R1–R10). Either ship this as 1.1.0 and move the runway to 1.2.0, or hold this PR and fold the runway items in before tagging.
  3. The open Dependabot PR (cargo-minor-and-patch) also touches Cargo.lock, so whichever merges second gets a trivial lockfile conflict.

After approval, the release steps are: date the CHANGELOG section, tag v1.1.0, publish error-forge-derive and then error-forge. Local notes are in .dev/audits/2026-10-14.md and .dev/release/v1.1.0.md (gitignored).

James Gober added 2 commits October 14, 2026 05:13
With default features error-forge pulled eight third-party crates into
every dependent's build (pastey, parking_lot, lock_api, parking_lot_core,
scopeguard, smallvec, cfg-if, and windows-link or libc). The 14 local
dependents use only the ForgeError trait. Both always-on dependencies
are gone; the default graph is now error-forge plus its own
error-forge-derive, with no third-party crate on any target.

pastey: define_errors! needs it only to lowercase variant names into
constructor names, which macro_rules cannot do. error-forge-derive now
carries a small proc macro for that, written against proc_macro alone
(error_forge::__private::paste!, not public API). Its syn, quote and
proc-macro2 dependencies move behind a new default-on `derive` feature,
and error-forge depends on it with default features off, so the derive
still costs nothing unless enabled. The error-forge-derive feature that
Cargo used to create implicitly is declared explicitly with the same
meaning; cargo semver-checks against 1.0.2 passes all 196 checks.
Constructor names, spans and keyword errors match pastey.

parking_lot: CircuitBreaker now uses std::sync::Mutex and takes it
through PoisonError::into_inner, so no method can panic on a poisoned
lock, as before. A successful closed-state call no longer takes the
lock a second time in on_success, which only acts on the half-open
probe. Against 1.0.2, execute is 17% (Windows) / 20% (Linux) faster
uncontended and 47% / 10% faster with eight threads on one breaker.
With identical single-lock code parking_lot is within 1 ns uncontended,
about 20 ns/call faster contended on Linux and 80 ns slower on Windows.

A clean debug build of a default-features dependent drops from 1.09 s
to 0.78 s wall (32 jobs) and 2.67 s to 0.99 s on one job.

CI gains a gate that fails if the default dependency graph of
error-forge contains a third-party crate on any target. STABILITY.md,
architecture.md, COMPARISON.md, migration.md, SECURITY.md and the README
describe the new dependency set; STABILITY.md narrows its dependency
rules to what dependents can observe.
Bump error-forge and error-forge-derive to 1.1.0, require error-forge-derive 1.1.0, and point the README install snippet at 1.1.0. The CHANGELOG entry stays under [Unreleased] until the release is approved and dated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant