paint-brush
30초 안에 WebAssembly용 TypeScript로 Rust 유형을 공유하는 방법: 빠른 가이드~에 의해@dawchihliou
930 판독값
930 판독값

30초 안에 WebAssembly용 TypeScript로 Rust 유형을 공유하는 방법: 빠른 가이드

~에 의해 Daw-Chih Liou6m2023/05/29
Read on Terminal Reader
Read this story w/o Javascript

너무 오래; 읽다

💡 공식 Rust 및 WebAssembly 툴체인이 TypeScript에 충분하지 않은 이유를 알아 보겠습니다. 🤹 Rust 코드를 최소한으로 변경하면서 TypeScript 정의를 자동 생성하는 방법을 보여 드리겠습니다. 🧪 우리는 npm에서 실제 WebAssembly 라이브러리를 함께 리팩토링하겠습니다.
featured image - 30초 안에 WebAssembly용 TypeScript로 Rust 유형을 공유하는 방법: 빠른 가이드
Daw-Chih Liou HackerNoon profile picture

Rust와 WebAssembly를 통해 가장 원활한 개발자 경험을 찾아보세요. 이는 Rust 코드에서 TypeScript 정의를 자동 생성하는 가장 빠른 방법입니다.

이 기사에서는

  • 💡 공식 Rust 및 WebAssembly 툴체인이 TypeScript에 충분하지 않은 이유를 알아 보겠습니다.


  • 🤹 Rust 코드를 최소한으로 변경하면서 TypeScript 정의를 자동 생성하는 방법을 보여 드리겠습니다.


  • 🧪 우리는 npm에서 실제 WebAssembly 라이브러리를 함께 리팩토링하겠습니다.


갑시다.


wasm-bindgen의 타이핑 문제

Rust에서 WebAssembly(Wasm) 모듈용 TypeScript 유형을 생성하는 것은 간단하지 않습니다.


Voy 라는 Wasm의 벡터 유사성 검색 엔진을 작업할 때 문제가 발생했습니다. 저는 JavaScript 및 TypeScript 엔지니어에게 의미론적 검색을 위한 스위스 칼을 제공하기 위해 Rust로 Wasm 엔진을 구축했습니다. 다음은 웹용 데모입니다.


보이 데모


GitHub에서 Voy의 저장소를 찾을 수 있습니다! 자유롭게 사용해 보세요.


저장소에는 다양한 프레임워크에서 Voy를 사용하는 방법을 확인할 수 있는 예제가 포함되어 있습니다.


나는 Wasm-packwasm-bindgen을 사용하여 Rust 코드를 Wasm으로 빌드하고 컴파일했습니다. 생성된 TypeScript 정의는 다음과 같습니다.


 /* tslint:disable */ /* eslint-disable */ /** * @param {any} input * @returns {string} */ export function index(resource: any): string /** * @param {string} index * @param {any} query * @param {number} k * @returns {any} */ export function search(index: string, query: any, k: number): any


보시다시피 개발자 경험에 별로 도움이 되지 않는 "모든" 유형이 많이 있습니다. 무슨 일이 일어났는지 알아보기 위해 Rust 코드를 살펴보겠습니다.


 type NumberOfResult = usize; type Embeddings = Vec<f32>; type SerializedIndex = String; #[derive(Serialize, Deserialize, Debug)] pub struct EmbeddedResource { id: String, title: String, url: String, embeddings: Embeddings, } #[derive(Serialize, Deserialize, Debug)] pub struct Resource { pub embeddings: Vec<EmbeddedResource>, } #[wasm_bindgen] pub fn index(resource: JsValue) -> SerializedIndex { /* snip */ } #[wasm_bindgen] pub fn search(index: &str, query: JsValue, k: NumberOfResult) -> JsValue { // snip }


문자열, 슬라이스 및 부호 없는 정수는 TypeScript에서 올바른 유형을 생성했지만 " wasm_bindgen::JsValue "는 그렇지 않았습니다. JsValue는 wasm-bindgen의 JavaScript 객체 표현입니다.


우리는 JsValue를 직렬화 및 역직렬화하여 Wasm을 통해 JavaScript와 Rust 사이에서 앞뒤로 전달합니다.


 #[wasm_bindgen] pub fn index(resource: JsValue) -> String { // 💡 Deserialize JsValue in to Resource struct in Rust let resource: Resource = serde_wasm_bindgen:from_value(input).unwrap(); // snip } #[wasm_bindgen] pub fn search(index: &str, query: JsValue, k: usize) -> JsValue { // snip // 💡 Serialize search result into JsValue and pass it to WebAssembly let result = engine::search(&index, &query, k).unwrap(); serde_wasm_bindgen:to_value(&result).unwrap() }


이는 데이터 유형을 변환하는 공식적인 접근 방식이지만 TypeScript를 지원하려면 더 많은 노력이 필요합니다.

Tsify를 사용하여 TypeScript 바인딩 자동 생성

한 언어에서 다른 언어로 데이터 유형을 변환하는 것은 실제로 FFI( 외래 함수 인터페이스 )라는 일반적인 패턴입니다. Rust 구조체에서 TypeScript 정의를 자동 생성하기 위해 Typeshare 와 같은 FFI 도구를 탐색했지만 이는 솔루션의 절반에 불과했습니다.


