Rust Glancer v0.3.0: new trait solver and other goodies
Huh, it's been a month since the last release. And it was a lot of work, too.
Rust Glancer is now 0.3.0, and it comes with a pretty big changelog. A lot of items in it, and if you're curious, you can check the changelog. This blog post will go through the significant changes, and my thoughts on the development of Rust Glancer so far.
This release in one picture:

Now, just to make it clear: it does not mean that Rust Glancer is 88.5% of a complete LSP server, it's less in practice (if we consider absolute functionality). It's an internal and somewhat subjective static set of 141 queries that I use to compare Rust Glancer to rust-analyzer. So the correct way to treat it is "quite some improvement over the previous release, in particular around definitions, inlay hints, and hovers".
If you want to try out Rust Glancer, you can use:
- VS Code extension.
- OpenVSX extension (cursor, vscodium, etc).
- Zed: official
Rust Glancerextension is available, though at the moment of publishing the 0.3.0 PR is open. - nvim:
nvim-lspconfigconfiguration is available, and AFAIR mason integration was shipped, but it still uses version 0.2. There were no client-side changes, so feel free to grab a pre-built release artifact.
But let's look at the actual changes.
Quality of Life
Since the way I develop Rust Glancer is based on the things I personally miss or find annoying, the improvements are rather random and cover more breadth than depth. Again, the full list is in the changelog, and here are some honorable mentions:
Better documentation rendering -- the doc comments now have syntax highlighting and working internal links, both in editor and in hover.

Working built-in derives -- initially I wanted to work on proc macros support in this release, but then decided that I want to make it more complete for regular analysis first. Still, built-in derives like Clone or Debug are very fundamental and don't actually require proc macro expansion, so these are supported now.

Better function signatures -- small change, but makes function signatures in hover blocks much nicer.

Code blocks folding -- not something that I use, but it was an actual user request, so I could not ignore it. Also, I discovered that rust-analyzer supports rather weird and seemingly undocumented code regions as part of the folding algorithm that only has a handful of uses inside of r-a itself (mostly in minicore). I wonder if someone really uses that.

And a bunch of other things:
- Optimized release builds based on the user recommendation (thanks!).
- Added proper inference for
std::ops::Try(aka?syntax), indexes, and ranges. - Made
goto definitionon trait method follow the relevant impl rather than leading to the trait. - Fixed implementations lookup, so that when you search for all implementations, you get more results.
Inference
Obligatory LLM usage disclaimer is a good point to start a funny story: as the disclaimer correctly states, I use LLMs heavily yet my goal is learning. And I usually learn using good old FAFO approach. So when the project grew to a point where it needed type inference, I wanted to do it without looking at rust-analyzer, and I wanted to come up with a solution that I understand.
And the simplest way to do type inference (I plan to write a dedicated blog post about it, so stay tuned) is naive: run a fixed loop over the whole body, revisiting all the expressions until you stop seeing changes. That's the design I ended up with, and it worked fairly well. Except for the fact that it was rather slow. And it became slower after Chalk introduction. So I kept adding optimizations, caches, shortcuts, right until I realized that I no longer understand how things work and what's going on.
At this stage I finally looked into rust-analyzer and realized that its approach is much more efficient (and simpler than my frankenstein at this point): it used a recursive approach and did not revisit expressions unnecessarily; instead the later evidence could solve already allocated inference variables. So I reimplemented the algorithm to use recursive scheduler, which turned out both much faster and much simpler than what I had originally.
With trait solver, which was the next thing I've done after changing the type inference approach, the story was similar: as I learned after the fact, I have chosen an SLG Solver while rust-analyzer historically used recursive one, and it could've contributed to a fair share of the problems I've had with managing solver state (or not, who knows). I dreaded integrating new solver (I mean, the new rustc solver, not replacing SLG Chalk with recursive Chalk), but, while it had quite some boilerplate, at the end of the day it helped me to improve the architecture by aligning the inference with compiler types/approaches, and overall it went rather smoothly. (and here one more clarification: it wasn't that big of an achievement as it was for rust-analyzer: thanks to rust-analyzer, I had a reference to compare against, which, combined with LLMs, makes the task significantly easier)
So on one hand, I've wasted quite a bunch of time here, but I'm glad I did. Had I not done this exercise of reinventing the wheel, it's unlikely that I'd ever understood how inference works (I'm not the sharpest crayon in the box, a lot of things that are simple to other people might feel like black magic to me -- but everything can be achieved if you're stubborn enough). If I didn't care about the fact that I actually understand the code and continued development with what I had, most likely I'd meet a deadend soon, because the complexity was already through the roof despite having much less stuff implemented than in rust-analyzer. Now I know more, and that's what matters.
I still can't say that I understand everything about type inference/new trait solver, and there is a good chance that in a few months I will again write how stupid I was, but that's the fun of it. The more you bang your head against the problem, the better you become. 6 months ago I held an opinion that I have no chance of understanding type inference whatsoever.
But OK, back to the actual release: with the new inference approach and the new solver, indexing became much faster and the code is in a much better shape for extensions, so Rust Glancer 0.4 will again be more complete.
Maintenance
When you use LLMs for development, code maintenance becomes even more important. I think the fact that I optimized CI or did some cleanups is not very interesting, so I'll just share a few tools that I really love:
- codspeed -- makes benchmarking in CI an easy task. This is not sponsored, and I use free tier and idk what their pricing is, but even with unpredictable GH runners it hasn't generated a single false positive in regressions, and the interface was really helpful to debug these regressions. Before that I used gungraun, which is cool, but has its own drawbacks; codspeed was much better fit for me.
- dylint -- lets you write your own clippy-like lints. I am a weird guy: generate code with LLMs yet rather opinionated about how it looks like, so it was rather helpful to enforce some things that LLMs repeatedly were getting wrong. If you're interested, you might check my collection of lints.
Conclusion
A lot of sweat went into this release, and I probably will take a short break before I'll start working on 0.4. In 0.4, I think I will continue working on improving analysis quality and supporting more cases. The indexing time seems to be rather reasonable now and in my day-to-day use I don't meet that much bugs related to the LSP behavior itself, so this part seems to already be fairly robust (but if you discover any issues, you know what to do).
Otherwise, thanks for reading. As usual, the best way to support if you like the project is to star the repo and/or follow me on twitter. If you have spare coins, consider sponsoring Rust Foundation to make sure that the future of Rust is safe (like our memory (sorry)).