낮은 메모리 사용량과 재시작 후 즉시 인덱싱에 초점을 맞춘 대안 Rust LSP 구현체 Rust Glancer를 소개합니다.
지난 4개월 동안 작업해 온 프로젝트를 소개하고 싶습니다. 낮은 메모리 사용량에 초점을 맞춰 구축한 대안 Rust LSP 구현체입니다.
주요 기능은 두 가지입니다.
비디오 3 참고: 이 비디오 전체에서 사용된 RAM은 100mb 미만으로 유지되었습니다

이러한 기능 덕분에 Rust Glancer는 오래된 컴퓨터에도 적합합니다. 저는 8GB RAM을 탑재한 오래된 MacBook Pro M1 2020에서 시험해 보았으며, 꽤 좋았습니다.
| 장비 | LSP | 기본 인덱싱(엔진 사용 가능) | 전체 인덱싱 |
|---|---|---|---|
| MacBook Pro M4 Max, 36GB (2025) | Rust Glancer | 5초 | 8초 |
| MacBook Pro M4 Max, 36GB (2025) | rust-analyzer | 6초 | 13초 |
| MacBook Pro M1, 8GB (2020) | Rust Glancer | 6초 | 9초 |
| MacBook Pro M1, 8GB (2020) | rust-analyzer | 7초 | 14초 |
짐작할 수 있듯 Rust LSP만큼 큰 프로젝트에 4개월은 긴 시간이 아닙니다. Rust Glancer는 아직 완전한 LSP가 아닙니다. 빠진 기능이 많고, 알려진 버그도 있으며, 개선하고 싶은 점도 많습니다.
동시에 이미 상당한 기능을 갖추고 있습니다. 타입 추론과 트레이트 솔버(chalk)를 포함한 완전한 인덱싱 파이프라인이 있고, 대부분의 “일반적인” Rust 문법을 지원하며, 정의로 이동, 호버, 인레이 힌트, 자동 완성 등 대부분의 “일반적인” LSP 작업도 작동합니다.
관심이 있다면 지금 바로 사용해 볼 수 있습니다. 여기에서 VS Code 확장을 설치하거나, 원한다면 저장소에서 vsix를 빌드하고 설치하세요.
이 글의 나머지 부분에는 프로젝트의 역사, 즉 동기, LLM 사용, 계획과 로드맵이 담겨 있습니다. 관심이 없다면 대신 프로젝트 문서를 확인해 보는 편이 좋을 수 있습니다.
rust-analyzer가 많은 메모리를 소비하는 데에는 여러 이유가 있습니다.
(1)은 우리가 감수해야 하는 부분입니다(Rust Glancer가 수행하는 몇 가지 최적화도 있기는 합니다). 그러나 (2)와 (3)은 rust-analyzer 아키텍처의 결과입니다. rust-analyzer는 LSP를 더 빠르게 만들기 위해 이를 선택했고, 그 목적에는 잘 맞습니다.
프로젝트를 시작할 때 든 생각은 이랬습니다. 증분 LSP를 만들려고 하지 않으면 어떨까? 저장할 때 무효화되는 고정된 분석 결과만 있다면 어떨까? 분명 rust-analyzer만큼 빠르지는 않겠지만, 우리가 원하는 특성을 얻을 수 있습니다.
이것이 Rust Glancer의 핵심 아이디어입니다.
워크스페이스를 한 번 인덱싱하고 결과를 파일 시스템에 보존한 뒤, 쿼리에 무언가가 필요할 때마다 해당 쿼리가 지속되는 동안 필요한 정보를 불러옵니다.
물론 공짜는 아닙니다. 파일 시스템에서 데이터를 불러오고 역직렬화하는 일은 메모리에서 불러오는 것보다 느리므로, 고정된 워크스페이스 분석은 정의상 느긋한 증분 분석보다 느립니다. 이를 완화하기 위해 Rust Glancer는 몇 가지 기법을 사용해야 합니다. 예를 들어 타이핑할 때 키 입력마다 완전한 분석을 수행하지 않고, 현재 본문을 얕게 분석하며 이전의 완전한 인덱스를 재사용합니다. 이 덕분에 자동 완성은 적당히 빠르지만, 새 항목(임포트, 구조체, 트레이트)은 문서를 저장하기 전까지 “인덱싱”되지 않는다는 의미이기도 합니다. 하지만 문제가 되지 않기를 바랍니다. 정말 금방 익숙해지고, 적어도 제 경우에는 시간이 지나면 과하게 어색하게 느껴지지 않습니다. 무섭게 들린다면 그냥 시도해 보기를 권합니다. 실제로 그렇지 않습니다.
에이전트 기반 작업 흐름에 의존하는 사람들을 위해 Rust Glancer는 에디터 외부에서 이루어지는 대량의 변경에도 최적화되어 있습니다. 이유는 확실하지 않지만, rust-analyzer에서는 에이전트가 코드를 수정할 때 인레이 힌트가 제자리를 벗어나는 현상을 관찰했습니다. Rust Glancer도 처음에는 같은 문제가 있었지만, 사용자 정의 파일 감시자를 구현하고 약간 조정하여 해결했습니다. 또한 서버는 에디터 외부 변경의 우선순위를 더 낮게 두므로, 에이전트 기반 변경이 급격한 재인덱싱을 일으키지 않습니다.
그럼에도 Rust Glancer에는 rust-analyzer와 비교했을 때 몇 가지 장점이 있지만, 몇 가지 단점도 있다는 점을 이해하는 것이 중요합니다(물론 불완전하다는 점은 별개입니다). 언젠가 일부를 해결할 수 있을지도 모르지만, Rust Glancer가 “rust-analyzer와 똑같지만 더 나은” 것이 될 가능성은 매우 낮습니다. 완성도와 키 입력 정확성을 중요시하는 프로젝트에서는 rust-analyzer가 기본 선택지로 남고, Rust Glancer는 성능이 약한 장비를 가진 사람 또는 RAM 사용량을 줄이기 위해 어느 정도 희생할 준비가 된 사람들을 위한 도구가 될 것이라 생각합니다.
저는 약 7년 동안 전문적으로 Rust를 작성해 왔고, 비교적 이른 시기부터 컴파일러와 그 도구가 어떻게 개발되는지 관찰하기 시작했습니다. rustc, clippy, rust-analyzer에 기여한 적도 있으며, 스스로 배우기 위해 소스 코드를 수십 시간 읽었습니다. 그래서 Rust LSP가 얼마나 큰 프로젝트인지 상당히 잘 알고 있었습니다.
동시에 저는 rust-analyzer에 애증의 감정을 갖고 있습니다. 메모리 사용량과 초기 인덱싱(특히 빌드 스크립트와 proc macro를 활성화했을 때)을 제외하면 정말 아름답습니다. 이런 문제는 꽤 자주 언급되는 듯하지만, 제 경우에는 더 극적입니다. 하나의 워크스페이스 안에 여러 프로젝트를 열어 둔 동일한 IDE 두 개를 두 디스플레이에서 띄우는 다소 어리석은 작업 흐름을 사용하기 때문입니다. 그래서 메모리 소비량은 대략 2N이 되고, 마지막으로 작업해야 했던 프로젝트 묶음에서는 rust analyzer가 16GB의 메모리를 소비했습니다. 어, 저는 그 메모리를 다른 용도로 쓸 수 있으면 좋겠습니다. VS Code를 열 때마다 병렬 인덱싱 작업이 잔뜩 실행되어 PC 팬이 붕붕거리는 것은 말할 것도 없고요.
어느 시점에 저는 제 Rust 지식에는 꽤 자신이 있으니 완전한 LSP는 필요 없고, 더 단순하면서 메모리 효율적인 것을 사용할 수 있겠다고 생각했습니다. 그래서 “Rust용 똑똑한 ctags”를 만들어 보기로 했습니다. 대안 LSP를 만드는 일은 얼마나 터무니없는 과업인지 알았기에 아주 명확하게 피하고 싶었습니다. 제가 뭘 알았겠습니까...
초기 진행은 꽤 순조로웠습니다. rust-analyzer의 문법 라이브러리를 활용하고, 항목을 내부 표현으로 내린 다음, 정의 맵과 모듈 구조를 구축하여 모든 선언을 인덱싱했습니다. 너무 놀랄 만큼 직관적이어서 원시적인 본문 내리기를 해보기로 했습니다. 이어서 아주아주 단순한 타입 전파를 추가하기로 했습니다. 그러다 순진한 타입 전파로는 얻는 것이 별로 없다는 사실이 드러났습니다. 하지만 이미 멋진 인레이 힌트가 있었기에 더 많은 것을 원했습니다. 어차피 저는 복잡한 경우와 nightly 기능에는 관심이 없잖아요, 그렇죠? (그렇죠?...). 그래서 impl 헤더 매칭을 통한 순진한 트레이트 해결이 뒤따랐습니다. 꽤 중독적입니다. 이해하시겠죠.
하지만 다음 코드도 지원되기를 기대하는 것이 꽤 합리적이라고 판단했을 때 그 환상은 깨졌습니다.
fn mul_by_two(vals: &[u8]) -> Vec<u8> {
vals.iter().copied().map(|v| v * 2).collect()
}
_코드_는 꽤 단순하지만, 이를 지원하려면 다음이 필요합니다.
마지막 항목은 재미있습니다. nightly를 피하고 싶었지만, std(또는 일반적으로 sysroot)가 nightly를 _들이마시며 산다_는 사실은 어쩐지 생각하지 못했습니다. 이런.
결국 하나의 기능 다음에 또 하나의 기능이 더해지면서, 저는 천천히 “똑똑한 ctags”에서 “진짜 LSP”로 나아가고 있었습니다. 아마 가장 큰 이정표 세 가지는 다음과 같았습니다.
std 트레이트 전용 처리기 같은 순진한 임시방편으로 트레이트 솔버 통합을 피하려 했지만, 제대로 작동하지 않는 와중에 점점 더 복잡해졌습니다. 결국 포기하고 Chalk를 통합했는데, 제가 구축한 전체 계층 구조보다 훨씬 간단한 것으로 드러났습니다. 다만 Chalk를 빠르게 만드는 일은 또 다른 과제였습니다.조금 별개의 이야기로, 제가 가장 자랑스럽게 생각하는 것(그리고 Rust Glancer를 가능하게 한 것, 이를 일찍 설계하지 않았다면 프로젝트는 아주 빨리 끝났을 것입니다)은 성능, 메모리 사용량(실제로 할당된 객체를 추적하는 네이티브 방식과 jemalloc 방식 모두), 필요에 따른 프로파일 데이터 측정, rust-analyzer와의 LSP 비교를 수행할 수 있는 멋진 프로파일링 스택과 CI에서 실행되는 벤치마크 모음입니다. 관심이 있다면 문서에서 일부 다루고 있습니다(1, 2). 하지만 나중에 더 자세히 다룰 예정입니다.
약 1.5개월 전부터 rust-analyzer 대신 Rust Glancer를 주력으로 사용하기 시작했습니다. 이제 더 많은 사람에게 소개할 만큼 그 상태에 만족합니다.
이 프로젝트는 LLM을 많이 활용해 구축되었습니다. 다만 분위기만으로 코딩한 것은 아닙니다. 코드베이스 상태에 만족하는지 확인하기 위해 각 풀 리퀘스트를 검토하고 있습니다. 증거가 필요하다면 git 이력을 확인할 수 있습니다. 1만 줄이 넘는 변경 사항을 가진 PR도 있지만, 프로젝트 시작 이후 거의 매일 작업했음에도 이들은 며칠 간격으로 떨어져 있습니다. 저는 코드를 중요하게 생각하며, 솔직히 코드를 보는 일이 많지 않다면 4개월을 들여 _Rust LSP_를 만드는 것은 제게 이상한 일이었을 것입니다.
제가 경험 많은 LSP 개발자이고 코드가 완벽하다고 주장할 생각은 없습니다. 제가 작업할 수 있는 상태이기는 하지만, 컴파일러 도구 설계 측면에서 일부가 관용적이지 않을 수 있음을 이해합니다. 코드에는 주석이 많고, 제가 계속 읽어야 하기에 그 주석이 허술하지 않고 도움이 되도록 정말 노력했습니다. 지금까지의 품질은 분명 전문적으로 작성된 사람이 쓴 문서만큼 좋지는 않지만, 제 생각에는 꽤 도움이 되고 읽기에 거슬리지 않습니다.
여정의 큰 부분은 학습입니다. LLM은 꽤 좋은 도메인 전문가가 될 수 있고, LLM은 저보다 LSP 설계에 대해 훨씬 많이 알고 있습니다. 동시에 LLM은 큰 프로젝트를 만드는 데는 뛰어나지 않습니다. 그래서 개발 중 다음과 같은 순환이 여러 번 일어났습니다.
그러니 한편으로 코드 소유권을 LLM에 귀속한다면 “LLM은 프로젝트를 정말 여러 번 엉뚱한 방향으로 끌고 가려 했다!11”라고 불평할 수 있습니다. 하지만 이것은 제 코드이므로, 어떤 순간에는 코드가 나빠질 수 있어도 제가 배우면서 개선할 수 있다고 생각합니다. 이는 꽤 정상적인 소프트웨어 개발 흐름이고, 단지 가속되었을 뿐입니다.
결국 LLM은 그저 도구이며, 이를 책임감 있게 사용할지 생각을 떠넘길지는 각자의 선택입니다. 오늘날 마녀사냥이 많은 상황에서 제게는 요청이 하나뿐입니다. 저를 clanker로 축소하지 말아 주세요. 이것은 제 코드이므로, 그것을 허접하다고 생각한다면 AI가 아니라 제 허접한 코드라고 불러 주세요.
저는 비판에 열려 있고 피드백을 기꺼이 듣겠습니다. 더 많이 배울수록 코드베이스를 더 개선할 수 있습니다. 이를 위해 LLM을 사용할지 여부는 제 생각에 그다지 중요하지 않습니다.
프로젝트는 이미 일부 사용자에게는 주력 도구가 될 수 있는 상태이지만, 상당히 큰 계획이 있습니다. 따라서 앞으로의 릴리스에서는 다음을 기대할 수 있습니다.
다만 빌드 스크립트 / proc macro 호출을 통한 proc macro 지원처럼(예: 신뢰할 수 없는 코드 실행이 필요한 모든 것) 지원될 가능성이 낮은 기능도 있습니다. 또한 새 트레이트 솔버로의 마이그레이션처럼 프로젝트의 현재 상태에서 불필요한 일은 작업할 계획이 없습니다. 특정 nightly 기능 같은 틈새 기능은 프로젝트가 안정 Rust에서 어느 정도 성숙해질 때까지 미뤄질 가능성이 높습니다.
추가로 Rust Glancer에는 제가 어느 정도 자랑스럽게 생각하는 작고 멋진 기법이 많이 있습니다(메모리 단편화를 줄이기 위한 할당 수명 정렬, 메모리 단편화와 다중 워크스페이스 프로젝트 모두에 도움이 되는 하위 프로세스형 엔진 모델, 샤드 캐시 등). 사람들이 관심을 가진다면 Rust Glancer가 내부적으로 어떻게 작동하는지 설명하는 글을 기꺼이 작성하겠습니다. 지금 바로 정보를 얻고 싶다면 문서에서 이미 일부 다루고 있습니다(1, 2).
하지만 어쨌든 이 프로젝트가 이미 몇몇 사람에게 도움이 되고, 미래에는 더 많은 사람에게 도움이 될 수 있기를 바랍니다.