2021년부터 실험해 온 새 프로그래밍 언어 Dada의 핵심 개념과 Rust·WebAssembly와의 관계를 소개한다.
URL: https://smallcultfollowing.com/babysteps/blog/2026/02/08/fun-with-dada/
Title: Dada와 함께 놀기 · baby steps
2021년으로 한참 거슬러 올라가서, 나는 “Dada”라고 부르는 새 프로그래밍 언어를 실험하기 시작했다. 그 뒤로 계속 이것저것 만지작거렸는데, 방금 깨달았다(세상에!) Dada에 대해 블로그 글을 단 하나도 쓴 적이 없다는 걸! 이건 고쳐야겠다고 생각했다. 이 글에서는 지금 시점의 Dada에 있는 기본 개념 몇 가지를 소개한다.
오해하지 말자. Dada는 사용하기에 적합하지 않다. 사실 컴파일러도 제대로 작동하지 않는데, 내가 언어를 다 작동하게 만들기도 전에 계속 바꾸고 있기 때문이다. 솔직히 Dada는 다른 무엇보다도 나에게 “스트레스 해소” 밸브에 가깝다1. 하위 호환성이나 RFC 같은 것들, 그 밖의 온갖 것들을 걱정하지 않고도 프로그래밍 언어를 만지작거릴 수 있다는 게 재미있다.
그렇긴 하지만, Dada는 Rust에 적용할 수 있을 법한 아이디어를 엄청나게 많이 낳는 비옥한 원천이었다. 그리고 그건 언어 설계에만 국한되지 않는다. 컴파일러를 가지고 놀던 것이 새 salsa 설계2로 이어졌고, 이 설계는 지금 rust-analyzer와 Astral의 ty 둘 다에서 쓰이고 있다. 그래서 나는 그 아이디어들을 정말 바깥으로 꺼내고 싶다!
나는 약 1년 전쯤 Dada 해킹을 멈췄는데3, 지난 며칠 동안 다시 작업을 시작했다. 그러다 문득, 어이, 지금이 블로깅을 시작하기에 완벽한 때라는 걸 깨달았다! 어차피 내가 뭘 하고 있었는지 다시 찾아내야 하는데, 글로 쓰는 건 항상 디테일을 정리하는 최고의 방법이니까.
Dada는 여러 단계를 거쳐 왔다. 초기에는 사람들이 배우기 더 쉬울 거라고 생각한 점진적 타입 프로그래밍 언어를 만드는 것이 목표였다.
아이디어는 이랬다. 타입을 전혀 쓰지 않고도 코드를 작성한 다음, 프로그램을 실행할 수 있다. 그리고 인터랙티브한 플레이그라운드가 있어서, 실행을 단계별로 진행하면서 “borrow checker” 상태(Dada에서는 이를 permissions라고 부른다)를 시각화해 준다. 내 바람은 사람들이 타입 체커와 씨름하는 것보다 이것이 더 배우기 쉽다고 느끼는 것이었다.
나는 이걸 실제로 동작하게 만들었고, 꽤 멋졌다. 2022년에 Programming Language Mentoring Workshop에서 이에 대한 발표를 하기도 했다. 다만 영상을 대충 훑어보니 permission 모델링을 제대로 데모하진 않은 것 같다. 아쉽다.
동시에, 나는 점진적 타입 접근이 과연 타당한지 확신이 서지 않았다. 내가 원했던 것은, 타입 주석 없이 프로그램을 실행했을 때 borrow를 위반하는 지점에서 에러가 나는 것이었다. 그런데 그러려면 프로그램이 miri처럼 많은 추가 데이터를 추적해야 했고, 결국 교육용 도구로서만 현실적이었다. 나는 여전히 이 방향을 탐구해 보고 싶긴 하지만, 개발 여정의 아주 초기에만 관심을 끌 요소를 위해 언어 설계에 복잡도를 너무 많이 더하는 느낌도 있었다4.
그래서 나는 다시 시작하기로 했고, 이번에는 Dada의 정적 타입 검사 부분에만 집중하기로 했다.
오늘날의 Dada는 Rust와 비슷하지만 더 간소화 되어 있다. 목표는 Dada가 Rust와 같은 기본적인 “소유권 중심(ownership-oriented)”의 느낌 을 유지하되, 사용자가 마주해야 하는 선택지와 자잘한 디테일을 훨씬 줄이는 것이다.5
Rust에는 의미적으로는 동등하지만 표현이 다른 타입이 종종 있다. 예를 들어 &Option<String>과 Option<&String>을 보자. 둘은 할 수 있는 일의 관점에서는 동등하지만, Rust에서는 당연히 둘을 엄격히 구분해야 한다. Dada에서는 이 둘이 같은 타입이다. Dada는 또한 &Vec<String>, &Vec<&String>, &[String], &[&str] 및 수많은 다른 변형들도 모두 같은 타입으로 만든다. 그리고 미리 묻기 전에 말하자면, 이걸 모든 것을 힙 할당으로 바꾸거나 가비지 컬렉터를 쓰는 방식으로 해결하는 건 아니다.
한마디로 요약하면, Dada의 목표는 “as_ref()를 절대 호출할 필요가 없는 Rust” 이다.
Dada에는 더 화려한 borrow checker도 있는데, borrow checker within에서 보여 준 것의 많은 부분을 이미 시연한다(다만 view type은 없다). Dada의 borrow checker는 내부 borrow(internal borrows)를 지원한다(예: 어떤 struct가 다른 필드에서 borrow하는 필드를 갖도록 만들 수 있다). 또한 라이프타임 없이 borrow checking 하기도 지원한다. 이런 것들의 상당수는 Rust로도 가져올 수 있지만, Dada에서는 몇 가지를 조정해서 일부 측면이 더 쉽게 되도록 하긴 했다.
Dada의 초점을 재정립하는 과정 어딘가에서, 나는 WebAssembly 컴포넌트를 만드는 데만 집중하기로 결정했다. 처음엔 WebAssembly를 타깃으로 삼는 것이 정말 편리할 거라고 느꼈다:
하지만 나는 WebAssembly를 타깃으로 하는 데 또 다른 장점이 있다는 것을 깨달았다: 컴파일 타임 리플렉션이 거의 사소한 일이 된다. Dada 컴파일러는 순수 온디맨드 방식으로 구성되어 있다. 즉, 어떤 함수 하나를 WebAssembly 바이트코드까지 완전히 컴파일해 두고, 크레이트의 나머지는 건드리지 않은 채로 둘 수 있다.
그리고 WebAssembly 바이트코드를 얻고 나면, 컴파일러 내부에서 그걸 실행할 수 있다! wasmtime을 쓰면 매우 빠르게 동작하는 고품질 JIT이 있다. 게다가 코드는 샌드박스 처리된다!
그래서 실행 중에 컴파일하고 실행하는 함수를 만들 수 있고, 그 결과를 이용해 컴파일의 다른 부분에서 사용될 다른 코드를 생성할 수 있다. 다시 말해, miri나 Zig의 comptime과 비슷한 것을 사실상 공짜로 얻는 셈이다. 와.
이 글을 쓰다 보니 나도 Dada를 가지고 놀고 싶어졌다. 아쉽게도 아직은 실제로 동작하지 않는다. 하! 하지만 나는 컴파일러를 계속 다듬어서 가능한 한 빨리 라이브 데모가 가능한 수준까지 끌어올릴 계획이다. 정확히 얼마나 걸릴지는 말하기 어렵다.
그동안, 내가 다시 구조를 파악하는 데 도움이 되도록 타입 시스템, borrow checker, 그리고 컴파일러 아키텍처에 대해 일련의 블로그 글을 써 보려고 한다. 이 셋은 모두 꽤 흥미롭다고 생각한다.
맞다, 나는 새 프로그래밍 언어를 설계하면서 쉰다. 다들 그렇지 않나?↩︎
내가 원하는 방식으로 Dada 컴파일러를 작성하기 위해 salsa의 새 버전을 설계한 건, 지금 생각해 보면 정말 장대한 야크 면도(yak shave)였다.↩︎
LLM에 흥미가 생기면서 동기를 잃었다. 솔직히 말해, 프로그래밍 언어를 설계하는 것이 “지난 전쟁을 치르는 것”인지 이해하려면 LLM에 대해 충분히 배워야 한다고 느꼈다. LLM을 이것저것 많이 만져 본 결과, 그것들이 프로그래밍 언어 선택의 중요성을 낮춘다는 건 확실히 느꼈다. 하지만 동시에 LLM은 인간보다도 더 고수준 추상화의 혜택을 크게 받는다고 생각하고, 그래서 Dada가 여전히 유용할 수 있다고 믿고 싶다. 게다가, 재미있다.↩︎
그리고 LLM 덕분에, 그 학습 기간은 그 어느 때보다 짧아졌다.↩︎
물론 이는 Dada의 유연성을 떨어뜨리기도 한다. Rust for Linux 같은 프로젝트가 Dada로는 잘 되지 않을 거라고 본다.↩︎