Rust Glancer: A Language Server Designed Around a Memory Budget


Developer tools quietly became some of the largest applications on a laptop. A modern editor may host a browser runtime, several extensions, terminals, test processes, and one language server per workspace. Rust makes the pressure especially visible: understanding a project can require parsing thousands of source files, expanding macros, resolving modules, inferring types, solving traits, and retaining enough relationships to answer “find references” instantly.

Rust Glancer asks a useful systems question: what if the language server were designed around a strict resident-memory budget instead of maximum keystroke-level freshness?

Its answer is not a smaller version of rust-analyzer. It is a different architecture. Rust Glancer builds a largely frozen analysis, writes compact package artifacts to disk, loads only the pieces needed for a query, and releases them when the query ends. The project targets less than 100 MB of memory for reasonable workspaces after indexing, while accepting that initial indexing can use much more and that some semantics remain stale until a file is saved.

That trade is more interesting than the headline number. It shows how changing the lifetime of data can change an entire tool.

Why a Rust language server holds so much state

A useful Rust language server cannot operate like a text search box. It must know that a method came from an inherent impl, that a trait implementation makes a call legal, that a re-export changes a public path, and that a local expression constrains several type variables at once.

The resulting state falls into several broad layers:

  • concrete syntax trees for source files;
  • module and definition maps;
  • macro expansions;
  • semantic representations of items and bodies;
  • inferred types and trait obligations;
  • indexes for navigation, references, completion, and diagnostics.

Rust-analyzer uses Salsa, an incremental query database, to compute this information lazily and retain dependencies between queries. When an input changes, Salsa can invalidate the affected results and recompute them. Rowan gives rust-analyzer a persistent syntax-tree representation that supports partial reparsing. Together they favor low-latency, continuously fresh answers.

That choice has a cost. Query results and dependency relationships remain in memory, syntax-tree nodes create allocation overhead, and long-lived allocations can become interleaved with short-lived ones. The allocator may own many pages even after some objects have been freed, so resident set size can remain much larger than the live data suggests.

None of this makes rust-analyzer badly designed. It means the design optimizes a different objective: rich, precise feedback while a programmer is typing.

Freeze the analysis and move its lifetime to disk

Rust Glancer makes saved source the stable boundary. It indexes the workspace into serializable package artifacts, fingerprints those artifacts, and stores them on disk. If the project and toolchain still match on restart, the cache can be reused instead of rebuilt.

An LSP query rarely needs the entire dependency graph at once. Hovering a symbol in an open file may need a small set of definitions, types, and relationships. Rust Glancer loads the relevant shards for the duration of that query, computes the response, then drops the temporary state. Jemalloc purging helps return unused pages to the operating system rather than keeping them ready for a hypothetical burst of allocations.

A Rust workspace is indexed into compact package artifacts on disk; an editor query loads only relevant shards into a short-lived query engine before returning an LSP response.
The durable index is broad, but the resident working set follows the current query.

This changes the normal performance bill. Disk reads and deserialization are slower than reading an already-resident object graph. In exchange, idle memory can fall sharply, restarts can reuse prior work, and switching to another application does not leave the whole semantic world resident just in case.

The architecture is closer to a database with a compact on-disk index than to an always-hot incremental compiler.

Typing uses an overlay, not a full semantic rebuild

Frozen analysis creates an obvious problem: programmers type between saves. If every keystroke rebuilt the affected semantic index, the design would recreate much of the work it was meant to avoid.

Rust Glancer instead combines the last complete saved analysis with shallow analysis of the dirty buffer. Syntax-local features can respond quickly, and existing semantic information remains available. New imports, structs, traits, or other project-level facts may not become fully indexed until the file is saved.

Saving creates a deliberate synchronization point. The changed file is rebuilt, affected reverse dependencies may be invalidated, refreshed artifacts are written to disk, and the new frozen state becomes authoritative. A dependency change or toolchain update can force broader reindexing, but ordinary saves should touch a much smaller slice than the whole workspace.

