Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

간단한 컴포넌트 빌드하기 (Rust)

Rust는 툴체인에서 사용할 수 있는 대상을 통해 WebAssembly 코어와 WebAssembly 컴포넌트를 일급으로 지원합니다.

Note

위 대상을 사용하려면 Rust 툴체인(예: rustup)을 통해 해당 대상이 활성화되어 있는지 확인하십시오.

예를 들어, wasm32-wasip2 대상을 추가하려면 다음과 같이 합니다(사용 가능한 모든 툴체인을 표시하려면 rustup toolchain list를 사용할 수 있습니다).

rustup target add wasm32-wasip2

기본 제공 지원 덕분에 Rust 코드(및 표준 라이브러리)는 네이티브 도구를 사용하여 WebAssembly로 컴파일할 수 있습니다:

cargo build --target wasm32-wasip2

Warning

과거에는 cargo-component 사용이 권장되었지만, 이제는 네이티브 도구를 직접 사용할 수 있으므로 이 프로젝트는 지원 중단 절차를 밟고 있습니다.

1. 설정

WebAssembly 바이너리를 검사하고 조작할 수 있도록 wasm-tools를 설치하십시오:

cargo install --locked wasm-tools

WebAssembly 바이너리를 위한 빠르고 안전한 런타임인 wasmtime을 설치하십시오:

curl https://wasmtime.dev/install.sh -sSf | bash

2. Rust로 WebAssembly 프로젝트 만들기

cargo new로 Rust 프로젝트를 새로 만드십시오:

$ cargo new --lib adder
    Creating library `adder` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Rust WebAssembly 프로젝트를 빌드할 때는 WebAssembly로 컴파일할 수 있는 바이너리 크레이트와 라이브러리 크레이트 중 하나를 만들 수 있으며(각각 “command” 컴포넌트와 “reactor” 컴포넌트를 생성합니다), 이 경우에는 리액터 컴포넌트를 선택하십시오.

Note

command 컴포넌트와 리액터 컴포넌트의 차이는 아직 중요하지 않지만, 바이너리와 공유 라이브러리의 차이와 비슷한 것으로 생각할 수 있습니다.

Rust 프로젝트에 반드시 추가해야 하는 한 가지는 Cargo.toml에서 crate-type을 설정하는 것입니다:

[lib]
crate-type = ["cdylib"]

리액터 컴포넌트(함수 라이브러리의 “등가물”)를 빌드하므로, Rust가 제공하는 cdylib(“c dynamic library“의 약자) 크레이트 타입을 사용해야 합니다.

3. add 인터페이스 추가하기

docs:adder WebAssembly 인터페이스 타입(WIT) 패키지의 adder 월드 월드가 익스포트하는 add 인터페이스를 구현하는 컴포넌트를 Rust로 만들겠습니다.

wit/world.wit라는 파일을 만들고 다음 내용으로 채우십시오:

package docs:adder@0.1.0;

interface add {
    add: func(x: u32, y: u32) -> u32;
}

world adder {
    export add;
}

이 파일의 (WIT) 타입은 우리 컴포넌트가 충족해야 하는 인터페이스(adder world)를 나타냅니다. 이를 두고 우리 컴포넌트가 add 인터페이스(그 자체로 단일 함수 add를 포함함)를 “익스포트“한다고 말합니다.

이러한 타입을 다루는 방식은 다른 인터페이스 정의 언어(IDL) 툴체인(예: protobuf)과 비슷한데, 인터페이스를 쉽게 구현할 수 있도록 해 주는 언어 수준의 바인딩이 필요하다는 점에서 그렇습니다.

4. adder 인터페이스용 바인딩 생성하기

Rust 툴체인은 WebAssembly 바이너리를 네이티브로 컴파일할 수 있지만, 의도한 (WIT) 인터페이스 타입(wit/world.wit)과 일치하는 바인딩을 자동으로 생성하지는 (아직) 못합니다.

wit-bindgen을 사용하여 바인딩을 생성할 수 있습니다:

cargo add wit-bindgen

Note

올바른 프로젝트의 의존성에 wit-bindgen이 추가되도록 위 명령은 cargo new로 생성한 adder 디렉터리 안에서 실행해야 합니다. 또는 Cargo.toml의 의존성 섹션에 wit-bindgen을 직접 추가할 수도 있습니다.

wit-bindgen-cli 크레이트를 통해 wit-bindgen을 바이너리로 사용할 수도 있지만, 여기서는 코드 우선(code-first) 방식의 바인딩 빌드에 초점을 맞춥니다.

wit-bindgen이 Rust 프로젝트에 포함되면(즉, Cargo.toml에 추가되면) 이를 사용하여 WIT 인터페이스에 대한 Rust 코드 바인딩을 생성할 수 있습니다. src/lib.rs 파일을 다음과 같이 수정하십시오:

#![allow(unused)]
fn main() {
mod bindings {
    //! This module contains generated code for implementing
    //! the `adder` world in `wit/world.wit`.
    //!
    //! The `path` option is actually not required,
    //! as by default `wit_bindgen::generate` will look
    //! for a top-level `wit` directory and use the files
    //! (and interfaces/worlds) there-in.
    wit_bindgen::generate!({
        path: "wit/world.wit",
    });
}
}

여기서는 wit_bindgen::generate 매크로가 출력한 코드를 담는 bindings라는 모듈을 생성합니다. wit_bindgen은 다양한 struct, interface, enum 등을 생성할 수 있으므로, 이러한 새 타입을 나중에 참조할 수 있는 모듈로 분리해 두는 것이 바람직한 경우가 많습니다.

현재 이 코드는 별다른 일을 하지 않는데, 이는 아직 구현을 추가하지 않았기 때문입니다.

