러스트(Rust) 실전 프로젝트 예제 따라하기 시리즈 - 5편: WebAssembly(WASM)로 브라우저 환경에서 러스트 코드 실행하기

이전 글들에서는 러스트를 활용해 CLI 유틸리티, 웹 서버, 데이터베이스 연동 REST API, JWT 인증 등 백엔드 개발 영역에서의 활용 예제를 다루었습니다. 이번에는 시야를 전환하여 WebAssembly(WASM)를 통해 브라우저 환경에서 러스트 코드를 실행해보며, 프런트엔드 세계와의 접점을 탐구해보겠습니다.

WASM을 사용하면 C++처럼 네이티브 언어로 작성한 코드를 브라우저에서 실행할 수 있으며, 러스트는 WASM 지원을 공식적으로 잘 갖추고 있습니다. 이를 통해 다음과 같은 장점을 누릴 수 있습니다.

  • 성능 향상: CPU 집약적인 연산을 자바스크립트 대신 러스트 WASM 모듈로 처리 가능
  • 안전성 보장: 러스트 언어 특성에 기반한 메모리 안전성, 에러 처리 장점을 웹 환경에도 적용
  • C++ 대비 단순성: C++로 WASM 빌드 시 Emscripten 설정 등 복잡도가 있지만, 러스트는 wasm-pack과 wasm-bindgen으로 빌드 및 번들링을 단순화.

프로젝트 개요

간단한 이미지 처리 기능을 WASM으로 구현해봅시다. 예를 들어, 이미지를 흑백으로 변환하는 로직을 러스트로 작성하고, 웹 브라우저 상에서 그 함수를 호출하여 이미지를 처리하는 데모를 만들어볼 수 있습니다.

  • 주요 기능:
    • 러스트 코드로 이미지 버퍼(픽셀 데이터)를 입력받아 흑백 변환 수행
    • WASM으로 빌드 후 브라우저에서 해당 함수를 호출해 이미지를 변환
  • 도구:
    • wasm-pack으로 러스트 코드를 컴파일하여 WASM 모듈과 JS 바인딩 코드 생성
    • wasm-bindgen으로 자바스크립트와의 상호작용 지원
  • 프런트엔드 설정:
    • 간단한 HTML + 자바스크립트 페이지에서 WASM 모듈 로딩
    • 변환 결과를 <canvas>에 그려 시각화

준비: wasm-pack 설치 및 프로젝트 초기화

먼저 wasm-pack을 설치해야 합니다. (Rust 설치가 되어있다고 가정)

cargo install wasm-pack

프로젝트를 새로 생성합니다.

cargo new wasm_image_proc --lib
cd wasm_image_proc

wasm-pack은 라이브러리 형태(--lib)의 프로젝트를 WASM으로 빌드하는데 최적화되어 있습니다.

Cargo.toml 설정

wasm-bindgen 크레이트를 추가합니다.

[package]
name = "wasm_image_proc"
version = "0.1.0"
edition = "2021"

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

[dependencies]
wasm-bindgen = "0.2"

crate-type = ["cdylib"] 설정은 WASM 생성에 필요한 설정입니다.

러스트 코드 작성

src/lib.rs에서 wasm-bindgen 속성을 이용해 자바스크립트와 상호작용할 수 있는 함수를 노출합니다.

use wasm_bindgen::prelude::*;

// 픽셀 데이터는 u8 배열 (R, G, B, A) 4바이트 단위로 구성되어 있다고 가정
// length = width * height * 4

#[wasm_bindgen]
pub fn to_grayscale(pixels: &mut [u8]) {
    for chunk in pixels.chunks_exact_mut(4) {
        let r = chunk[0];
        let g = chunk[1];
        let b = chunk[2];
        let gray = ((r as u16 + g as u16 + b as u16) / 3) as u8;

        // R, G, B 값을 gray로 변경
        chunk[0] = gray;
        chunk[1] = gray;
        chunk[2] = gray;
        // A (알파) 값은 그대로 둔다.
    }
}

#[wasm_bindgen] 어트리뷰트가 붙은 함수는 JS에서 호출할 수 있습니다. to_grayscale 함수는 픽셀 버퍼를 받아 모든 픽셀을 평균값으로 변환해 흑백 처리합니다.

빌드 및 번들링

