compose-rust 가이드

Rust로 쓰는 네이티브 UI

Rust로 Compose 모양의 UI를 쓰면, Compose가 그립니다.

compose-rust는 Rust에 Compose 모양의 API를 줍니다. composable 함수, remember, recomposition입니다. 그것들이 기술한 것은 미리 컴파일된(AOT) Compose 렌더러가 그립니다. 웹뷰를 넣지 않고, 애플리케이션과 함께 JVM을 배포하지도 않습니다.

무엇인가

compose-rust는 네 가지를 합친 것입니다.

왜 만드나

하루 종일 켜 두는 애플리케이션을 겨냥합니다. 그런 앱은 무게가 매시간 느껴집니다. 웹뷰는 브라우저 엔진을 함께 데려옵니다. 동봉된 Java 런타임은 jlink 를 거쳐도 80~120 MB를 더합니다.

순수 Rust UI 툴킷은 글을 많이 다루는 앱에 가장 중요한 곳, 곧 글자 자체에서 막힙니다. 한국어, 일본어, 중국어 입력은 플랫폼 IME를 거치고, 90%만 맞는 IME로는 앱을 쓸 수 없습니다. Compose Multiplatform에는 이미 성숙한 텍스트, IME, 위젯 동작이 있습니다. compose-rust는 딱 그것을 빌려 옵니다. 애플리케이션 코드는 Rust에 남고, Compose는 Rust가 보낸 UI 트리를 해석합니다.

제약결과
웹뷰 없음WKWebView, WebView2, WebKitGTK를 쓰지 않고, Tauri나 wry도 쓰지 않습니다.
동봉된 JVM 없음렌더러는 미리 컴파일합니다. JVM은 렌더러 자체를 개발하는 셸에만 나타납니다.
손으로 쓴 JNI나 cinterop 없음경계 심은 모두 Rust 스키마에서 생성하므로 두 쪽이 모르게 어긋날 수 없습니다.
UI 코드는 Rust에Kotlin은 렌더러의 구현이지, 기능을 작성하는 곳이 아닙니다.
Compose 수준의 텍스트와 IMECompose의 플랫폼 텍스트 입력을 우회하는 코드 경로가 없습니다.

현재 상태

0.0.x는 이름을 확보하고 렌더러를 배포하는 단계입니다. API는 바뀝니다. 0.0.1 크레이트에 지금 들어 있는 것은 다음과 같습니다.

부분상태내용
경계구현렌더러가 부르는 compose_rust_host_* C 함수들, Host, LaunchBuilder, launch_runtime, 그리고 워커 스레드에서 프레임을 깨우는 request_frame_from_worker 입니다.
프로토콜구현고정 레이아웃 mutation 레코드입니다. 배치 버퍼 하나에 쓰이고 렌더러가 그 자리에서 읽습니다. 핫 패스에 직렬화 포맷이 없습니다.
스키마구현위젯, 속성, Modifier, 디자인 역할, Theme, DesignSystem 입니다. 서로 다른 스키마로 빌드된 Host와 렌더러는 스키마 해시 때문에 함께 돌지 못합니다. codegen 바이너리가 여기서 Kotlin 쪽을 생성합니다.
Runtime 트레이트구현트리를 만드는 쪽이 구현하는 인터페이스와 그 주변 타입입니다. 에셋, 커스텀 드로잉, 입력 이벤트, 알림, 메시지, 팔레트, 창 크기 클래스가 있습니다.
렌더러 다운로드구현빌드 스크립트가 대상에 맞는 미리 빌드된 렌더러를 받아 검증하고 링크합니다. 시작하기를 보십시오.
작성 API계획#[composable], remember, Recomposer, launch, 위젯 함수입니다. 2026-10-20의 1.0.0에 들어오며, #23 과 #64 가 추적합니다.

1.0.0 전까지 이 크레이트는 트리를 만드는 층이 올라설 토대입니다. 아직 화면을 직접 쓰는 도구가 아닙니다.

계획한 모양

계획이며, 아직 크레이트에 없습니다

아래 코드는 작성 API가 가질 모양을 보입니다. 0.0.x에서는 컴파일되지 않고, 이름도 확정되지 않았습니다.

