`run_streaming`'s `CaptureOnly` arm clones the entire child stdout into a field no caller reads
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 81.1k
- Forks
- 5.1k
- Avg merge
- 4d 21h
- Merged PRs (30d)
- 35
Description
Two full copies of the child's output are made on the buffered path, one of which appears to be unused entirely.
Provenance: AI-assisted source audit, human-directed, at v0.44.2 (700bdde). Reachability was traced by reading every call site; not measured with a profiler.
1. stream.rs:475 — a clone nobody reads
In the FilterMode::CaptureOnly arm:
filtered = raw_stdout.clone();
Tracing the consumers of StreamResult on that path:
src/core/runner.rs:107reads.raw/.raw_stdoutsrc/cmds/python/uv_cmd.rs:69likewise
.filtered is only read on the Streaming path (runner.rs:203, search.rs:435), which is a different arm. So for every CaptureOnly invocation the process allocates a second copy of the child's stdout — up to the 10 MiB RAW_CAP — and drops it unused. 34 modules route through runner::run.
2. stream.rs:491 — a third copy, retained alongside the first two
raw = format!("{}{}", raw_stdout, raw_stderr);
StreamResult then holds raw, raw_stdout and raw_stderr simultaneously. With the 10 MiB cap applied to each stream, that is up to ~30 MiB of duplicated capture even on the capped path, against the <5 MB figure in the module docs.
3. runner.rs:14-19 — two more to_string()
let filtered = filtered.to_string();
... never_worse(raw_for_tracking, &filtered).to_string()
Both are unconditional, and never_worse already returns a &str borrow, so the second copy exists only to satisfy the return type.
Suggested directions (untested — no programmer has reviewed these)
- Leave
.filteredempty (or make itOption<String>) in theCaptureOnlyarm, since nothing consumes it there. - Build
rawlazily — a method that concatenates on demand, or have consumers takeraw_stdout/raw_stderrdirectly, so all three are not resident at once. - Return
Cow<str>fromemit_guardedso the common case borrows.
None of these change behaviour; they are allocation removals. Related but separate: #3392 covers rtk grep, where the same copy-everything shape produces a measured 31× memory factor.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading stream.rs around lines 475-491 and runner.rs lines 14-19, then trace the CaptureOnly consumers named in runner.rs and src/cmds/python/uv_cmd.rs. Confirm which StreamResult fields are required and preserve the existing output while removing redundant allocations. Done means behavior remains unchanged and the unnecessary copies are no longer retained.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- cli, performance
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100