러스트(그리고 모든 언어)에서 웹 어셈블리를 더 좋게 만들기


원문
Lin Clark, https://hacks.mozilla.org/2018/03/making-webassembly-better-for-rust-for-all-languages/

러스트(Rust) 커뮤니티의 2018년 최대 목표는 웹 언어가 되는 것이다. 웹 어셈블리(WebAssembly)를 타깃으로 러스트는 자바스크립트처럼 웹에서 실행될 수 있다. 그런데 이것이 무엇을 뜻하는 걸까? 러스트가 자바스크립트를 대체하려는 걸까?

그 대답은 아니오이다. 우리는 러스트 웹 어셈블리 앱들이 완전히 러스트만으로 작성될 것이라고 생각하지 않는다. 사실, 우리는 대다수의 애플리케이션은 여전히 자바스크립트로 작성될 것이라 생각한다. 러스트 웹 어셈블리 애플리케이션조차 말이다.

왜냐하면 거의 모든 상황에서 자바스크립트를 선택하는 것이 좋기 때문이다. 자바스크립트는 빠르고 쉽게 실행시킬 수 있다. 게다가, 다양한 문제들을 믿을 수 없이 혁신적인 방식으로 문제를 해결해온 자바스크립트 개발자들의 생생한 생태계가 있다.

img

하지만 종종 러스트 + 웹 어셈블리는 애플리케이션의 어떤 작업을 위한 올바른 도구가 될 수 있다. 소스 맵을 파싱 한다거나, 엠버(Ember)가 DOM의 변경사항을 찾는 일처럼 말이다.

그렇기에 러스트 + 웹 어셈블리가 나아가야 할 길은 러스트를 웹 어셈블리로 컴파일 하는 데서 멈추지 않는다. 우리는 웹 어셈블리가 자바스크립트 생태계에 잘 어울리도록 해야 한다. 웹 개발자들은 웹 어셈블리를 자바스크립트인 것 마냥 사용할 수 있어야 한다.

하지만 아직 웹 어셈블리는 거기까지 준비되지 않았다. 이것을 실현시키기 위해, 우리는 간편하게 웹 어셈블리를 로드시키고, 자바스크립트와 연동시킬 도구를 만들어야 한다. 이 일은 러스트를 위한 일이지만, 웹 어셈블리를 타깃으로 하는 다른 언어들을 위한 것이기도 하다.

img

우리는  웹 어셈블리의 어떤 사용성을 손봐야 할까? 여기 몇 가지를 적었다.

  1. 어떻게 해야 웹 어셈블리와 자바스크립트 사이에 쉽게 오브젝트를 주고받을 수 있을까?
  2. 어떻게 이 패키지를 npm에 등록할 수 있을까?
  3. 어떻게 개발자들이 번들러, Node 혹은 브라우저를 가리지 않고 쉽게 자바스크립트와 WASM 패키지를 합칠 수 있을까?

그보다 먼저, 무엇을 우리는 러스트에서 실현시키고 있는가?

러스트는 자바스크립트 함수를 호출할 수 있게 된다. 자바스크립트도 러스트 함수를 호출할 수 있게 된다. 러스트는 alert 같은 호스트 플랫폼의 함수를 호출할 수 있게 된다. 러스트 크레이트(crate)들은 npm 패키지들을 의존성(dependency)으로 가질 수 있게 된다.  그리고 이 모든 것들을 통해, 러스트와 자바스크립트는 양쪽의 입장에서 적절한 방법으로 오브젝트를 주고받을 수 있어진다.

img

바로 그것이 우리가 러스트에서 실현 중인 것이다. 이제 우리가 도전 중인 웹 어셈블리의 사용성을 보자.

Q. 어떻게 해야 웹 어셈블리와 자바스크립트 사이에 쉽게 오브젝트를 주고받을 수 있을까?

A. wasm-bindgen

웹 어셈블리를 사용할 때 가장 어려운 부분 중 하나는 함수에 다양한 값을 넣고 받아오는 일이다. 왜냐하면 웹 어셈블리는 현재 오로지 정수와 실수 두 가지 타입만을 가지고 있기 때문이다.

