dioxus-compose 가이드

Rust로 쓰는 네이티브 UI

UI는 Dioxus로 쓰고, 그리는 일은 Compose에게 맡깁니다.

dioxus-compose는 Rust 코드가 rsx!와 컴포넌트, 훅으로 화면을 기술하고, 미리 컴파일해 둔 Compose Multiplatform 렌더러가 그것을 그리는 GUI 스택입니다. 웹뷰를 품지 않고, JVM을 함께 배포하지도 않습니다.

왜 만드는가

이 스택이 겨냥하는 것은 하루 종일 켜 두는 애플리케이션입니다. 늘 켜 두는 도구에서는 무게가 매시간 체감됩니다. 그런데 웹 기반 셸은 가볍지 않습니다. 웹뷰는 브라우저 엔진을 통째로 데려오고, JRE는 jlink로 줄여도 80~120 MB가 붙습니다.

그렇다고 순수 Rust UI 프레임워크로 가면, 텍스트를 다루는 앱에서 가장 중요한 부분인 텍스트 자체에서 막힙니다. 한국어·일본어·중국어 입력은 플랫폼 IME를 거치고, 90%만 맞는 IME는 결국 쓸 수 없는 앱입니다. Compose Multiplatform은 텍스트와 IME, 위젯 품질이 이미 성숙해 있습니다. 그래서 이 프로젝트는 딱 그 부분만 빌려 옵니다. 애플리케이션 코드는 Rust에 남고, Compose는 Rust가 보내는 UI 트리를 해석하는 인터프리터가 됩니다.

협상 불가 조건그래서 생기는 결과
웹뷰 금지WKWebView, WebView2, WebKitGTK를 쓰지 않습니다. Tauri와 wry도 마찬가지입니다.
JVM 동봉 금지데스크톱은 GraalVM native-image 공유 라이브러리로 배포하고, iOS는 Kotlin/Native로 만들 계획입니다.
수동 JNI·cinterop 금지경계 심(shim)은 전부 Rust 스키마에서 생성합니다.
UI 코드는 Rust에Kotlin은 렌더러의 구현 세부일 뿐, 기능을 작성하는 자리가 아닙니다.
Compose 수준의 텍스트·IMECompose의 플랫폼 텍스트 입력을 우회하는 경로를 만들지 않습니다.

코드는 이렇게 생겼습니다

저장소의 desktop_demo 예제를 줄인 것입니다. 위젯 이름은 Compose 그대로이고, 속성은 그것을 snake_case로 옮긴 Rust 표기입니다.

dioxus-compose/examples/desktop_demo.rs
use dioxus_compose::prelude::*;

fn app() -> Element {
    let mut messages = use_signal(Vec::<String>::new);
    let mut draft = use_signal(String::new);

    rsx! {
        Column {
            fill_max_width: true,
            Text { text: "dioxus-compose chat" }
            for message in messages() {
                Text { text: message }
            }
            TextField {
                placeholder: "Write a message",
                multiline: true,
                on_value_change: move |value| draft.set(value),
                on_key_down: move |event: KeyEvent| {
                    if event.key() == Key::Enter && !event.shift_key() {
                        // … draft를 messages에 넣습니다 …
                        event.consume();
                    }
                }
            }
        }
    }
}

fn main() {
    dioxus_compose::launch(app);
}

현재 상태

로드맵을 기능 목록처럼 포장하지 않고 있는 그대로 적습니다. 지금 끝에서 끝까지 도는 것은 macOS 데스크톱뿐입니다. 계획으로 표시한 항목은 SPEC.md에 설계만 되어 있고 구현은 없습니다.

플랫폼상태방식
macOS 데스크톱 (arm64) 동작 Liberica NIK 25 Full로 native-image --shared 빌드한 렌더러를 Rust 바이너리가 링크합니다. 2026-09-20 확인.
Windows / Linux 데스크톱 계획 같은 native-image 경로입니다. 빌드 스크립트는 아직 macOS 외에서는 바로 멈춥니다.
iOS 계획 Kotlin/Native -produce static으로 같은 C 심볼을 내보냅니다.
Android 계획 Kotlin이 프로세스와 루프를 소유하고, Rust cdylib을 생성된 JNI 심으로 호출합니다.
Web (wasm) 계획 Kotlin이 wasm linear memory를 소유하고 Rust가 그것을 import합니다. 배치 arena를 제자리에서 공유하므로 복사가 없고, 경계 호출은 코드젠으로 만든 JS forwarder를 거칩니다(실측 약 12 ns).

기능별 상태

기능상태메모
위젯: Column, Row, Box, Text, TextField, Button, Spacer, LazyColumn동작스키마 전체입니다. 이 밖의 것은 스키마부터 넓혀야 합니다.
이벤트와 동기 소비동작on_click, on_value_change, on_submit, on_focus_lost, on_key_downevent.consume().
비제어 TextField동작편집 값과 조합 상태를 렌더러가 소유합니다.
Rust 스키마 → Kotlin 프로토콜 코드젠동작생성물이 낡으면 테스트가 실패합니다.
fill_max_width·fill_max_height 외의 Modifier일부패딩, 크기, 배경, clickable은 프로토콜에는 있지만 rsx! 속성이 아직 없습니다.
LazyColumn 윈도잉 (FR-8)Host 쪽컴포넌트·프로토콜·Host 테스트는 통과합니다. Kotlin 인터프리터는 아직 Column으로 그리고 RangeRequested를 보내지 않습니다.
AppendText 스트리밍 (FR-9)Host 쪽Host가 프레임당 한 건으로 모아 flush합니다. rsx! 수준 API는 아직 없고 Host::append_text는 프로토콜 node id를 받습니다.
디자인 시스템과 테마 (FR-13, FR-14)계획명세는 와이어 태그까지 Agreed지만 구현된 것은 없습니다. 크레이트에 Paint, TypeRole, DesignSystem, SetTheme 모두 아직 없습니다. 디자인 시스템을 보세요.

다음으로 읽을 곳