rokdocs
v0.1

1.8. WASM Component Model - ffi.wit

WebAssembly Component Model (bytecodealliance.org, Preview 2 는 2024년 1월에 안정화되었습니다) 은 타입이 부여된 언어 간 인터페이스를 정의하기 위한 기반입니다. 전통적인 C ABI FFI (1.7) 가 의존하던 raw pointer 와 길이 쌍 모델을, 이 체계는 record / variant / resource 라는 풍부한 타입으로 대체합니다.

1.8.1. WIT - WebAssembly Interface Types

WIT 는 언어 중립적인 인터페이스 정의 언어입니다.

package example:fs;

interface fs {
    record stat {
        size: u64,
        mtime: u64,
        is-dir: bool,
    }

    variant error {
        not-found,
        permission-denied,
        io-error(string),
    }

    resource file {
        constructor(path: string);
        read: func(buf-size: u32) -> result<list<u8>, error>;
        close: func();
    }

    open: func(path: string) -> result<file, error>;
}

이렇게 정의된 인터페이스는 Rust, Go, Python, rok 등 어떤 언어에서도 import 또는 export 의 형태로 자유롭게 활용됩니다.

1.8.2. rok 측 binding 형태

import 측은 외부 component 를 씁니다. 여기서 wit/fs.wit 는 맨 위에 package example:fs; 를 둔 1.8.1 의 interface 입니다.

ffi::wit::import { "wit/fs.wit" }       // 이 소스 파일 기준 상대 경로

use example::fs::fs::{File, Error};

let f = File::new("data.txt");
let bytes = try f.read(1024);           // throws (Error) -> List<u8>
f.close();

export 측은 component 를 제공합니다. calculator.wit 는 world 안에 add: func(a: s32, b: s32) -> s32 를 선언합니다.

ffi::wit::export { "calculator.wit" }

#[wit::export]
fn add(a: i32, b: i32) -> i32 { a + b }

경로와 위치

  • 경로는 소스 파일 기준 상대 경로입니다. .wit 파일이나 package 디렉토리를 가리키며, 디렉토리의 deps/ 에 의존이 있습니다.
  • 이 항목은 프로그램의 패키지와 의존 패키지에서 쓸 수 있습니다. binding 은 그 항목이 있는 모듈 (의존 패키지면 그 의존 패키지의 모듈) 안 ns::pkg::iface (wasi::filesystem::types) 아래에 들어갑니다.
  • WIT 타입은 PascalCase rok 타입이 됩니다 (descriptor-type → DescriptorType, file → File). 함수와 필드는 snake_case 가 됩니다.

World

이름 붙은 파일의 world 는 그 component 자체입니다. 그 밖이면 키워드가 정합니다: ffi::wit::import 는 interface 를 import, ffi::wit::export 는 export 합니다. include ... with { a as b } 는 포함한 항목의 이름을 바꿉니다.

타입 대응

WITrok
recordstruct
variant, enumenum
flagsDefault 를 가진 bool 필드의 struct (flag 32 개까지)
option<T>T? (중첩 option 은 층을 유지: option<option<T>> → T??)
result<T, E>Result<T, E>. 이것을 돌려주는 함수는 throws (E) -> T
tuple<..>튜플
list<T>List<T>
list<T, N>[T; N]
stringstr (UTF-8)
char, bool, f32, f64같음
sN / uNiN / uN
future<T>AsyncFuture<T>
stream<T>rok::core::channel::Receiver<T> (sender 를 닫으면 끝남)
error-contextrok::bare::ffi::wit::ErrorContext
import 한 resourceusing / Drop 으로 해제되는 handle struct
export 하는 resource사용자의 #[wit::export] pub struct

Async

