---
title: "러스트를 리스프 문법으로 쓰면 이런 느낌이라는 주말 프로젝트"
published: 2026-05-09T21:46:27.000Z
canonical: https://jeff.news/article/2450
---
# 러스트를 리스프 문법으로 쓰면 이런 느낌이라는 주말 프로젝트

러스트의 소유권, 빌림, 라이프타임, 제네릭, 트레이트 같은 의미론은 그대로 두고, 겉 문법만 리스프의 에스표현식으로 바꾼 실험 프로젝트가 나왔다. 완성형 컴파일러라기보다는 리스프 매크로를 러스트 세계에 붙이면 어떤 맛이 나는지 보여주는 트랜스파일러에 가깝다.

- 한 줄로 말하면, 러스트의 속은 그대로 두고 겉문법만 리스프로 갈아끼운 프로젝트임
  - 작성자는 대놓고 “프로덕션 컴파일러 아님, 주말 프로젝트임”이라고 선을 그음
  - 목표도 러스트 완전 대체가 아니라, 러스트 의미론에 리스프 매크로를 붙이면 어떤 느낌이 나는지 보는 실험에 가까움

- 동작 방식은 꽤 단순함: 에스표현식으로 코드를 쓰면 `.rs` 파일로 변환하고, 그다음은 `rustc`가 처리함
  - 파이프라인은 `(s-expr -> .rs -> binary)` 구조
  - 타입 검사, 빌림 검사, 최적화는 전부 기존 러스트 컴파일러가 맡음
  - rlisp는 “문법만 바꿔주는 앞단” 역할을 하는 셈

- 예시를 보면 러스트의 구조체, 메서드, 참조, 필드 접근이 전부 괄호 문법으로 표현됨
  - `(struct Point (x f64) (y f64))`처럼 구조체를 선언함
  - `(&self)`, `(other &Point)` 같은 식으로 참조와 인자를 적음
  - `(. self x)`는 `self.x`처럼 필드 접근으로 변환되고, `(. p1 distance (& p2))`는 메서드 호출로 이어짐

- 러스트의 핵심 기능을 꽤 많이 덮고 있다는 게 의외의 포인트임
  - 소유권, 빌림, 라이프타임, 제네릭, 트레이트, 패턴 매칭을 에스표현식으로 표현함
  - 문법 문서에는 visibility, module, turbofish, inline Rust, const/static, if-let, while-let, match guard, derive, where 절, supertrait, associated type, type alias까지 들어가 있음
  - 아직 빠진 문법도 있고, lifetime bound 같은 건 작업 목록에 남아 있음

> [!IMPORTANT]
> 이 프로젝트가 직접 러스트의 타입 시스템을 재구현하는 건 아님. 변환 결과를 rustc에 넘기기 때문에 “문법 실험”과 “언어 의미론”이 깔끔하게 분리됨.

- 연산자는 리스프식으로 쓰되, 결과물은 러스트다운 중위 표기법으로 나감
  - `(+ a b)`는 `(a + b)`로 변환됨
  - `(+ a b c)`는 `(a + b + c)`처럼 여러 항을 이어 붙임
  - `-`, `*`, `/`, `==` 같은 이항 연산자도 같은 식으로 처리함

- 식별자 처리에는 러스트와 리스프 문법 사이의 충돌을 피하려는 장치가 있음
  - `page-header` 같은 케밥 케이스 식별자는 `page__header`처럼 이중 밑줄로 변환됨
  - 문제는 `foo-bar`와 `foo__bar`가 둘 다 `foo__bar`가 될 수 있다는 점
  - 이런 충돌은 경고를 띄우는 방식으로 처리함

- 가장 실용적인 재미는 매크로 쪽임
  - rlisp 매크로는 컴파일 타임에 에스표현식을 받아 에스표현식을 돌려주는 함수처럼 동작함
  - 러스트의 `proc_macro` 크레이트, 토큰 스트리밍, `syn`/`quote` 조합 없이 `defmacro`로 끝남
  - `quasiquote`, `unquote`, `unquote-splicing`을 써서 리스프식 코드 생성 패턴을 그대로 가져옴

- 예를 들어 `when` 매크로는 조건과 여러 본문 표현식을 받아 `if`와 `do` 형태로 확장됨
  - `(when (> x 10) ...)`가 `(if (> x 10) (do ...))`로 바뀜
  - 최종적으로는 `if x > 10 { ... }` 형태의 러스트 코드가 생성됨
  - `&rest`는 남은 인자를 리스트로 모으고, `unquote-splicing`은 그 리스트를 주변 표현식에 펼쳐 넣음

