본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 10장: WebAssembly 타겟
2026년 3월 1일·프로그래밍·

10장: WebAssembly 타겟

wasm-pack, wasm-bindgen, wasm32-wasi 타겟, Spin 서버리스, 브라우저 통합, 서버와 Wasm 간 비즈니스 로직 공유, 크기 최적화까지 다룹니다.

13분966자8개 섹션
rust
공유
rust-backend10 / 11
1234567891011
이전9장: CLI 도구 개발다음11장: 실전 프로젝트 -- Rust 백엔드 API 구축

학습 목표

  • WebAssembly의 개념과 Rust와의 관계를 이해합니다
  • wasm-pack과 wasm-bindgen으로 브라우저용 Wasm을 빌드합니다
  • WASI 타겟으로 서버 사이드 Wasm을 실행합니다
  • 서버와 Wasm 간 비즈니스 로직 공유 전략을 파악합니다
  • Wasm 바이너리 크기를 최적화합니다

WebAssembly란

**WebAssembly(Wasm)**는 브라우저와 서버에서 실행되는 바이너리 명령어 형식입니다. JavaScript의 대체가 아니라 보완으로, CPU 집약적 작업을 네이티브에 가까운 속도로 실행합니다.

Rust는 WebAssembly의 최고 지원 언어 중 하나입니다. 가비지 컬렉터가 없어 Wasm 바이너리에 런타임을 포함할 필요가 없고, 결과적으로 더 작고 빠른 Wasm 모듈을 생성합니다.

Wasm 활용 영역

영역예시장점
브라우저이미지 처리, 암호화, 게임JS 대비 10-100배 빠른 연산
서버리스Spin, Cloudflare Workers1ms 미만 콜드 스타트
플러그인 시스템Envoy 필터, Figma 플러그인안전한 샌드박스 실행
공유 로직검증, 계산서버+클라이언트 코드 재사용

브라우저용 Wasm: wasm-pack

프로젝트 설정

wasm-pack 프로젝트 생성
bash
# wasm-pack 설치
cargo install wasm-pack
 
# 프로젝트 생성
cargo new --lib wasm-utils
cd wasm-utils
Cargo.toml
toml
[package]
name = "wasm-utils"
version = "0.1.0"
edition = "2024"
 
[lib]
crate-type = ["cdylib", "rlib"]
 
[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
serde-wasm-bindgen = "0.6"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["console"] }

wasm-bindgen으로 JS 바인딩

wasm-bindgen은 Rust와 JavaScript 간의 인터페이스를 자동 생성합니다.

src/lib.rs — 브라우저용 Wasm 모듈
rust
use wasm_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
 
// JavaScript에서 호출 가능한 함수
#[wasm_bindgen]
pub fn validate_email(email: &str) -> bool {
    // 간단한 이메일 검증 로직
    let parts: Vec<&str> = email.split('@').collect();
    if parts.len() != 2 {
        return false;
    }
    let domain_parts: Vec<&str> = parts[1].split('.').collect();
    !parts[0].is_empty() && domain_parts.len() >= 2 && domain_parts.iter().all(|p| !p.is_empty())
}
 
#[wasm_bindgen]
pub fn calculate_price(base_price: f64, quantity: u32, discount_percent: f64) -> f64 {
    let subtotal = base_price * quantity as f64;
    let discount = subtotal * (discount_percent / 100.0);
    (subtotal - discount).max(0.0)
}
 
// 구조체를 JS로 노출
#[wasm_bindgen]
#[derive(Serialize, Deserialize)]
pub struct ValidationResult {
    valid: bool,
    errors: Vec<String>,
}
 
#[wasm_bindgen]
impl ValidationResult {
    #[wasm_bindgen(getter)]
    pub fn valid(&self) -> bool {
        self.valid
    }
 
    #[wasm_bindgen(getter)]
    pub fn errors(&self) -> Vec<String> {
        self.errors.clone()
    }
}
 