async func 는 import 와 export 모두에서 async fn 입니다. 동기 task (preview 2 의 main, 동기 export) 는 component model 을 기다릴 수 없으며 그러면 런타임 panic 이므로, async import 는 async export 안에서 await 합니다. preview 3 command 는 wasi:cli/run@0.3.x 를 #[wit::export] async fn run() -> Result<(), ()> 로 export 합니다. async export 호출은 동시에 실행되며, 각각이 component model task 입니다. 호출은 자기 차례에 결과를 돌려줍니다. async export 안에서 socket 과 timer 는 host 의 event 사이에 기다리며, component model event 도 대기 중이면 적어도 10 ms 마다 확인합니다 (preview 2 pollable 과 component model 의 event 는 한꺼번에 기다릴 수 없기 때문). 아무것도 전해 주지 않는 것을 기다리는 async export 는 계속 기다립니다. 다른 호출이 그것을 전해 줄 수도 있습니다.

생성된 binding 이 부르는 async canonical ABI 는 rok::std::wit 에 있습니다: executor 위에서 돌며, stream<T> 는 core 타입에 대응합니다. 메모리 helper, block, handle, ErrorContext 는 rok::bare::ffi::wit 에 남습니다.

Export

export 함수는 그 항목의 모듈에 있는 #[wit::export] fn 이며, snake_case 이름으로 WIT 함수와 짝지어집니다. export 하는 resource 는 #[wit::export] pub struct 이고, 그 impl 이 new (constructor), 메서드, static 함수를 줍니다. 실패할 수 있는 constructor (result<r, ..>) 는 throw 하는 new 이며, resource 의 destructor 는 그 Drop 을 실행합니다. 빠진 export 는 컴파일 에러입니다.

WIT 를 export 하고 진입 파일에 fn main 도 top-level 문도 없는 패키지는 reactor 입니다: wasi:cli/run 을 export 하지 않습니다 (4.16.1).

타깃

WIT 에는 wasm32-wasi (WASI preview 2 component) 가 필요하며, 다른 타깃에서는 컴파일 에러입니다. export 는 main 전에 불릴 수 있습니다: 런타임은 첫 export 호출에서 시작합니다.

미리 빌드한 의존 패키지는 그 ffi::wit 항목이 읽는 .wit 파일을 ROKHIR 에 담고 다닙니다 (refs-lang 9.4.7). 의존하는 쪽은 그것을 디스크에서 읽지 않습니다.

제약

import 한 interface 는 component 가 export 하는 interface 의 타입을 쓸 수 없으며, 쓰면 컴파일 에러입니다.

1.8.3. C FFI vs WIT 차이

두 방식의 성격은 여러 축에서 뚜렷하게 갈립니다.

측면C FFI (1.7)WIT (이 장)
타입 시스템C primitive + 사용자 structrecord / variant / resource
안전성낮음 (raw ptr 손수 관리)높음 (typed)
메모리 관리malloc/freeresource (자동 destructor)
에러 처리errno / 반환값 conventionresult<T, E> 1급
Stringnull-terminatedlength-prefixed UTF-8
List(ptr, len) manuallist<T> 자동
호출 비용거의 없음 (direct call)trampoline (~ns)
적용 범위모든 native targetwasm32-wasi (preview 2 component)
unsafe 필요✓✗

1.8.4. 선택 기준

선택의 기준은 단순합니다 - 무엇을 호출할지, 어디에 배포할지에 따라 달라집니다.

시나리오권장
전통 C 라이브러리 호출C FFI (1.7)
시스템 콜 (libc, Win32)C FFI
GPU / SIMD intrinsicC FFI
다른 언어와 안전 인터페이스WIT
WASM 환경 배포WIT
플러그인 / 샌드박스 격리WIT
Modular / Mojo 등 다국어WIT

이 장이 갖는 가장 큰 의의는, rok 이 전통 C FFI 와 WIT 두 갈래를 모두 지원한다는 점에 있습니다. 사용자는 안전성, 성능, target 환경의 제약에 맞추어 자유롭게 한쪽을 고르면 됩니다.

1.8.5. 참고

더 깊이 파고들고 싶다면 다음 자료들을 권합니다.

GitHub에서 이 페이지 고치기