5. 생성된 Guest 트레이트를 통해 adder 월드 구현하기

src/lib.rs에서 bindings::Guest 트레이트를 구현하여 컴포넌트의 기능을 채울 수 있습니다. 코드는 다음과 비슷한 모습이어야 합니다:

mod bindings {
    //! This module contains generated code for implementing
    //! the `adder` world in `wit/world.wit`.
    //!
    //! The `path` option is actually not required,
    //! as by default `wit_bindgen::generate` will look
    //! for a top-level `wit` directory and use the files
    //! (and interfaces/worlds) there-in.

    // The line below will be expanded as Rust code containing
    wit_bindgen::generate!({
        path: "wit/adder/world.wit",
    });

    // In the lines below we use the generated `export!()` macro reuse and
    use super::AdderComponent;
    export!(AdderComponent);
}

/// Struct off of which the implementation will hang
///
/// The name of this struct is not significant.
struct AdderComponent;

impl bindings::exports::docs::adder::add::Guest for AdderComponent {
    fn add(x: u32, y: u32) -> u32 {
        x + y
    }
}

위 코드 목록에서 주목할 점이 몇 가지 있습니다:

  1. AdderComponent 구조체가 도입되지만, 이는 Guest 트레이트의 구현체로서만 유용합니다.
  2. bindings::exports::docs::adder::add::Guest 트레이트는 내보내지는 docs:adder/add 인터페이스를 그대로 반영합니다.
  3. (1)과 (2)에 따라, AdderComponent는 생성된 바인딩을 통해 (WIT 관점에서) adder 월드를 구현합니다.
  4. export!() 매크로는 wit_bindgen::generate! 매크로에 의해 생성되며, 중요한 설정을 수행합니다.
    • export!는 bindings 모듈 내부에서 사용하는 것이 가장 쉽지만, 그 경우 super::AdderComponent 구조체를 참조해야 합니다

Note

wit_bindgen::generate! 매크로가 생성한 코드를 자세히 살펴보려면 cargo-expand 크레이트를 사용할 수 있습니다

6. 컴포넌트 빌드하기

이제 네이티브 Rust 툴체인으로 WASI P2 컴포넌트를 빌드하여 컴포넌트를 빌드해 보겠습니다.

cargo build --target=wasm32-wasip2

이 명령은 디버그 빌드를 수행하며, target/wasm32-wasip2/debug/adder.wasm에 WebAssembly 컴포넌트를 생성합니다:

du -hs target/wasm32-wasip2/debug/adder.wasm
3.3M    target/wasm32-wasip2/debug/adder.wasm

Rust와 같은 컴파일 언어의 WebAssembly 컴포넌트로서 3메가바이트는 큰 크기입니다. 더 많은 최적화를 수행하도록 릴리스 모드로 컴파일해 보겠습니다:

cargo build --target=wasm32-wasip2 --release

릴리스 모드로 컴파일하면 훨씬 작은 바이너리를 얻을 수 있습니다:

$ du -hs target/wasm32-wasip2/release/adder.wasm
16K     target/wasm32-wasip2/release/adder.wasm

Rust 툴체인에서 일반적으로 사용할 수 있는 여러 최적화 옵션을 사용하여 바이너리 출력을 제어할 수 있다는 점에 유의하십시오.

Warning

--release로 빌드하면 결과 .wasm 파일에서 모든 디버그 관련 정보가 제거됩니다.

로컬에서 프로토타이핑하거나 테스트할 때는 오류 발생 시 유용한 백트레이스를 얻기 위해(예: wasmtime::WasmBacktraceDetails::Enable 사용 시) --release를 사용하지 않는 것이 좋을 수 있습니다. 참고: 이 경우 결과 .wasm 파일은 상당히 커집니다(4MB 이상일 가능성이 높습니다).

7. 빌드된 컴포넌트 검사하기

이제 WIT 바이너리가 있으므로 WebAssembly 컴포넌트 도구를 사용하여 이를 살펴볼 수 있습니다.

예를 들어, WebAssembly 컴포넌트는 자기 문서화(self-documenting)되어 있어 이 정보를 포함하고 있으므로, wasm-tools를 사용하여 컴포넌트의 WIT 패키지를 출력할 수 있습니다:

wasm-tools component wit target/wasm32-wasip2/release/adder.wasm

위 명령은 아래와 같은 출력을 생성해야 합니다:

package root:component;

world root {
  export docs:adder/add@0.1.0;
}
package docs:adder@0.1.0 {
  interface add {
    add: func(x: u32, y: u32) -> u32;
  }
}

8. adder 컴포넌트 실행하기

컴포넌트가 작동하는지 확인하기 위해, adder 월드를 대상으로 하는 컴포넌트를 실행할 수 있는 Rust 애플리케이션에서 이를 실행해 보겠습니다.

이 애플리케이션은 wasmtime을 사용하여 Rust “호스트”/“임베더” 바인딩을 생성하고, WASI 월드를 가져오며, 컴포넌트를 실행합니다.

component-docs 저장소를 로컬에 클론한 상태에서 다음을 실행하십시오:

$ cd examples/example-host
$ cargo run --release -- 1 2 ../add/target/wasm32-wasip2/release/adder.wasm
1 + 2 = 3

이로써 Rust로 기본적인 WebAssembly 컴포넌트를 성공적으로 빌드하고 실행했습니다 🎉


원문 © 2023 The Bytecode Alliance Contributors · WebAssembly Components Documentation · CC BY 4.0
Yeokja 한국어 번역·편집 · Anthropic 사의 claude-sonnet-5 모델을 활용하여 번역되었으며 학습을 모두 비허용한 상태로 작업하였습니다 · 이 번역은 원문을 수정한 문서이며 CC BY 4.0으로 제공합니다.