이제 wasm-pack을 이용해 빌드합니다.

wasm-pack build --target web

이 명령을 실행하면 pkg 디렉토리에 wasm_image_proc.js와 wasm_image_proc_bg.wasm 파일이 생성됩니다. --target web 옵션은 웹 환경에 적합한 번들(ES 모듈 형태)을 생성합니다.

이제 이 결과물을 웹 페이지에서 사용하기만 하면 됩니다.

HTML/JS 페이지 작성

프로젝트 루트에 static 디렉토리를 만들고 간단한 HTML 페이지를 준비합니다.

mkdir static
touch static/index.html

static/index.html 내용 예시:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Rust WASM Image Processing</title>
</head>
<body>
<h1>Rust WASM Image Processing Demo</h1>
<canvas id="canvas" width="300" height="300"></canvas><br>
<button id="convert">Convert to Grayscale</button>

<script type="module">
    import init, { to_grayscale } from '../pkg/wasm_image_proc.js';

    async function main() {
        await init(); // WASM 초기화

        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');

        // 임의로 캔버스를 컬러로 채워보자 (예: 빨강)
        ctx.fillStyle = 'red';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        document.getElementById('convert').addEventListener('click', () => {
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            // imageData.data는 Uint8ClampedArray
            const pixels = new Uint8Array(imageData.data);
            
            // WASM 함수 호출
            to_grayscale(pixels);

            // 변경된 픽셀 데이터를 canvas에 다시 적용
            for (let i = 0; i < pixels.length; i++) {
                imageData.data[i] = pixels[i];
            }
            ctx.putImageData(imageData, 0, 0);
        });
    }
    main();
</script>
</body>
</html>

여기서 import init, { to_grayscale } from '../pkg/wasm_image_proc.js';를 통해 WASM 바인딩을 불러옵니다. init() 호출로 WASM 모듈 초기화 후 to_grayscale 함수를 호출할 수 있습니다.

로컬 서버로 페이지 열기

로컬 HTTP 서버를 띄워 static/index.html을 확인해봅시다. Python의 SimpleHTTPServer나 Node.js serve 패키지 등 간단한 서버를 사용할 수 있습니다.

예: Python3

cd static
python3 -m http.server 8080

브라우저에서 http://localhost:8080 접속.

"Convert to Grayscale" 버튼을 클릭하면 빨간색 사각형이 회색으로 변하는 것을 확인할 수 있습니다.

확장 아이디어

  • C++ 대비: C++로 WASM 빌드 시 Emscripten 설정, EMSCRIPTEN_KEEPALIVE 어트리뷰트, 비동기 FFI 호출 등에 더 많은 설정이 필요. 러스트는 wasm-bindgen과 wasm-pack 덕분에 비교적 쉽고 직관적.
  • 고급 처리: 실제 이미지 파일 로딩, Web Worker로 비동기 처리, WebAssembly Threads 지원 등을 추가해 더 복잡한 이미지 처리나 영상 필터링 구현 가능.
  • Rust + JS 상호작용 강화: js-sys, web-sys 크레이트로 웹 API 직접 호출, DOM 조작, fetch API 호출 등 더 풍부한 상호작용 가능.

다음 단계

이 예제는 WASM과 러스트의 기본 연동 패턴을 보여주었습니다. 다음 글에서는 C++ 코드와의 FFI 연동, 마이크로서비스 아키텍처나 CI/CD 파이프라인 구축, 성능 최적화 등 더 심화된 주제를 탐구하며 러스트 생태계 활용 범위를 넓혀갈 예정입니다.

결론

이번 글을 통해 러스트 코드를 WebAssembly로 컴파일하여 브라우저 환경에서 실행하고, 자바스크립트 코드와 상호작용하는 과정을 살펴보았습니다. 러스트와 WASM의 결합은 프런트엔드 성능 개선, 타입 안정성, 메모리 안전성 확보에 유용하며, 기존 C++ 기반 WebAssembly 작업 대비 훨씬 단순한 워크플로우를 제공합니다.

이제 여러분은 웹 프런트엔드 영역에서도 러스트를 활용하는 방법을 이해했으니, 실제 프로젝트에서 고성능 로직을 WASM으로 옮겨 웹 애플리케이션 성능을 개선해보는 실험을 해볼 수 있습니다.

유용한 링크와 리소스

반응형