rust, 계획한 모양(0.0.x에서 컴파일되지 않음)
#[composable]
fn counter() {
    let count = remember(|| mutable_state_of(0));
    Column(Modifier::new().fill_max_width(), || {
        Text(format!("{}", count.get()));
        Button(|| count.set(count.get() + 1), || Text("+1"));
    });
}

fn main() {
    compose_rust::launch(counter);
}

정해진 것은 세 가지입니다. #[composable] 이 그룹을 넣으므로 그룹이나 키를 손으로 쓰지 않으며, 분기, 반복문, 조기 return, ?, 되감기를 지나도 그룹이 맞게 닫힙니다. 이름은 Compose를 따릅니다. 위젯 어휘는 스키마에 있는 그것 하나이므로 두 번 정의하지 않습니다. 작성 API의 나머지는 UI 작성하기에 있습니다.

Dioxus와 rsx!가 좋다면 dioxus-compose를 쓰십시오. compose-rust 위에서 돌고, HTML/CSS rsx!와 Compose 위젯 rsx!를 모두 지원합니다.

플랫폼

아래 표는 0.0.1에서 플랫폼마다 받는 렌더러입니다. "처음부터 끝까지 동작"은 사람이 직접 써 본 플랫폼이라는 뜻입니다. "빌드되고 시작됨"은 CI가 빌드하고 스모크 테스트가 창을 열어 그린다는 뜻이며, 증명하는 것이 그보다 적습니다.

플랫폼상태0.0.1의 렌더러
macOS (arm64) 처음부터 끝까지 동작 애플리케이션 옆에 놓이는 GraalVM 네이티브 이미지 라이브러리입니다. 실행 파일 안에 링크되는 Kotlin/Native 렌더러(자기 창, Metal로 그림)는 CI에서 돌고 있고, 다음 릴리스에서 이것을 대신합니다(#22). 기본적인 한글 IME 입력은 동작하고, IME 체크리스트 전체는 아직 끝나지 않았습니다.
Linux (x64, arm64) 빌드되고 시작됨 GraalVM 네이티브 이미지 라이브러리입니다. Kotlin/Native 렌더러(자기 X11 창, GLX로 그림)는 두 아키텍처 모두 CI에서 헤드리스 시작 테스트를 통과하고, 다음 릴리스에서 이것을 대신합니다.
Windows 빌드되고 시작됨 GraalVM 네이티브 이미지이고, 렌더러가 바뀔 때마다 스모크 테스트를 합니다. Windows도 실행 파일 하나가 되도록 Kotlin/Native로 옮겨 가는 중입니다(#24).
iOS 빌드되고 시작됨 같은 C 심볼을 내보내는 Kotlin/Native 정적 아카이브이고, XCFramework로 릴리스합니다.
Android 처음부터 끝까지 동작 Kotlin Activity가 프로세스와 루프를 갖고, Rust는 cdylib입니다. 양쪽 JNI 심은 스키마에서 생성합니다. 크레이트가 렌더러의 Kotlin 소스를 싣고 있고, 빌드 스크립트가 그것을 Gradle 프로젝트에 풀어 놓습니다.
Web (wasm) 처음부터 끝까지 동작 WebAssembly.Memory 하나를 Kotlin/Wasm 모듈이 갖고 Rust 모듈이 가져다 쓰므로, 배치는 쓰인 자리에서 읽힙니다. 렌더러에서 Host로 가는 호출은 생성된 JavaScript 포워더를 거치며, 약 12 ns로 측정했습니다. 포워더 없는 직결 호출은 계획입니다(#54).

실제 무게

notepad 샘플을 릴리스로 빌드하고 스트립한 숫자이며, Kotlin/Native 렌더러를 링크했습니다. 0.0.1이 배포하는 경로가 아니라 0.0.1 다음에 기본이 되는 경로입니다. 결과는 실행 파일 하나이고, 옆에 놓이는 런타임도 안에 든 가상 머신도 없습니다.

플랫폼실행 파일실제 점유 메모리
macOS (arm64)28.76 MB35.1 MB
Linux (x86-64)37.93 MB아직 재지 않음
Windows아직 재지 않음아직 재지 않음

비교하자면, 렌더러가 아직 Java 런타임을 싣고 있던 때 macOS의 같은 종류 앱은 파일 넷, 93.1 MB였고 실제 점유는 56 MB였습니다. 브라우저에서는 렌더러와 Skia를 합쳐 gzip으로 6.95 MB입니다.

다음으로