Claude on Rust performance pitfalls
Captured chat with Claude Sonnet on 2026-04-02, after I asked it to audit our WASM rendering path. Cleaned, kept the calls I'm acting on.
The setup
I'd been seeing the editor stutter on docs over ~30KB. The render call (comrak → WASM → JS) sat at 18-25ms p95 — not catastrophic, but mismatched with how snappy the UI should feel. So I dumped render.rs, postprocess.ts, and the perf trace into Claude and asked it to find three places we're leaving cycles on the floor.
What Claude actually said
It went past the obvious "use &str more" advice and pointed at:
- Allocation on hot paths.
flavor.rscallsString::new()once per detection pass even when the input is unchanged. Most renders hit the same flavor twice in a row (initial mount + autosave debounce), so we're paying for an empty allocation that immediately gets dropped. - Bound-checked loops in postprocess. The per-line scan in
postprocess.tsruns withoutget_uncheckedin places where we already validated the bound. JS engines do their own bounds elision on monomorphic code, but the V8 + WASM boundary forfeits it. - JSON marshalling for the syntax flavor metadata. We re-serialise the flavor descriptor on every render. The descriptor is static across a session. One
JSON.stringifyper frame is "free" in isolation, but on a 60Hz update loop it adds up.
What I'm doing about it
- Skipping (1) for now. The win is < 1% of total render time and the change costs LOC + readability. Filed as
FLAVOR_ALLOC_DEFERif we ever care. - Acting on (2) this week. The per-line loop is straightforward and the gain is measurable in the editor profile.
- Caching the flavor descriptor at module scope (fix for (3)). Tiny change, real win.
What I'd ask differently next time
I gave Claude the trace inline. It would've spotted (1) and (3) faster if I'd also handed it the renderer's call shape — how often render() runs per keystroke, autosave cadence. The "what's expensive" question lands better when the upstream pattern is on the table too.