- 반복문, 클로저, 모듈, 가시성 같은 일상적인 러스트 코드도 대부분 괄호 세계로 들어옴
  - `while`, `loop`, `for`, destructuring이 있는 `for`까지 예시가 있음
  - 타입 없는 람다, 타입 명시 람다, `move` 클로저도 지원함
  - `pub`, `pub(crate)`, `pub(super)`, 공개 필드와 비공개 필드도 표현 가능함

- 빠져나갈 구멍도 있음: `(rust "...")`로 원시 러스트 코드를 그대로 박을 수 있음
  - rlisp가 아직 표현하지 못하는 문법은 문자열로 넣으면 생성된 `.rs` 파일에 그대로 들어감
  - 예시에서는 `let x: i32 = 42; x * 2` 같은 코드를 raw Rust로 삽입함
  - 실험 프로젝트에서는 이런 escape hatch가 없으면 금방 막히기 때문에 꽤 현실적인 선택임

- 작성자가 강조하는 장점은 “문법이 균일해진다”는 쪽에 있음
  - 표현식, 타입, 패턴, 문장이 모두 비슷한 모양을 갖게 됨
  - 구조 편집도 쉬워짐. 에스표현식은 괄호 균형이 명확해서 중괄호 하나 날려먹는 류의 실수가 줄어듦
  - 함수 시그니처와 match arm이 같은 문법 체계 안에 들어오는 느낌이 리스프 쪽 감성 포인트임

- 물론 이걸 당장 업무 코드에 쓰자는 얘기는 아님
  - 작성자도 “mostly for fun”이라고 못 박음
  - 그래도 러스트의 어려움이 문법인지, 타입 시스템인지, 매크로 모델인지 분해해서 생각하게 만드는 데는 꽤 좋은 장난감임
  - 특히 언어 설계나 DSL, 매크로 시스템에 관심 있는 개발자라면 구경할 만함

---

## 기술 맥락

- 이 프로젝트의 선택은 “새 언어를 만들자”가 아니라 “러스트 앞에 다른 문법층을 얹자”에 가까워요. 그래서 타입 검사와 빌림 검사를 직접 구현하지 않아도 되고, 이미 검증된 rustc의 안전성 모델을 그대로 가져갈 수 있거든요.

- 매크로를 에스표현식 변환 함수로 둔 것도 중요한 선택이에요. 러스트의 `proc_macro`는 강력하지만 토큰 단위 처리와 별도 생태계가 필요해서 진입 장벽이 높은 편인데, 리스프식 모델은 코드가 곧 데이터라는 전제 덕분에 매크로 작성이 훨씬 직접적이에요.

- 대신 트레이드오프도 분명해요. 러스트의 모든 문법과 에디터 도구, 포매터, 생태계 관습은 기본적으로 `.rs` 소스를 중심으로 돌아가니까, rlisp는 변환 전 코드와 변환 후 코드 사이의 디버깅 경험을 계속 신경 써야 해요.

- raw Rust escape hatch를 둔 이유도 여기서 나와요. 실험용 문법층이 러스트 전체를 한 번에 덮으려 하면 금방 막히기 때문에, 부족한 부분은 원문 러스트로 빠져나가게 해두는 편이 실제로 더 오래 굴러가요.

## 핵심 포인트

- 에스표현식으로 작성한 코드를 러스트 소스 파일로 변환한 뒤 rustc가 타입 검사와 빌림 검사를 그대로 수행함
- 매크로는 proc_macro나 토큰 스트리밍 없이 에스표현식을 받아 에스표현식을 반환하는 함수처럼 동작함
- 제네릭, 라이프타임, 모듈, match guard, where 절, associated type, turbofish 같은 러스트 기능 상당수를 문법 레퍼런스에 포함함
- 표현 불가능한 부분은 raw Rust 블록으로 빠져나갈 수 있게 설계함

## 인사이트

이 프로젝트의 재미는 러스트를 대체하려는 데 있지 않고, 러스트에서 가장 무거운 부분인 문법 표면을 걷어내면 타입 시스템과 빌림 검사기가 얼마나 독립적으로 남는지 보여준다는 점에 있음. 특히 매크로 쪽은 러스트의 proc_macro가 얼마나 복잡한 모델인지 새삼 비교하게 만듦.