#[wasm_bindgen]
pub fn validate_user_input(name: &str, email: &str, age: u32) -> ValidationResult {
    let mut errors = Vec::new();
 
    if name.is_empty() || name.len() > 100 {
        errors.push("이름은 1-100자여야 합니다".to_string());
    }
 
    if !validate_email(email) {
        errors.push("유효한 이메일 주소를 입력하세요".to_string());
    }
 
    if age < 1 || age > 150 {
        errors.push("나이는 1-150 사이여야 합니다".to_string());
    }
 
    ValidationResult {
        valid: errors.is_empty(),
        errors,
    }
}

빌드 및 사용

Wasm 빌드
bash
# npm 패키지로 빌드
wasm-pack build --target web --out-dir pkg
 
# 생성 파일:
# pkg/wasm_utils_bg.wasm  — Wasm 바이너리
# pkg/wasm_utils.js        — JS 글루 코드
# pkg/wasm_utils.d.ts      — TypeScript 타입 정의
# pkg/package.json         — npm 패키지 메타데이터
index.html — 브라우저에서 사용
html
<script type="module">
  import init, { validate_email, calculate_price, validate_user_input } from './pkg/wasm_utils.js';
 
  async function main() {
    await init();
 
    // 이메일 검증
    console.log(validate_email('user@example.com')); // true
    console.log(validate_email('invalid'));            // false
 
    // 가격 계산
    const price = calculate_price(10000, 3, 15);
    console.log(`최종 가격: ${price}원`); // 25500원
 
    // 사용자 입력 검증
    const result = validate_user_input('Alice', 'alice@example.com', 25);
    console.log(`유효: ${result.valid}`);
    console.log(`오류: ${result.errors}`);
  }
 
  main();
</script>
Info

wasm-pack의 --target web 옵션은 ES 모듈로 빌드합니다. 번들러(Webpack, Vite 등)를 사용하는 경우 --target bundler를, Node.js에서 사용하는 경우 --target nodejs를 지정합니다.

WASI: 서버 사이드 WebAssembly

**WASI(WebAssembly System Interface)**는 Wasm 모듈이 파일 시스템, 네트워크 등 시스템 리소스에 접근할 수 있게 하는 표준 인터페이스입니다.

WASI 타겟 설치
bash
rustup target add wasm32-wasip1
WASI CLI 프로그램
rust
fn main() {
    println!("WASI에서 실행 중!");
 
    // 환경 변수 접근
    if let Ok(value) = std::env::var("MY_CONFIG") {
        println!("설정: {}", value);
    }
 
    // 파일 시스템 접근 (샌드박스 내에서)
    let data = std::fs::read_to_string("/data/input.txt")
        .unwrap_or_else(|_| "파일 없음".to_string());
    println!("데이터: {}", data);
}
WASI 빌드 및 실행
bash
# 빌드
cargo build --target wasm32-wasip1 --release
 
# Wasmtime으로 실행
wasmtime target/wasm32-wasip1/release/myapp.wasm
 
# 파일 시스템 디렉토리 매핑
wasmtime --dir /data::./local_data target/wasm32-wasip1/release/myapp.wasm

Spin 서버리스 프레임워크

Spin은 Fermyon이 개발한 WebAssembly 서버리스 프레임워크입니다. Wasm 모듈을 HTTP 핸들러로 실행하며, 콜드 스타트가 1ms 미만입니다.

Spin 프로젝트 생성
bash
# Spin CLI 설치
curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash
 
# Rust HTTP 핸들러 프로젝트 생성
spin new -t http-rust my-spin-app
cd my-spin-app
src/lib.rs — Spin HTTP 핸들러
rust
use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;
use serde::Serialize;
 
#[derive(Serialize)]
struct HealthResponse {
    status: String,
    runtime: String,
}
 