우리에게 필요한 것은 Wasm 컴파일을 활용하고 Wasm 모듈의 API에 대한 유형 정의를 생성하는 방법입니다. 이와 같이:


 #[wasm_bindgen] pub fn index(resource: Resource) -> SerializedIndex { /* snip */ } #[wasm_bindgen] pub fn search(index: SerializedIndex, query: Embeddings, k: NumberOfResult) -> SearchResult { // snip }


운 좋게도 Tsify는 사용 사례에 대한 놀라운 오픈 소스 라이브러리입니다. 우리가 해야 할 일은 "Tsify" 특성에서 파생되고 #[tsify] 매크로를 구조체에 추가하는 것입니다.


 type NumberOfResult = usize; type Embeddings = Vec<f32>; type SerializedIndex = String; #[derive(Serialize, Deserialize, Debug, Clone, Tsify)] #[tsify(from_wasm_abi)] pub struct EmbeddedResource { pub id: String, pub title: String, pub url: String, pub embeddings: Embeddings, } #[derive(Serialize, Deserialize, Debug, Tsify)] #[tsify(from_wasm_abi)] pub struct Resource { pub embeddings: Vec<EmbeddedResource>, } #[derive(Serialize, Deserialize, Debug, Clone, Tsify)] #[tsify(into_wasm_abi)] pub struct Neighbor { pub id: String, pub title: String, pub url: String, } #[derive(Serialize, Deserialize, Debug, Clone, Tsify)] #[tsify(into_wasm_abi)] pub struct SearchResult { neighbors: Vec<Neighbor>, } #[wasm_bindgen] pub fn index(resource: Resource) -> SerializedIndex { /* snip */ } #[wasm_bindgen] pub fn search(index: SerializedIndex, query: Embeddings, k: NumberOfResult) -> SearchResult { // snip }


그게 다야! "from_wasm_abi" 및 "into_wasm_abi" 속성을 살펴보겠습니다.


와즘 ABI


두 속성 모두 Rust 데이터 유형을 TypeScript 정의로 변환합니다. 그들이 다르게 하는 것은 Wasm의 ABI(Application Binary Interface)를 사용한 데이터 흐름의 방향입니다.


  • into_wasm_abi : 데이터는 Rust에서 JavaScript로 흐릅니다. 반환 유형에 사용됩니다.


  • from_wasm_abi : 데이터는 JavaScript에서 Rust로 흐릅니다. 매개변수에 사용됩니다.


두 속성 모두 serde-wasm-bindgen을 사용하여 Rust와 JavaScript 간의 데이터 변환을 구현합니다.


Wasm 모듈을 빌드할 준비가 되었습니다. "wasm-pack build"를 실행하면 자동 생성된 TypeScript 정의는 다음과 같습니다.


 /* tslint:disable */ /* eslint-disable */ /** * @param {Resource} resource * @returns {string} */ export function index(resource: Resource): string /** * @param {string} index * @param {Float32Array} query * @param {number} k * @returns {SearchResult} */ export function search( index: string, query: Float32Array, k: number ): SearchResult export interface EmbeddedResource { id: string title: string url: string embeddings: number[] } export interface Resource { embeddings: EmbeddedResource[] } export interface Neighbor { id: string title: string url: string } export interface SearchResult { neighbors: Neighbor[] }


모든 "any" 유형은 Rust 코드에서 정의한 인터페이스로 대체됩니다✨

마지막 생각들

생성된 유형은 보기에는 좋지만 일부 불일치가 있습니다. 자세히 살펴보면 검색 기능의 쿼리 매개변수가 Float32Array로 정의되어 있음을 알 수 있습니다.


쿼리 매개변수는 EmbeddedResource의 "embeddings"과 동일한 유형으로 정의되므로 TypeScript에서도 동일한 유형을 가질 것으로 예상됩니다.


왜 다른 유형으로 변환되는지 알고 있다면 주저하지 말고 GitHub의 Voy 에서 끌어오기 요청을 제출하세요.


Voy 는 WebAssembly의 오픈 소스 의미 검색 엔진입니다. 저는 더 많은 프로젝트가 의미론적 기능을 구축하고 전 세계 사람들을 위한 더 나은 사용자 경험을 만들 수 있도록 힘을 실어주기 위해 이를 만들었습니다. Voy는 다음과 같은 몇 가지 디자인 원칙을 따릅니다.


  • 🤏 매우 작음 : 네트워크가 느린 모바일 브라우저나 IoT 등 제한된 장치에 대한 오버헤드를 줄입니다.


  • 🚀 신속함 : 사용자에게 최고의 검색 경험을 제공합니다.


  • 🌳 Tree Shakable : 번들 크기를 최적화하고 Web Workers와 같은 최신 웹 API에 대한 비동기 기능을 활성화합니다.


  • 🔋 재개 가능 : 언제 어디서나 휴대용 임베딩 인덱스를 생성합니다.


  • ☁️ 전 세계 : CDN 엣지 서버에서 의미 검색을 실행합니다.


npm에서 사용 가능합니다. 즐겨 사용하는 패키지 관리자를 사용하여 간단히 설치하면 바로 사용할 수 있습니다.


 # with npm npm i voy-search # with Yarn yarn add voy-search # with pnpm pnpm add voy-search


한번 시도해 보세요. 여러분의 의견을 듣게 되어 기쁩니다!

참고자료


연결하고 싶으신가요?

이 기사는 원래 Daw-Chih 웹사이트 에 게시되었습니다.