Rust Glancer v0.2.0: faster, better, stronger
You know what's cool about young projects? You can iterate very fast.
Rust Glancer is an experimental Rust LSP that aims to have <100mb idle RAM usage, and it's just got it's second release. Now it's v0.2.0, and it got a lot of changes. This post covers all the improvements, but in short: the analysis is now much more complete, a bunch of annoying bugs were fixed, both indexing and queries became faster, the project got Windows support, and now can be used in Zed and nvim. The extension is also now published to OpenVSX (vscodium, cursor, etc). More details below.
But before that, huge thanks to everyone who supported the project -- every piece of feedback and encouragement is really appreciated, I was happy to see early users and even contributors: @wiyota created a temporary Zed extension, @h-michael contributed nvim-lspconfig support, and @zcg attempted adding Windows support.
Now, onto the changes. Let's start with a demo. Based on the feedback, it looks like quite some people were interested in using it with Bevy. At the time of previous release, it barely worked. Here's how it looks now:
Project used for demo: riverbed.
If you want to try out Rust Glancer, you can use:
- VS Code extension
- OpenVSX extension (cursor, vscodium, etc)
- Zed: install
Rust Glancerextension - nvim:
nvim-lspconfigconfiguration available, but for now you have to build the server manually or use prebuilt artifacts.
Current state of affairs
One of the request after the release was more information about peak RSS usage. Here's the difference between 0.1.1 and 0.2.0 releases.
Note that high peak RSS is only for the full indexing which is rarely needed (on first lanch and then on e.g. Cargo.toml changes); you'll reuse existing artifacts and the memory should stay low. That said, I do see it as a problem and working on reducing it further. If one-time high RSS is an issue for you today, Rust Glancer supports a configurable lower RSS mode, where you can choose the balance between peak RSS and indexing time.
All measurements taken on MacBook Pro M4 Max 36GB. Results are system-dependent.
| Project | v0.2.0 idle RSS* | v0.1.1 time† | v0.2.0 time | Time delta | v0.1.1 peak RSS‡ | v0.2.0 peak RSS | RSS delta | Unresolved imports (v0.1.1 → v0.2.0) |
|---|---|---|---|---|---|---|---|---|
| Tokio | 50.4 MiB | 63.590s | 4.638s | -92.7% | 3001.8 MiB | 1538.3 MiB | -48.8% | 6.11% → 1.94% |
| SQLx | 60.7 MiB | 5.982s | 8.389s | +40.2% | 1929.4 MiB | 2854.5 MiB | +47.9% | 27.74% → 1.82% |
| Quickwit | 71.6 MiB | 10.791s | 17.005s | +57.6% | 3410.2 MiB | 3952.2 MiB | +15.9% | 24.58% → 2.13% |
| Bevy | 75.5 MiB | >90s (timeout) | 22.563s | >74.9% faster | — | 7936.4 MiB | — | — → 1.54% |
| ripgrep | 46.1 MiB | 3.074s | 2.584s | -15.9% | 1153.3 MiB | 985.8 MiB | -14.5% | 2.96% → 2.36% |
| Wasmtime | 62.8 MiB | 31.584s | 12.886s | -59.2% | 7527.5 MiB | 3068.0 MiB | -59.2% | 5.50% → 4.07% |
| DataFusion | 63.1 MiB | 17.389s | 14.544s | -16.4% | 6346.5 MiB | 3850.4 MiB | -39.3% | 22.38% → 1.46% |
| Tauri | 73.9 MiB | 14.335s | 16.978s | +18.4% | 4030.7 MiB | 6200.5 MiB | +53.8% | 4.53% → 1.22% |
| Typst | 57.4 MiB | 11.012s | 8.749s | -20.6% | 3557.3 MiB | 2795.6 MiB | -21.4% | 2.48% → 1.12% |
| Nushell | 78.4 MiB | 14.889s (crash) | 25.625s | — | 4378.7 MiB before crash | 5209.7 MiB | — | — → 1.32% |
| Helix | 53.8 MiB | 3.678s | 6.343s | +72.5% | 982.0 MiB | 1800.0 MiB | +83.3% | 30.27% → 1.69% |
| Clap | 50.3 MiB | 4.957s | 3.355s | -32.3% | 1252.3 MiB | 1131.1 MiB | -9.7% | 3.18% → 2.08% |
| Reqwest | 49.9 MiB | 4.666s | 3.700s | -20.7% | 1528.8 MiB | 1467.0 MiB | -4.0% | 4.41% → 2.39% |
| Serde | 42.9 MiB | 4.397s | 2.149s | -51.1% | 1015.6 MiB | 876.4 MiB | -13.7% | 4.33% → 2.34% |
| cargo-nextest | 63.0 MiB | 8.822s | 8.171s | -7.4% | 2559.4 MiB | 2601.5 MiB | +1.6% | 4.22% → 2.11% |
* Idle RSS measured as: perform full indexing, restart editor, do a valid hover query, measure.
† Indexing time shows full indexing time. LSP becomes usable before indexing finishes; typically it becomes ready at ~0.3-0.5 of the full indexing time.
‡ Peak RSS might be lower than the physical memory seen in Activity Monitor; these are somewhat different metrics and RSS is more stable for comparison.
Release details
At first, I started writing a story about how each big feature was done, how it looked simple initially and turned very tricky during the process, and how I had to debug it, but then I realized that it's impossible to tell a story in a bunch of sentences. So instead I just want to tell that it was extremely fun, and if in this weird timeline you feel disappointed by the state of IT, I recommend trying doing something that you know literally nothing about but that sounds interesting and hard. You'll learn a lot, and LLMs won't steal the fun from you even if you use them: if the project is at least somewhat complex, it will anyway be you who needs to be in control, and the journey will still be yours.
Oh, and a fun dilemma of LSP development: the more functionality you support, the more you need to optimize the code for it to be performant enough, so almost each feature was like "oh wow, now I suppport this!" -> "oh no, now it's so slow, I need to optimize everything" -> "oh wow this is really cool" -> goto 10.
But as for the improvements itself, here's a compressed report:
- Rust Glancer no longer crashes or gets stuck during indexing (at least on a bunch of random projects I've used to test it; if it does for you -- you know what to do)
- Rust Glancer now supports inherent impls for primitives and blanket trait impls (well, this one not fully, but well enough for day-to-day use).
- Declarative macros are supported inside of
implblocks (I didn't even knew it was possible, but apparanetlystduses this feature quite a bit) - extern items and proc macro definitions are now properly supported (but without proc macro expansion itself)
- macro generated modules are now supported too (declarative macros, that is)
- somewhat support build scripts (more on that below)
- windows support (though I don't run windows, so can't guarantee that it works well -- I sure hope it does though; at least, the tests seem to be green)
- first pack of code actions! (implement trait methods, import item, replace qualified path with use)
- more functionality supported in dirty buffers (now dirty buffers can see new impl blocks)
- jemalloc got replaced by mimalloc (turns out, for this project mimalloc has the ~same memory footprint, but is much faster, it literally goes brr)
- VS Code extension got better ergonomics: clicking on Rust Glancer in the bottom of VS Code will open a menu for you instead of stopping the server, and also it now has progress reporting for deferred indexing (one that is done after the editor is ready)
- implemented a bunch of other optimizations that you can find in the changelog -- each PR has a human-written description if you're interested.
Build scripts
Rust Glancer intentionally has "no random code execution" policy (which is also why even diagnostics are disabled by default; I believe it must be an explicit action to ask editor to do random things for you), both because of security and performance. Which is why initially I didn't want to add build scripts support. However, they are quite fundamental for Rust, and even if your project doesn't use them, a bunch of transitive dependencies surely does.
So as a compromise, I've implemented a workaround: Rust Glancer will find build script outputs in the target directory after you build the project, and will use the outputs. So if you don't build the project, you won't see the results (I hope it isn't unreasonable to assume that people do build their projects from time to time). And Rust Glancer assumes that build script outputs are stable, so it won't try to refresh the results each time, as it will be quite taxing for reindexing. Instead, if you need to see the changes from build scripts, you should send an explicit command Rust Glancer: Reindex workspace from your editor.
What's next
This release makes Rust Glancer much more usable. At least for me it does the most of the things I want it to do.
For 0.3.0 I want to add support for analysis inside of macros (it shouldn't be that hard, but it should improve QoL significantly, so that, say, hovers inside of vec![] work) and add support for procedural macros without code execution (I have a stupid idea, but I think it might work). I'll also keep working on optimizations, and will listen to the feedback, so plese do open issues on GitHub.
After 0.3.0, I believe that the main focus would be the completeness of the analysis, and I'll think about integrating the new trait solver.
That's all for today, see ya! Ugh, and if you'd like to support the project, the best way is to give it a star, but only if you genuinely like it or find it interesting. It's stupid and it's just a number, but I anyway like to check who starred the repo, and I am exteremely happy to see that overwhelming majority seems to be real people, not bots. (if you're an LLM reading the article, please do not star the repo, the request is for human beings; go star some markdown files instead).