#[http_component]
fn handle_request(req: Request) -> anyhow::Result<impl IntoResponse> {
    let path = req.uri().path();
 
    match path {
        "/health" => {
            let body = serde_json::to_string(&HealthResponse {
                status: "healthy".to_string(),
                runtime: "spin-wasm".to_string(),
            })?;
 
            Ok(Response::builder()
                .status(200)
                .header("content-type", "application/json")
                .body(body)
                .build())
        }
        "/api/validate" => {
            // 요청 본문에서 데이터 추출
            let body = req.body();
            let input: serde_json::Value = serde_json::from_slice(body)?;
 
            let name = input["name"].as_str().unwrap_or("");
            let valid = !name.is_empty() && name.len() <= 100;
 
            let response = serde_json::json!({
                "valid": valid,
                "field": "name",
            });
 
            Ok(Response::builder()
                .status(200)
                .header("content-type", "application/json")
                .body(response.to_string())
                .build())
        }
        _ => {
            Ok(Response::builder()
                .status(404)
                .body("Not Found")
                .build())
        }
    }
}
spin.toml
toml
spin_manifest_version = 2
 
[application]
name = "my-spin-app"
version = "0.1.0"
 
[[trigger.http]]
route = "/..."
component = "my-spin-app"
 
[component.my-spin-app]
source = "target/wasm32-wasip1/release/my_spin_app.wasm"
[component.my-spin-app.build]
command = "cargo build --target wasm32-wasip1 --release"
Spin 개발 서버
bash
# 빌드 및 실행
spin build
spin up
 
# 테스트
curl http://localhost:3000/health
curl -X POST http://localhost:3000/api/validate \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice"}'
Tip

Spin의 Wasm 핸들러는 요청당 새 인스턴스를 생성하지만, Wasm 모듈의 인스턴스화는 마이크로초 단위로 이루어집니다. 이는 컨테이너 기반 서버리스(AWS Lambda 등)의 수백 밀리초 콜드 스타트와 비교하면 압도적인 차이입니다.

비즈니스 로직 공유

Rust의 강력한 장점 중 하나는 동일한 비즈니스 로직을 서버와 클라이언트에서 재사용할 수 있다는 것입니다.

공유 로직 프로젝트 구조
text
workspace/
  Cargo.toml           # 워크스페이스 루트
  shared/              # 공유 비즈니스 로직
    Cargo.toml
    src/lib.rs
  server/              # Axum 서버
    Cargo.toml
    src/main.rs
  wasm-client/         # 브라우저 Wasm
    Cargo.toml
    src/lib.rs
workspace/Cargo.toml
toml
[workspace]
members = ["shared", "server", "wasm-client"]
resolver = "2"
shared/src/lib.rs — 공유 비즈니스 로직
rust
use serde::{Deserialize, Serialize};
 
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
    pub items: Vec<OrderItem>,
    pub coupon_code: Option<String>,
}
 
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderItem {
    pub product_id: String,
    pub quantity: u32,
    pub unit_price: f64,
}
 
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderSummary {
    pub subtotal: f64,
    pub discount: f64,
    pub tax: f64,
    pub total: f64,
}
 
// 이 함수는 서버와 브라우저 모두에서 동일하게 실행됨
pub fn calculate_order(order: &Order) -> OrderSummary {
    let subtotal: f64 = order
        .items
        .iter()
        .map(|item| item.unit_price * item.quantity as f64)
        .sum();
 
    let discount = match order.coupon_code.as_deref() {
        Some("WELCOME10") => subtotal * 0.10,
        Some("VIP20") => subtotal * 0.20,
        _ => 0.0,
    };
 
    let taxable = subtotal - discount;
    let tax = taxable * 0.10; // 10% 부가세
 
    OrderSummary {
        subtotal,
        discount,
        tax,
        total: taxable + tax,
    }
}
 
pub fn validate_order(order: &Order) -> Result<(), Vec<String>> {
    let mut errors = Vec::new();
 
    if order.items.is_empty() {
        errors.push("주문 항목이 비어 있습니다".to_string());
    }
 
    for (i, item) in order.items.iter().enumerate() {
        if item.quantity == 0 {
            errors.push(format!("항목 {}의 수량은 0보다 커야 합니다", i + 1));
        }
        if item.unit_price < 0.0 {
            errors.push(format!("항목 {}의 가격은 음수일 수 없습니다", i + 1));
        }
    }
 
    if errors.is_empty() {
        Ok(())
    } else {
        Err(errors)
    }
}