이것은 당신이 그저 웹 어셈블리 함수에 문자열을 던져 실행시킬 수 없다는 뜻이다. 몇 가지 과정을 밟아야만 한다.

  1. 자바스크립트 쪽에서, 문자열을 숫자들로 인코드 (TextEncoder 같은 것을 사용해서)

img

  1. 그 숫자들을 웹 어셈블리의 메모리에 (기본적으로 숫자 배열 하나로) 로드.

img

  1. 문자열의 첫 글자에 해당하는 배열의 인덱스를 웹 어셈블리 함수에 전달.

img

  1. 웹 어셈블리 쪽에서는 숫자들을 가져오기 위해 그 정수를 포인터로 사용.

img

이것으로 여기까지가 문자열을 처리하는데 필요한 일들이다. 만약 여러분이 더 복잡한 타입을 주고받기 위해서는 더 복잡한 과정이 필요하다.

여러분이 웹 어셈블리 코드를 많이 사용하다 보면, 아마도 이러한 연동에 필요한 코드들을 라이브러리로 추상화하게 될 것이다. 하지만 당신이 애초에 이러한 연동에 필요한 코드를 작성하지 않아도 된다면, 그리고 만일 당신이 복잡한 값들을 그저 언어의 경계를 가로질러 던지는 것만으로 알아서 동작할 수 있다면 좋지 않을까?

그것이 wasm-bindgen이 하는 일이다. 러스트 코드에 몇몇 annotation을 추가하면, wasm-bindgen은 자동으로 양쪽에서 복잡한 타입을 처리하는 코드를 만들어 줄 것이다.

img

이는 자바스크립트 함수들이 원하는 데이터 타입이 무엇이든 러스트에서 호출한다는 뜻이다:

#[wasm_bindgen]
extern {
    type console;

    #[wasm_bindgen(static = console)]
    fn log(s: &str);
}
#[wasm_bindgen]
pub fn foo() {
    console::log("hello!");
}

... 혹은 자바스크립트에서 러스트의 구조체(struct)들을 자바스크립트의 클래스(class)들처럼 사용한다거나:

// Rust
#[wasm_bindgen]
pub struct Foo {
    contents: u32,
}

#[wasm_bindgen]
impl Foo {
    pub fn new() -> Foo {
        Foo { contents: 0 }
    }
    pub fn add(&mut self, amt: u32) -> u32 {
        self.contents += amt;
        return self.contents
    }
}
// JS
import { Foo } from "./js_hello_world";
let foo = Foo.new();
assertEq(foo.add(10), 10);
foo.free();

... 그리고 기타 등등.

내부적으로 wasm-bindgen은 언어 독립적으로 디자인 되었다. 이는 즉, 이 도구가 안정화되면 C/C++ 같은 다른 언어 구문을 지원하도록 확장할 수 있다는 뜻이다.

Alex Crichton이 몇 주 이내에 wasm-bindgen에 대한 글을 쓸 예정이니 찾아보기 바란다.

Q. 어떻게 이 패키지를 npm에 등록할 수 있을까?

A. wasm-pack

일단 할 일들을 마치고 나면, 우리는 몇몇 개의 파일을 손에 쥐고 있을 것이다. 컴파일된 웹 어셈블리 파일이 있고, 의존성과 wasm-bindgen이 생성한 자바스크립트 파일들이 있다. 우리는 이것들 모두를 패키지 할 수 있는 방법이 필요하다. 게다가, npm 의존성이 있다면 그것들을 package.json manifest 파일에도 추가해야 한다.

image

이 역시 자동으로 해결되면 좋을 것이다. 그리고 그것이 바로 wasm-pack의 역할이다. 이것은 컴파일 된 웹 어셈블리 파일을 npm 패키지로 만들어주는 만능 도구 같은 것이다.