A dirty editor buffer receives shallow syntax analysis over the last saved index; saving invalidates affected crates, rebuilds that slice, and freezes new package artifacts.
Dirty buffers get fast, approximate help; save-time invalidation restores complete semantic state for the affected slice.

This is a product decision as much as a compiler decision. Some developers expect auto-imports and newly declared items to become globally visible immediately. Others would happily press save before asking for complete project semantics if the reward is a cooler laptop and several gigabytes of free memory.

Low idle memory still requires a large indexing phase

Moving state to disk does not make the information free to produce. Rust Glancer’s own profiling documentation shows an indexing run against rust-analyzer source reaching hundreds of megabytes during lowering and roughly a gigabyte around package-cache writing before dropping to about 75 MB after offload and cleanup in that specific run.

The distinction between peak RSS and steady-state RSS is essential. The project author also noted in the HN discussion that initial indexing can currently consume more memory than rust-analyzer. The benefit appears during the much longer working and idle periods after that initial build.

The practical memory curve is therefore:

  1. pay a high, short-lived cost to understand the workspace;
  2. compact and serialize reusable analysis;
  3. discard build intermediates;
  4. keep a small service shell alive;
  5. load narrow slices for individual queries.

A machine that cannot survive the initial peak will not be rescued by a low idle figure. Rust Glancer offers an indexing preference that caps some parallel work to reduce peak memory at the cost of build time. Its documentation gives macro expansion as an example: limiting that phase from 16 threads to two reduced peak RSS substantially in one measurement, while making the phase modestly slower.

Allocation lifetime is part of the architecture

The most instructive work is below the LSP protocol. Rust Glancer organizes indexing phases to avoid mixing objects with different lifetimes.

Imagine processing one package completely before moving to the next. Parsing creates temporary syntax nodes; lowering creates longer-lived item data; semantic analysis adds another lifetime. Repeating that sequence can scatter long-lived objects between holes left by temporary allocations. Freeing the temporary objects does not necessarily produce large contiguous pages that jemalloc can return to the operating system.

Rust Glancer instead performs the same phase across packages, shrinks and compacts its results, evicts data as soon as the next layer no longer needs it, and then advances. Parse syntax can be dropped after item-tree lowering. When function bodies later need syntax again, a file can be reparsed, lowered, and released locally.

The project provides two supporting traits:

  • MemorySize estimates retained memory recursively and attributes it to types and scopes;
  • Shrink recursively removes spare container capacity where possible.

This instrumentation makes memory a first-class correctness property. A new feature is not merely timed; its retained structures can be compared at named checkpoints. Jemalloc’s allocated, active, resident, and mapped figures help distinguish genuine live data from fragmentation or pages that have not yet been purged.

The general lesson applies beyond language servers: if a service has clear phases, align allocation lifetimes with those phases. Data layout and destruction order can matter as much as choosing a smaller object representation.

The “100×” claim needs a benchmark contract

The HN title describes two orders of magnitude less RAM, but the launch article itself states a target below 100 MB for reasonable projects and presents indexing-time comparisons, not a complete public memory comparison across many repositories. The author says a fair benchmark is still planned.

A credible comparison should specify:

  • repository commit and dependency graph;
  • Rust toolchain and enabled features;
  • build-script and procedural-macro settings;
  • cold start, warm start, indexing peak, active query, and idle RSS;
  • editor count and whether servers are shared;
  • cache size and disk-read latency;
  • which LSP features produce correct answers;
  • latency distributions for hover, completion, navigation, and references.

Feature completeness matters because omitting expensive features is a valid product trade, but it is not a like-for-like optimization. Rust Glancer does not currently aim to invoke build scripts or procedural macros as rust-analyzer does. It supports common syntax and actions, type inference, declarative macros, and Chalk-based trait solving, while remaining young and incomplete.

The useful claim is narrower: a frozen, offloaded analysis can make steady-state memory dramatically smaller for developers willing to accept its consistency model and feature boundary.