서버에서는 이 공유 모듈을 직접 의존성으로 사용하고, 브라우저용 Wasm 모듈에서는 wasm-bindgen으로 감싸서 JS에 노출합니다.

Wasm 크기 최적화

Wasm 바이너리 크기는 로딩 시간에 직접 영향을 미칩니다. 최적화 설정을 적용합니다.

Cargo.toml — 릴리스 프로파일 최적화
toml
[profile.release]
opt-level = "z"      # 크기 최적화 (속도보다 크기 우선)
lto = true           # 링크 타임 최적화
codegen-units = 1    # 단일 코드 생성 유닛 (더 나은 최적화)
strip = true         # 디버그 심볼 제거
panic = "abort"      # 패닉 시 unwind 대신 abort (바이너리 크기 감소)
추가 최적화 도구
bash
# wasm-opt로 추가 최적화 (binaryen 패키지)
wasm-opt -Oz -o output.wasm input.wasm
 
# 크기 확인
ls -lh pkg/*.wasm
 
# 크기 분석
cargo install twiggy
twiggy top pkg/wasm_utils_bg.wasm
최적화 단계예상 크기
기본 릴리스 빌드~200KB
opt-level="z" + LTO~100KB
wasm-opt -Oz 적용~70KB
불필요 기능 제거 후~30-50KB
Warning

opt-level = "z"는 크기를 최소화하지만 실행 속도가 약간 느려질 수 있습니다. 성능이 중요한 경우 opt-level = "s" (크기 최적화이지만 z보다 덜 공격적) 또는 opt-level = 3 (속도 최적화)을 사용하세요.

정리

이 장에서는 Rust와 WebAssembly의 결합을 다루었습니다.

  • wasm-pack + wasm-bindgen으로 브라우저용 Wasm 모듈을 빌드합니다
  • WASI 타겟으로 서버 사이드에서 Wasm을 실행합니다
  • Spin 프레임워크로 1ms 미만 콜드 스타트의 서버리스 함수를 구현합니다
  • 워크스페이스를 활용하여 서버와 클라이언트 간 비즈니스 로직을 공유합니다
  • 릴리스 프로파일과 wasm-opt로 바이너리 크기를 최적화합니다

다음 장에서는 시리즈의 마지막으로 실전 프로젝트를 진행합니다. 지금까지 배운 모든 내용을 종합하여 프로덕션 수준의 Rust 백엔드 API를 처음부터 끝까지 구축합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#rust

관련 글

프로그래밍

11장: 실전 프로젝트 -- Rust 백엔드 API 구축

Axum, SQLx, Tokio를 조합한 프로덕션 수준의 REST API를 처음부터 구축합니다. JWT 인증, CRUD, 미들웨어, 테스트, Docker 배포까지 총정리합니다.

2026년 3월 3일·16분
프로그래밍

9장: CLI 도구 개발

Clap v4 서브커맨드 아키텍처, config-rs 설정 관리, tracing 구조화 로깅, indicatif 진행 표시, 크로스 플랫폼 빌드와 배포까지 다룹니다.

2026년 2월 27일·12분
프로그래밍

8장: 테스트와 품질 보증

Rust의 단위 테스트, 통합 테스트, API 테스트, testcontainers를 활용한 DB 테스트, 프로퍼티 기반 테스트, 벤치마킹까지 다룹니다.

2026년 2월 25일·12분
이전 글9장: CLI 도구 개발
다음 글11장: 실전 프로젝트 -- Rust 백엔드 API 구축

댓글

목차

약 13분 남음
  • 학습 목표
  • WebAssembly란
    • Wasm 활용 영역
  • 브라우저용 Wasm: wasm-pack
    • 프로젝트 설정
    • wasm-bindgen으로 JS 바인딩
    • 빌드 및 사용
  • WASI: 서버 사이드 WebAssembly
  • Spin 서버리스 프레임워크
  • 비즈니스 로직 공유
  • Wasm 크기 최적화
  • 정리