wasm-pack은 여러분 대신 wasm-bindgen을 실행한다. 그리고 그 모든 파일들을 패키지로 만들어 준다. 먼저 package.json을 생성하고, 러스트 코드를 참조해 npm 의존성을 추가한다. 그 이후 여러분은 npm publish만 하면 된다.

다시 한번 말하자면, 이 도구는 언어 독립적인 기반을 가지고 있다. 그렇기에 우리는 이것이 여러 언어 생태계를 지원할 것으로 기대한다.

Ashley Williams가 다음 달에 wasm-pack의 더 많은 것에 대해 쓸 것이다. 그러니 그것도 참조하시라.

Q. 어떻게 개발자들이 번들러, Node 혹은 브라우저를 가리지 않고 쉽게 자바스크립트와 WASM 패키지를 합칠 수 있을까?

A. ES modules

자 이제 우리는 우리 WebAssembly를 npm에 등록했다. 이제 이 웹 어셈블리를 어떻게 자바스크립트 애플리케이션에서 쉽게 사용될 수 있도록 만들까?

웹 어셈블리 패키지를 의존성으로써 쉽게. 그러니... 자바스크립트의 의존성 그래프에 포함되도록 말이다.

image

현재, 웹 어셈블리는 모듈을 생성하기 위한 명령형(imperative) 자바스크립트 API 가 있다. 당신은 모든 과정을 위한 코드를 작성해야 한다. 파일을 읽어 오는 것에서부터 의존성을 만들어 내는 것까지. 이것은 어려운 일이다.

하지만 이제는 브라우저가 네이티브 모듈을 지원하기에, 선언적(declarative) API를 사용할 수 있다. 특히 우린 ES 모듈 API를 사용할 수 있다. 이것을 사용하면 ES 모듈을 사용하듯 웹 어셈블리 모듈 사용이 쉽게 된다.

image

우린 이것을 표준화 하기 위해 TC39 그리고 웹 어셈블리 커뮤니티와 같이 일하고 있다.

그러나 우리가 ES 모듈 지원을 표준으로 할 필요는 없다. Node와 브라우저가 ES 모듈을 지원한다 해도, 개발자들은 여전히 번들러를 사용하고 싶을 것이다. 번들러는 모듈 파일들을 위한 네트워크 횟수를 줄여주기 때문이다. 이는 당신의 코드를 다운로드하는데 걸리는 시간이 적게 걸린다는 뜻이다.

번들러들은 여러 개의 파일에 흩어져 있는 모듈들을 하나의 파일로 묶고, 이것들을 로드하기 위해 상단에 약간의 런타임을 추가한다.

image

최소한 얼마간은, 번들러가 모듈들을 만들기 위해서는 여전히 그 자바스크립트 API를 사용해야 한다. 그러나 사용자들은 ES 모듈 문법으로 작성할 것이고, 그들은 그것들이 ES 모듈처럼 동작하리라 기대할 것이다. 우리는 번들러가 ES 모듈을 에뮬레이트 하기 쉽도록 웹 어셈블리에 기능을 추가해야 한다.

나는 ES 모듈을 웹 어셈블리 스펙에 연동하려는 노력에 대해 더 써보려 한다. 또한 앞으로 몇 달간 번들러와 번들러의 웹 어셈블리 지원을 위해 몰두할 예정이다.

결론

러스트가 웹 언어로써 유용해지기 위해서는 자바스크립트 생태계와 잘 어울려야 한다. 그러기 위해서 우리는 몇 가지 할 일이 있고, 다행히도 그 일은 다른 언어에게도 도움이 될 것이다. 모든 언어에서 웹 어셈블리를 더 나아지게 만들고 싶은가? 우리 일을 도우시라. 우리도 당신이 이 일을 시작할 수 있도록 기꺼이 도울 테니. :)

Lin Clark

Lin은 Mozilla Developer Relations 팀의 엔지니어이다. 자바스크립트, 웹 어셈블리, 러스트 그리고 Servo를 다루며, 코드 카툰도 그린다.


크리에이티브 커먼즈 라이선스
이 저작물은 크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.
최규우2018.06.04
Back to list