Procedural macros expose the hardest boundary

Procedural macros run code to generate code. Supporting them faithfully means executing build-time programs, handling generated tokens, tracking dependencies, and reflecting changes back into analysis. They expand the semantic world and introduce a security boundary.

Rust Glancer intentionally avoids executing untrusted build scripts and proc macros today. The project has floated the possibility of explaining common macro effects without running the macro itself. A derive such as serialization could, in principle, be represented through a trusted shim that contributes the relevant generated trait implementation.

That approach resembles stub generation: model the public semantic effect rather than every implementation detail. It could cover popular macros efficiently, but it creates a compatibility burden. Every shim must match real macro behavior closely enough that navigation, inference, and diagnostics remain trustworthy. Unknown or highly dynamic macros still require a fallback or an explicit “analysis incomplete” signal.

This is a good place for restraint. An LSP that quietly invents incorrect generated code is worse than one that clearly marks an unsupported boundary.

A hybrid language-server architecture may be the destination

Matklad, rust-analyzer’s original author, highlighted a promising middle ground. Open files need rich, incremental syntax and semantics because humans are changing them. Most project files change less often. Thousands of dependency files may never be opened at all.

One engine does not have to represent all three groups identically:

  • open files: fully incremental syntax trees and live semantic queries;
  • project files: compact on-disk stubs with bodies loaded lazily;
  • dependencies: compiler metadata such as Rust’s .rmeta, expanded only when navigation requires source detail.

IntelliJ’s PSI architecture follows a related idea by presenting one interface over concrete syntax trees, compact stub trees, and compiled metadata. The consumer asks for code facts without caring which backing store supplied them.

Rust Glancer is valuable even if it never replaces rust-analyzer because it demonstrates the other end of the design space. It makes persistence, offloading, compaction, and human-scale query timing central rather than secondary.

Who should try it now

Rust Glancer is most compelling for developers who:

  • work on memory-constrained laptops or remote development machines;
  • keep several Rust workspaces or editor windows open;
  • tolerate save-time semantic refresh;
  • do not depend heavily on build scripts or procedural-macro expansion;
  • want warm restarts from a reusable project cache;
  • are comfortable evaluating an early language server against real projects.

Rust-analyzer remains the safer default when completeness, immediate semantic accuracy, proc-macro support, and mature editor integration matter more than resident memory.

Evaluation should be practical. Run both tools on the same pinned workspace for several days. Record cold and warm startup, peak and idle RSS, query latency, incorrect or missing results, cache growth, CPU wakeups, and recovery after branch changes. The winner is the tool that improves the whole workstation experience without hiding analysis gaps that matter to the codebase.

Memory budgets create better questions

Rust Glancer’s strongest contribution is not a number. It is a reframing.

Instead of asking only how to recompute less after every edit, it asks which facts truly need to remain resident. Instead of treating allocator behavior as an implementation footnote, it measures object lifetimes and fragmentation at pipeline checkpoints. Instead of assuming every dirty buffer needs complete global semantics, it creates a shallow overlay and makes save the consistency boundary.

Those decisions will not suit every developer. They do, however, show that a language server’s resource appetite is not an unavoidable property of understanding Rust. It is partly the result of what the tool promises, when it promises it, and where it chooses to keep the answer.

Sources and further reading

  • Hello, world!, the Rust Glancer launch article, architecture, performance notes, limitations, and roadmap.
  • Memory approach, the project’s offloading, purging, compaction, eviction, parallelism, and measurement strategy.
  • Rust Glancer source, implementation, setup, issues, and current project status.
  • Rust Glancer, Matklad’s analysis of syntax representation, metadata-backed dependencies, proc macros, and hybrid IDE architecture.
  • Salsa, the incremental computation framework used by rust-analyzer.
  • Language Server Protocol specification, the editor–server contract both tools implement.
  • Hacker News discussion, author clarifications on indexing peaks, save-time invalidation, query residency, and feature trade-offs.
100%