Determinate Nix의 실험적 기능 `builtins.wasm`를 통해 Nix 평가 단계에서 WebAssembly 함수를 호출해 언어를 확장하는 방법, YAML 같은 기능을 추가하는 예시, 성능 특성, 배포/관리 방식, 현재 상태와 사용 방법을 소개한다.
Determinate NixSecure PackagesFlakeHubInstall NixDocs
더보기
탐색
Determinate NixSecure PackagesFlakeHubInstall NixDocs
기타 리소스
Learn NixOpen sourceEnterpriseEvent calendarWebinarsAboutPeopleCareersBlogStatus

2026년 3월 5일 작성, Eelco Dolstra
nixdeterminate-nixnix-languagewebassemblywasmwasi
읽는 데 10분
Nix 언어는 소프트웨어 구성 설정을 기술하기 위한 도메인 특화 언어입니다. 튜링 완전이긴 하지만, 애초에 범용 언어로 의도된 것은 아니었습니다. 복잡한 프로그래밍 작업을 하기에는 Rust 같은 현대적 언어가 제공하는 편의 기능이 부족합니다.
Nix 언어는 어떤 형태의 JIT 컴파일도 없는 완전한 인터프리터 언어이기도 하므로, 계산 집약적인 작업에 그다지 적합하지 않습니다. 대부분의 경우 이는 Nix 사용자에게 큰 장애물이 되지 않지만, 언어에 builtin 함수로 제공되지 않는 일을 Nix에서 해야 할 때 문제가 됩니다.
예를 들어, 어떤 구성 데이터를 추출하기 위해 Nix에서 YAML 파일을 파싱해야 한다고 가정해 보겠습니다. 아쉽게도 Nix에는 builtin YAML 파서가 없습니다. 그래서 몇 가지 선택지가 있습니다:
Nix로 YAML 파서를 작성한다. Nix는 이런 종류의 문자열 처리에 그다지 좋은 언어가 아니기 때문에, 이는 상당히 벅차고 즐겁지 않은 작업입니다. 결과로 나오는 파서도 꽤 느리고 메모리를 많이 먹게 됩니다.
Nix에 YAML 파서를 builtin 함수로 추가한다. 이는 C++로 작성해야 하지만, C++용으로 이미 존재하는 YAML 파서 라이브러리를 재사용할 수 있습니다. 하지만 이를 업스트림에 받아들여지도록 하는 건 쉽지 않을 것입니다. 주된 이유는 YAML이 복잡한 반면, Nix 언어는 릴리스 간 재현 가능(reproducible)하도록 의도되었기 때문입니다. 따라서 YAML 파서 의존성을 업데이트하면 Nix 버전 간 평가 결과가 달라질 수 있는데, 이는 builtins.fromTOML에서 실제로 문제가 되었던 사례가 있습니다. 그리고 설령 새 builtin 함수를 받아냈다고 해도, 릴리스에 포함되어 모두가 사용할 수 있게 되기까지는 시간이 꽤 걸립니다.
Nix 플러그인을 작성한다. 플러그인도 C++로 작성해야 한다는 점에서 앞의 접근과 비슷하지만, 업스트림에 받아들여질 필요는 없습니다. 대신 YAML 파서를 호출하는 Nix 식을 사용하는 모든 사람이 해당 플러그인을 설치했는지 보장해야 합니다. 이는 이를 사용하는 Nix flake가 더 이상 자체 완결적(self-contained)이 아니게 됨을 의미하며, 어떤 flake가 특정 플러그인을 요구한다고 선언할 수 있는 편리한 메커니즘도 없습니다.
“import-from-derivation”(IFD)을 사용한다. 즉, 원하는 어떤 언어나 도구로든 YAML 파싱을 수행해 derivation 안에서 실행하고, 그 결과를 import합니다. 예시는 다음과 같습니다:
builtins.fromJSON ( runCommand "parse-yaml" { src = ./input.yaml; } ``...run some command that converts $src from YAML into JSON...``)
하지만 IFD는 realising 과정에서 많은 의존성을 다운로드하고 빌드해야 할 수 있어 비용이 큰 메커니즘입니다. 또한 구성의 평가(evaluating)와 빌드(building) 사이의 분리를 깨뜨리므로, nix flake show 같은 작업이 예상치 못하게 많은 것들을 다운로드하고 빌드하기 시작할 수 있습니다.
IFD는 큰 소스 트리를 순회하고자 할 때(예: 소스 파일의 의존성을 찾아내기 위해) 특히 부적합합니다. 전체 소스 트리를 Nix 스토어로 복사해야 하기 때문입니다—lazy trees가 있더라도 말입니다.
Determinate Nix는 이제 Nix 언어를 확장하는 더 나은 방법을 제공합니다: WebAssembly의 힘을 통해서입니다.
WebAssembly (Wasm)는 Nix에 매력적인 이유와 거의 같은 이유로 만들어졌습니다. 웹 브라우저에서 JavaScript 프로그램이 계산 비용이 큰 작업을 더 성능 좋은 언어로 오프로딩할 수 있도록 하기 위해서입니다. Wasm은 Rust, C++, Zig 등 다양한 고수준 언어에서 컴파일될 수 있는 저수준 바이너리 명령 형식입니다. 빠르고, 이식 가능하며, 안전하도록 설계되었습니다. 구현도 많고, Wasmtime과 WasmEdge처럼 C++에 임베드할 수 있는 구현도 여러 가지가 있습니다.
WebAssembly에는 정확히 정의된 의미론(semantics)이 있습니다. 불순한 외부 함수(“host functions”라는 Wasm 용어로 부르는)에 접근하지 않는 한, WebAssembly 함수 호출은 실행될 때마다 항상 같은 결과를 냅니다. 예를 들어 WebAssembly는 기본적으로 난수 소스에 접근할 수 없습니다. 이는 Nix가 재현 가능하도록 의도되었다는 점에서 결정적으로 중요합니다. 이것이 없다면 Wasm 함수가 언어의 순수성(purity)을 깨뜨릴 수 있습니다.
builtins.wasmbuiltins.wasm 함수는 Nix에서 WebAssembly 함수를 호출할 수 있게 해줍니다. 다음은 n번째 피보나치 수를 계산하는 Wasm 함수를 호출하는 예시입니다:
nix-repl> builtins.wasm { path = ./nix_wasm_plugin_fib.wasm; function = "fib"; } 33warning: 'nix_wasm_plugin_fib.wasm' function 'fib': greetings from Wasm!5702887
따라서 Wasm 함수를 호출하려면 Wasm 모듈의 경로와 호출하려는 함수의 이름을 제공해야 합니다. Wasm 함수는 단일 Nix 값을 입력으로 받아(이 경우 33), 단일 Nix 값을 출력으로 반환합니다. 다만 이 값들은 임의로 복잡한 Nix 값일 수 있으며, 예를 들면 attrset 같은 것도 가능합니다.
Wasm 모듈은 Wasm을 타깃으로 컴파일할 수 있는 컴파일러가 있는 어떤 언어로도 작성할 수 있습니다. nix_wasm_plugin_fib.wasm는 Rust로 작성되었습니다. 다음은 소스 코드입니다:
use nix_wasm_rust::{warn, Value};
#[no_mangle]pub extern "C" fn fib(arg: Value) -> Value { warn!("greetings from Wasm!");
fn fib2(n: i64) -> i64 { if n < 2 { 1 } else { fib2(n - 1) + fib2(n - 2) } }
Value::make_int(fib2(arg.get_int()))}
nix_wasm_rust는 Nix가 Wasm 모듈에 제공하는 Wasm 호스트 함수에 대한 Rust 래퍼를 제공하는 지원 크레이트입니다. Value 타입은 (아직 평가되지 않았을 수도 있는) Nix 값을 나타냅니다. arg.get_int() 호출은 Nix에 대한 호스트 함수 호출을 수행해 arg 값이 정수로 평가되는지 확인하고 그 값을 반환합니다. 반대로 Value::make_int()는 새로운 Nix 정수 값을 생성합니다. attrset과 리스트를 포함해 다른 Nix 데이터 타입을 접근하거나 구성하는 비슷한 함수들도 있습니다. warn!() 매크로는 stderr에 메시지를 출력하는 호스트 함수를 호출합니다.
builtins.wasm을 사용하면, Rust에는 YAML을 파싱하고 생성하는 크레이트가 이미 있기 때문에 YAML 지원을 추가하는 일은 꽤 간단합니다. 다음은 Rust로 구현한 fromYAML입니다:
use nix_wasm_rust::{Type, Value};use yaml_rust2::{Yaml, YamlLoader};
#[no_mangle]pub extern "C" fn fromYAML(arg: Value) -> Value { Value::make_list( &YamlLoader::load_from_str(&arg.get_string()) .unwrap() .iter() .map(yaml_to_value) .collect::<Vec<_>>(), )}
fn yaml_to_value(yaml: &Yaml) -> Value { match yaml { Yaml::Integer(n) => Value::make_int(*n), Yaml::String(s) => Value::make_string(s), Yaml::Array(array) => { Value::make_list(&array.iter().map(yaml_to_value).collect::<Vec<_>>()) } Yaml::Hash(hash) => Value::make_attrset(...), ... }}
Nix는 Rust로 작성된 Wasm 런타임인 Wasmtime을 사용하며, Cranelift라는 JIT 코드 생성기를 탑재하고 있습니다. 그 결과 코드는 동등한 Nix 코드보다 훨씬 빠릅니다. 예를 들어 다음은 Nix로 작성한 피보나치입니다:
Terminal window
# command time nix eval --expr 'let fib = n: if n < 2 then 1 else fib (n - 1) + fib (n - 2); in fib 40'16558014177.52user 1.66system 1:19.33elapsed 99%CPU (0avgtext+0avgdata 4570812maxresident)k
그리고 아래는 위에서 보여준 Rust Wasm 버전을 사용한 것입니다:
Terminal window
# command time nix eval --impure --expr 'builtins.wasm { path = ./nix_wasm_plugin_fib.wasm; function = "fib"; } 40'warning: 'nix_wasm_plugin_fib.wasm' function 'fib': greetings from Wasm!1655801410.31user 0.02system 0:00.33elapsed 100%CPU (0avgtext+0avgdata 30076maxresident)k
79.33초에서 0.33초로, 240배 가속입니다! 주목할 점은 0.33초에는 코드 생성 오버헤드가 포함되어 있다는 것입니다. Nix는 이를 호출 간 디스크에 캐시할 수도 있지만 현재는 그렇지 않습니다. 그뿐만 아니라 Wasm 버전은 메모리도 훨씬 적게 사용합니다. 4.5GB 대신 30MB로, 151배 감소입니다.
하지만 모두 좋은 것만은 아닙니다. Wasm 호출은 매 호출마다 새로운 Wasm 인스턴스를 만들어야 하므로 무시할 수 없는 오버헤드가 있습니다. 같은 함수에 대한 호출 사이에서 인스턴스를 재사용할 수는 없는데, 그렇게 되면 함수가 전역 카운터를 유지하는 식의 불순한 일을 할 수 있기 때문입니다. 다만 우리는 Wasmtime의 사전 인스턴스화(pre-instantiation) 기능을 사용하여, Wasm 모듈을 파싱하고 컴파일하는 작업은 Nix 프로세스당 한 번만 수행합니다. Intel i7-1260P에서 Nix는 초당 약 123,000회의 Wasm 호출을 처리할 수 있습니다. 반면 “네이티브” 함수 호출은 초당 약 280만 회 정도 처리할 수 있습니다. 따라서 Wasm은 더 큰 작업에 사용하는 것이 가장 좋습니다.
Wasm 모듈은 종종 크기가 충분히 작아서 Git 저장소에 직접 커밋할 수 있습니다. 예를 들어 YAML을 파싱하고 생성하는 컴파일된 Wasm 모듈은 180KiB입니다—Nixpkgs 같은 저장소에 추가하기에도 아마 허용 가능한 크기일 것입니다.
또는 다음처럼 평가 시점에 Wasm 모듈을 가져올 수도 있습니다:
builtins.wasm { path = builtins.fetchurl https://.../nix_wasm_plugin_fib.wasm; function = "fib";} 33
flake를 사용한다면 file flake 입력 타입을 사용해 HTTP로 단일 Wasm 모듈을 가져올 수 있습니다. 이렇게 하면 nix flake update로 Wasm 의존성을 자동으로 업데이트할 수 있습니다.
마지막으로 import-from-derivation을 사용해 소스에서 Wasm 모듈을 선언적으로 빌드할 수도 있습니다. 하지만 그러면 다시 import-from-derivation을 쓰는 셈이 되어, 목적을 다소 무색하게 만듭니다!
builtins.wasm은 현재 Determinate Nix의 실험적 기능입니다. 또한 업스트림 Nix에 추가하기 위한 PR도 있습니다.
builtins.wasm을 시험해 보고 싶다면, Determinate Nix를 설치하거나 셸 세션에 Determinate Nix CLI를 추가하세요:
Terminal window
nix shell github:DeterminateSystems/nix-src
nix-wasm-rust 저장소의 예제 Wasm 함수 모음을 받으려면 다음을 실행하세요:
Terminal window
nix build github:DeterminateSystems/nix-wasm-rust
그다음 동작 여부를 테스트합니다:
Terminal window
nix eval --extra-experimental-features wasm-builtin \ --impure --raw --expr \ 'builtins.wasm { path = ./result/nix_wasm_plugin_mandelbrot.wasm; function = "mandelbrot"; } { width = 60; }'
Rust로 Wasm 함수를 작성하고 싶다면, nix-wasm-rust 크레이트가 Nix와 인터페이스하기 위해 필요한 모든 것을 제공합니다. 또한 Nix를 사용해 Rust를 Wasm으로 컴파일하는 방법에 대한 블로그 글도 있는데, 도움이 될 수 있습니다. 다른 언어의 경우 Determinate Nix 매뉴얼의 Wasm Host Interface 문서를 참고하세요. 이 인터페이스는 변경될 수 있으며, 이것이 builtins.wasm이 아직 실험적인 주된 이유입니다. Nix Wasm 함수를 작성하는 데 대한 피드백을 환영합니다—특히 호스트 인터페이스에서의 제약을 겪는다면 알려 주세요.
Nix에서 Wasm의 적용처는 Nix 언어 확장만이 아닙니다. Wasm은 플랫폼 독립적인 derivation 빌더도 가능하게 하며, 이는 또한 여러 흥미로운 가능성을 열어줍니다. 하지만 이 주제는 다른 블로그 글에서 다루겠습니다.
Nix 언어에는 비판자도 있지만, 그럼에도 Nix가 오랫동안 안정적인 토대를 제공해 온 것은 사실입니다. Nix 사용이 계속 증가하는 지금, 언어의 역사적 한계 일부를 넘어설 수 있도록 밀어붙이고, Nix 생태계가 이를 통해 무엇을 만들어낼지 지켜보기에는 좋은—그리고 흥미로운—시기처럼 느껴집니다.
Wasm 지원은 Determinate Systems와 Shopify의 협업 결과입니다. Shopify의 Surma가 첫 번째 프로토타입을 개발했고, Wasm을 통해 Nix에서 JavaScript를 실행하는 함수를 작성했습니다.
이미 Determinate Nix가 설치되어 있다면, Determinate Nixd 명령 하나로 3.17.0으로 업그레이드할 수 있습니다:
3.17.0 버전 업그레이드 명령
sudo determinate-nixd upgrade
아직 Determinate Nix가 설치되어 있지 않다면, macOS에서는 그래픽 설치 프로그램을 사용해 Determinate Nix로 업그레이드하거나 마이그레이션할 수 있습니다:
지금 macOS에 Determinate Nix 설치 🍎 Apple Silicon(aarch64-darwin) 지원 포함
리눅스에서는:
Linux에 Determinate Nix 설치
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ sh -s -- install --determinate
NixOS에서는 전용 NixOS 모듈 또는 Determinate Nix가 미리 설치된 NixOS ISO(x86_64용 NixOS 설치 이미지, ARM용 NixOS 설치 이미지) 사용을 권장합니다.
GitHub Actions에서는:
.github/workflows/nix-ci.yaml
on: pull_request: workflow_dispatch: push: branches: - main
jobs: nix-ci: runs-on: ubuntu-latest
# FlakeHub에 로그인하고 비공개 flake에 접근하려면 이 블록을 포함하세요
permissions: id-token: write contents: read
steps: - uses: actions/checkout@v5 - uses: DeterminateSystems/flake-checker-action@main - uses: DeterminateSystems/determinate-nix-action@v3 - uses: DeterminateSystems/flakehub-cache-action@main - run: nix flake check
Amazon Web Services에서는:
aws.tf
data "aws_ami" "detsys_nixos" { most_recent = true owners = ["535002876703"]
filter { name = "name" values = ["determinate/nixos/epoch-1/*"] }
filter { name = "architecture" values = ["x86_64"] }}

작성자 Eelco Dolstra
Eelco는 Utrecht University의 박사 과정 학생으로 Nix 프로젝트를 시작했습니다. 그는 Determinate Systems의 공동 창업자이며 Nix 팀의 일원입니다.
private flakes 및 FlakeHub Cache에 접근하고 싶으신가요?
최신 업데이트 받기
구독
hello@determinate.systems +1 (641) NIX-HELP (649-4357)
탐색
기타 리소스
컴플라이언스
AI 도구용
© 2021-2026 Determinate Systems. 판권 소유.