Rust Glancer: a low-RAM Rust LSP via frozen analysis
Picture this: you open an editor, load a Rust workspace, and everything feels fine… until you notice the fans ramp up and your RAM graph keeps climbing. This is the moment a lot of us start asking a surprisingly low-level question: why does my IDE language server need so much memory just to answer “go to definition”?
Rust Glancer answers that question with a bold design choice: instead of keeping a full, incrementally-updated semantic model in RAM, it records a “frozen” analysis to disk and reloads just what each query needs. The result, at least for many “reasonable” projects, is reported idle RAM under 100 MB and very fast restarts after the workspace has already been indexed. (rust-glancer.github.io)
That trade-off isn’t free. The project is explicitly experimental and incomplete, and it sacrifices some “live while typing” precision in exchange for memory stability. ()
Below is the technical story of what’s going on, why it works, and what architectural lessons it suggests for building language servers that don’t melt your machine.
A quick mental model: what an LSP really does
A Language Server Protocol (LSP) is a standardized way for an editor (the client) to talk to a language-aware service (the server). Under the hood, messages use JSON-RPC (a request/response mechanism encoded as JSON). (microsoft.github.io)
When you ask for code completion, go-to-definition, or hover text, the client typically sends a query like:
- “Given this file and cursor position, what symbols could apply here?”
To answer, the server usually needs semantic information:
- where names are declared
- what each name’s type might be
- how traits and implementations relate
- how macros affect the final code shape
Different language servers get that semantic information in different ways. The big architectural lever is whether the server maintains an always-hot, always-in-memory model that is updated on every keystroke, or whether it keeps the “heavy facts” somewhere durable (disk) and swaps pieces in only when needed.
Why rust-analyzer is fast (and memory-hungry)
Rust Glancer is positioned as an alternative architecture to rust-analyzer. () To understand the trade-off, it helps to look at how rust-analyzer works at a high level.
Incremental query databases (salsa)
rust-analyzer is built around a query-based incremental computation framework called salsa. In plain language, salsa lets you define “queries” (think: functions) that map from inputs to outputs, and it caches results so repeated queries can be answered without recomputing everything. (rust-analyzer.github.io)
That caching is a performance superpower: it makes responses to many editor requests feel snappy.
But caching and incremental state are also memory-hungry by nature: you’re keeping a lot of intermediate data around because it might be needed again “soon,” and salsa is designed for on-demand incremental evaluation. ()
Syntax trees as green/red structures (rowan)
rust-analyzer also uses rowan, a library for syntax tree representation. The key property people like about rowan is its ability to support partial invalidation: when only part of a file changes, only the relevant parts of the tree need reparsing. ()
The downside is subtle: tree-like representations can lead to fragmentation, where the memory your process asks the OS for is larger than the “useful” payload you expect to be using. ()
Combine that with “keep everything resident in memory and avoid IO,” and you get a system that’s incredibly responsive, while also being hard to tame on smaller machines. (git.joshthomas.dev)
The Rust Glancer bet: freeze analysis, page it in on demand
Rust Glancer’s documentation describes the core idea very directly: it uses frozen workspaces. It performs indexing eagerly, stores results on the filesystem, and loads data only for the duration of each LSP query. ()
Why does this reduce RAM? Because “idle RAM usage” stops meaning “all semantic facts for the whole workspace are live in memory.” Instead, idle RAM becomes “only the working set for currently running query execution.” The project reports idle usage under 100 MB even for bigger projects. ()
There’s also a restart story. Since results are already serialized to disk after the initial indexing, restarting the editor doesn’t require re-indexing everything from scratch. ()
So the architecture flips the question from:
- “How do we keep an always-updated semantic model in RAM?”
to:
- “How do we serialize a semantic model well enough that reloads are fast and queries still work?”
“Frozen” doesn’t mean “stale forever”
The tricky part is keeping the frozen model aligned with your actual edits.
Rust Glancer addresses this by making indexing happen on save, not on every keystroke. During typing, it performs a “shallow” analysis and reuses the previous complete index, which keeps completions reasonably fast but delays the visibility of newly added items (imports, new structs/traits, etc.) until the file is saved. ()
That’s the trade-off you feel as a developer: instantaneous semantic updates become “instant-ish,” anchored to save boundaries.
Is that acceptable? The project’s own guidance suggests many people adapt quickly. ()
Trait solving, type inference, and the parts you can’t fake
A natural temptation in “low memory” designs is to reduce semantic depth. Rust Glancer doesn’t fully do that. It reports having a full indexing pipeline with type inference and a trait solver (Chalk). ()
Chalk is an experimental trait solver that models Rust’s trait system as logical constraints and searches for solutions. (rustc-dev-guide.rust-lang.org)
For an IDE, trait solving matters because many features—hover types, method resolution, completions—depend on understanding which impl blocks apply.
So Rust Glancer doesn’t dodge the hardest semantic work; instead, it tries to pay that cost once during indexing, then reuse the stored results.
That’s another reason disk-backed frozen analysis is compelling: it turns repeated “semantic recomputation” into “semantic reuse,” and reuse is where caching shines—even if caching lives on disk.
A simplified architecture sketch (the idea in code form)
Under the hood, frozen analysis designs usually look like this at the conceptual level:
- Index workspace: compute semantic facts and store them on disk in a queryable format.
- On each LSP request: determine which facts are needed, load them from disk, compute the final answer, drop the loaded data.
- On edits: mark buffers dirty; delay deep re-indexing until save.
Here’s a tiny pseudo-example of the “page in facts, answer query, drop” pattern:
// Pseudocode: not Rust Glancer's real code.
// The shape is: load just enough to answer one request.
struct FactStore;
impl FactStore {
fn load_symbols_for_file(&self, file_id: FileId) -> SerializedSymbols {
// Deserialize from disk (more IO, less RAM).
unimplemented!()
}
fn load_trait_impl_index(&self, workspace_id: WorkspaceId) -> SerializedTraitIndex {
unimplemented!()
}
}
fn answer_hover(facts: &FactStore, req: HoverRequest) -> HoverResult {
let symbols = facts.load_symbols_for_file(req.file_id);
let impl_index = facts.load_trait_impl_index(req.workspace_id);
// Combine facts to compute the hover payload.
// Then drop loaded structs so RAM returns to baseline.
unimplemented!()
}
In a real system, the “queryable format” matters a lot. If you serialize in a way that forces large reloads, you lose the memory win. If you serialize too granularly, overhead grows.
Rust Glancer’s documentation emphasizes that it only loads data “for the duration of the LSP query,” which is essentially this drop-after-answer strategy. (rust-glancer.github.io)
Where the sharp edges are
Frozen analysis buys you memory stability, but it also creates predictable disadvantages:
- Slower average query execution because disk deserialization is slower than in-memory lookup. ()
- New edits aren’t fully reflected until save because indexing is not continuous with every keystroke. ()
- Feature gaps and bugs are expected in an experimental project. ()
And those edges matter most when you rely on “agentic workflows” or high-frequency out-of-editor modifications. The project mentions it has specific attention there, including a custom file watcher and lower priority for some out-of-editor changes. ()
If you’ve ever seen an IDE’s inline hints drift after external edits, you already understand why file-watching semantics are hard.
The practical side: running it in VS Code
Rust Glancer ships as a VS Code extension that starts the rust-glancer LSP server and can be pointed at a specific server binary path. (marketplace.visualstudio.com)
The Marketplace page also shows example configuration knobs, including server path and environment variables used for logging. ()
This matters because in frozen-analysis systems, “what gets persisted,” “when it gets invalidated,” and “how logs explain mismatches” are all part of the daily debugging reality.
A design lesson worth keeping
Why does this architecture matter beyond Rust Glancer?
Because it challenges a default assumption in language server design: that incremental, always-in-memory semantics is the only route to correctness and speed.
Rust Glancer demonstrates a different corridor: serialize a semantic snapshot, load on demand, and accept a measured loss in immediacy. ()
And that corridor can be especially attractive on older or constrained machines where “fast enough” is better than “the best possible model that constantly thrashes RAM.” ()
In other words, the most interesting part isn’t that it’s under 100 MB. It’s the architectural courage to treat memory as a budgeted resource and redesign the analysis pipeline accordingly.
Closing thought
Language servers live at the boundary between two worlds: compiler-grade semantic reasoning and the user’s need for low-latency feedback. Rust Glancer chooses a path that shifts work from “always recompute in RAM” to “compute once, persist, then page in facts.” ()
That shift doesn’t magically remove complexity; it relocates it—from incremental state management toward serialization, invalidation rules, and query-time loading.
And once you see that relocation clearly, the design feels less like magic and more like engineering: a set of conscious compromises that make different kinds of machines and workflows feel equally at home.
Comments (0)
No comments yet. Be the first to respond!
Leave a Comment
Your comment will be visible after review.