Summary
The Count / Find callback config structs leak internal concurrency
primitives into the public API, the same class of representation-leak the #495
FilesData / ConcurrentRunner reshape addresses — but #495 does not name these
structs.
Evidence
CountCfg.stats: Arc<Mutex<Count>> (src/count.rs:73)
CountCfg.filters: Arc<[String]> (src/count.rs:71),
FindCfg.filters: Arc<[String]> (src/count.rs:78)
Why 2.0-worthy
Arc<Mutex<Count>> forces every caller to materialize the exact shared-mutability
shape the library happens to use internally; we cannot switch to a lock-free
accumulator or a channel without breaking the signature. Hiding the sync
primitive behind an opaque collector is a breaking change → 2.0.
Proposed change
Fold these callback configs into the #495 terminal-processor reshape: hide the
Arc/Mutex behind an opaque collector type, and reconsider whether
filters: Arc<[String]> should be a borrowed slice or an opaque matcher.
Acceptance
CountCfg / FindCfg expose no Arc<Mutex<…>> / Arc<[String]> in their
public signatures.
Part of the pre-2.0 review (#505); companion to #495.
Resolution
Implemented in commit 3f3f573 (branch fix/issue-537, integration
fix/batch-2026-06-06).
Design (chosen with the user): two opaque newtypes in the library.
NodeTypeFilters(Arc<[String]>) — replaces CountCfg.filters and
FindCfg.filters. new(&[String]), From<Vec<String>>,
From<&[String]>, and a borrowed as_slice(&self) -> &[String]
accessor for the internal count/find calls.
CountCollector(Arc<Mutex<Count>>) — replaces CountCfg.stats.
new() / with_count(Count), Clone (cheap Arc bump), and
into_count(self) -> Count to recover the tally after the walk.
Why a borrowed &[String] field was rejected: CountCfg/FindCfg
are handed to ConcurrentRunner::run, whose Config: 'static bound
wraps the config in an Arc shared across worker threads. A borrow
tied to the caller's stack cannot satisfy 'static. The filter set
must stay owned; the newtype keeps it owned-but-opaque.
Poison recovery (#445) preserved. into_count handles
Arc::try_unwrap + Mutex::into_inner and degrades on poison via
PoisonError::into_inner rather than panicking; the inner-field
Callback::call path keeps the existing clear_poison recovery. The
inner fields are private, so neither newtype leaks Arc/Mutex in
its public signature.
Call sites updated: dispatch.rs (CountCfg/FindCfg
construction), the CLI Config.count_lock field type, and
run_command_count (now extracts via into_count() instead of a bare
Arc::try_unwrap().into_inner().expect() chain). Both newtypes are
re-exported from lib.rs.
Tests: the #445 poison-recovery test updated to the new
constructor (still exercises the poison path); into_count returns the
accumulated tally after concurrent updates; into_count degrades on a
poisoned mutex; NodeTypeFilters round-trips its patterns. Full
workspace cargo test --all-features, clippy, fmt, and rustdoc all
clean; CLI count/find verified end-to-end.
This is a breaking public field-type change, deferred to 2.0.
Summary
The
Count/Findcallback config structs leak internal concurrencyprimitives into the public API, the same class of representation-leak the #495
FilesData/ConcurrentRunnerreshape addresses — but #495 does not name thesestructs.
Evidence
CountCfg.stats: Arc<Mutex<Count>>(src/count.rs:73)CountCfg.filters: Arc<[String]>(src/count.rs:71),FindCfg.filters: Arc<[String]>(src/count.rs:78)Why 2.0-worthy
Arc<Mutex<Count>>forces every caller to materialize the exact shared-mutabilityshape the library happens to use internally; we cannot switch to a lock-free
accumulator or a channel without breaking the signature. Hiding the sync
primitive behind an opaque collector is a breaking change → 2.0.
Proposed change
Fold these callback configs into the #495 terminal-processor reshape: hide the
Arc/Mutexbehind an opaque collector type, and reconsider whetherfilters: Arc<[String]>should be a borrowed slice or an opaque matcher.Acceptance
CountCfg/FindCfgexpose noArc<Mutex<…>>/Arc<[String]>in theirpublic signatures.
Part of the pre-2.0 review (#505); companion to #495.
Resolution
Implemented in commit 3f3f573 (branch
fix/issue-537, integrationfix/batch-2026-06-06).Design (chosen with the user): two opaque newtypes in the library.
NodeTypeFilters(Arc<[String]>)— replacesCountCfg.filtersandFindCfg.filters.new(&[String]),From<Vec<String>>,From<&[String]>, and a borrowedas_slice(&self) -> &[String]accessor for the internal
count/findcalls.CountCollector(Arc<Mutex<Count>>)— replacesCountCfg.stats.new()/with_count(Count),Clone(cheap Arc bump), andinto_count(self) -> Countto recover the tally after the walk.Why a borrowed
&[String]field was rejected:CountCfg/FindCfgare handed to
ConcurrentRunner::run, whoseConfig: 'staticboundwraps the config in an
Arcshared across worker threads. A borrowtied to the caller's stack cannot satisfy
'static. The filter setmust stay owned; the newtype keeps it owned-but-opaque.
Poison recovery (#445) preserved.
into_counthandlesArc::try_unwrap+Mutex::into_innerand degrades on poison viaPoisonError::into_innerrather than panicking; the inner-fieldCallback::callpath keeps the existingclear_poisonrecovery. Theinner fields are private, so neither newtype leaks
Arc/Mutexinits public signature.
Call sites updated:
dispatch.rs(CountCfg/FindCfgconstruction), the CLI
Config.count_lockfield type, andrun_command_count(now extracts viainto_count()instead of a bareArc::try_unwrap().into_inner().expect()chain). Both newtypes arere-exported from
lib.rs.Tests: the #445 poison-recovery test updated to the new
constructor (still exercises the poison path);
into_countreturns theaccumulated tally after concurrent updates;
into_countdegrades on apoisoned mutex;
NodeTypeFiltersround-trips its patterns. Fullworkspace
cargo test --all-features, clippy, fmt, and rustdoc allclean; CLI
count/findverified end-to-end.This is a breaking public field-type change, deferred to 2.0.