Why We Rewrote Snapshot in Rust: Zero-Latency Screen Capture
Why NHR Soft rewrote Snapshot in Rust for zero-latency screen capture, lower memory overhead, stable frame timing, and a more trustworthy desktop capture pipeline.
Published
July 23, 2026
Reading Time
4 min read
Article Size
742+ words
Social Tags
Share This Article
Article Overview
This article is part of the NHR Soft knowledge base and is structured to help readers understand the topic quickly, review practical steps, and share product or engineering insights with confidence.
When we first shipped Snapshot in 2022, we wrote it in Go. Go was a pragmatic choice: fast to write, reasonable runtime performance, and a great standard library. For most tasks, it's still our go-to language. But screen capture has a specific problem that Go's garbage collector simply couldn't solve: latency spikes.
The Latency Problem
Screen capture is a real-time operation. Users expect it to feel instant — that means the entire capture-to-clipboard pipeline needs to complete in under 50ms. With Go, we were hitting 40–45ms on average, but with occasional GC pauses spiking to 120ms or more. On a fast machine with low memory pressure, this was fine. On an older MacBook or a Windows laptop with background processes, it felt sluggish.
"The GC pause isn't the enemy — unpredictability is. A tool that's usually fast but occasionally slow erodes user trust faster than one that's consistently average."
Choosing Rust
We evaluated three paths:
- Tune the Go runtime — GOGC tuning can reduce pauses but can't eliminate them
- C/C++ — Fast, but we wanted memory safety without manual bookkeeping
- Rust — Zero-cost abstractions, no GC, and a borrow checker that catches memory bugs at compile time
Rust was the obvious choice. The borrow checker is notorious for its steep learning curve, but once you internalise the ownership model, it produces code that is provably safe and fast.
Architecture Changes
The Rust rewrite introduced three key architectural changes:
- Platform-native capture APIs — We use
CoreGraphicson macOS andDXGI Desktop Duplicationon Windows, called directly via Rust FFI bindings. - Zero-copy pipeline — Captured frames move through the pipeline without unnecessary memory copies using Rust's ownership system.
- WebAssembly annotation module — The annotation layer compiles to WASM, keeping the core binary lean while allowing web-based rendering.
Results
After the rewrite, our p99 latency dropped from 120ms to under 8ms. Average capture time sits at 3–5ms. The binary is also 40% smaller than the Go build, which matters for distribution.
The Rust ecosystem around system-level programming has matured enormously in the last two years. If you're building performance-critical native tooling, it's no longer a niche choice — it's the right one.
What changed during the migration
The rewrite was not just a language swap. We also redesigned the capture lifecycle so that region selection, frame acquisition, annotation handoff, and clipboard export each had their own measurable boundary. That let us identify exactly where latency was introduced and remove guesswork from optimization. Once the system was instrumented properly, we learned that allocations and cross-thread copies mattered more than the raw capture API call itself.
Benchmarking methodology
To keep our results honest, we tested on multiple hardware profiles: Apple Silicon laptops, older Intel-based MacBooks, and mainstream Windows laptops used by office teams. We measured cold start, warm capture, repeated region capture, and export to clipboard. We also repeated runs while common background applications were active, because real user environments are noisy. A benchmark that only looks good on a clean developer machine is not useful for production software.
- Cold start timing measured the time from opening Snapshot to first successful capture.
- Warm capture timing measured repeated capture performance after APIs and buffers were already initialized.
- Tail latency tracked slowest events, because users remember spikes more than averages.
- Memory stability checked whether long sessions introduced drift or fragmentation.
Why this matters for users
For users, the benefit is not “Rust” as a badge. The benefit is trust. When you hit a shortcut to grab part of your screen, the tool should react instantly, every time. That matters for product teams collecting feedback, support teams documenting bugs, developers writing release notes, and anyone moving quickly between applications. Consistency creates confidence, and confidence is a product feature.
Lessons we would reuse in future native tools
- Design for observability before you begin performance tuning.
- Optimize the full pipeline rather than only the obvious hot path.
- Choose a language that matches runtime constraints, not just team habit.
- Measure p95 and p99 latency, not only average execution time.
FAQ
Did Go fail for this project?
No. Go was productive for early versions. The issue was that Snapshot eventually needed more predictable low-level behavior than a garbage-collected runtime could comfortably provide.
Is Rust required for every desktop utility?
Not at all. We still use other languages where they offer faster iteration and the runtime profile is appropriate.
What is the biggest practical gain after the rewrite?
Lower latency spikes, smaller memory overhead, and a capture experience that feels more immediate on older hardware.
Public Discussion
Name and Comment
Share your thoughts on this article. Your name and comment will be published right away on the page.
Published Comments
0
Start the conversation
No comments yet. Be the first person to leave a public note on this article.