Rust logoRust Korea
홈매뉴얼커뮤니티검색로그인
<- 시리즈 목록

2장. 개발 환경 구축 — Rust와 Axum 시작하기

약 15분 읽기

이 장에서는 Axum 개발을 시작하기 위해 필요한 모든 도구를 설치하고 설정합니다. Rust가 처음인 분들도 따라할 수 있도록 단계별로 설명합니다. 환경 구축이 끝나면 첫 Axum 프로젝트를 생성하고 실행해 봅니다.

2.1 Rust 설치

Axum은 Rust로 작성되었으므로, 먼저 Rust를 설치해야 합니다. Rust의 공식 설치 도구인 rustup을 사용하는 것이 가장 쉽고 권장되는 방법입니다. rustup은 Rust 컴파일러(cargo, rustc), 표준 라이브러리, 그리고 여러 도구를 관리해 줍니다.

2.1.1 Linux/macOS에 설치

# rustup 공식 설치 스크립트를 다운로드하여 실행합니다.
# 이 스크립트는 안정적인 방식으로 Rust를 설치합니다.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 설치 후 환경 변수를 현재 셸에 적용합니다.
source "$HOME/.cargo/env"

# 설치가 잘 되었는지 확인합니다.
rustc --version    # Rust 컴파일러 버전 출력
cargo --version    # Cargo 패키지 매니저 버전 출력

설치 스크립트는 대화형으로 진행되며, 기본 설정(1)을 선택하면 됩니다. 설치가 완료되면 cargo 명령어를 사용할 수 있습니다.

2.1.2 Windows에 설치

Windows에서는 rustup-init.exe를 다운로드하여 실행합니다. 공식 사이트(rustup.rs)에서 다운로드할 수 있습니다. 설치 과정에서 Visual Studio C++ Build tools가 필요하다는 안내가 나올 수 있는데, 이는 Rust가 Windows에서 MSVC 툴체인을 사용하기 때문입니다. 안내에 따라 Build Tools를 설치하면 됩니다.

2.1.3 Rust 버전 요구사항

Axum 0.8.x는 최소 Rust 1.80(MSRV, Minimum Supported Rust Version)을 요구합니다. rustup으로 설치하면 최신 안정 버전이 설치되므로 대부분 문제가 없지만, 오래된 환경에서는 rustup update로 최신 버전으로 업데이트해야 할 수 있습니다.

# Rust를 최신 안정 버전으로 업데이트합니다.
rustup update stable

# 설치된 툴체인 목록을 확인합니다.
rustup show

2.2 유용한 개발 도구 설치

Rust 개발 경험을 크게 향상시키는 몇 가지 도구를 추천합니다. 이 도구들은 선택 사항이지만, 설치하면 생산성이 크게 올라갑니다.

2.2.1 rust-analyzer (IDE 지원)

rust-analyzer는 Rust 언어 서버로, VS Code, Vim, Emacs 등 다양한 에디터에서 코드 자동 완성, 타입 힌트, 정의로 이동, 리팩토링 등을 제공합니다. Rust 개발에는 사실상 필수입니다.

# rustup을 통해 rust-analyzer 컴포넌트를 설치합니다.
rustup component add rust-analyzer

# VS Code를 사용한다면 "rust-analyzer" 확장을 설치하세요.

2.2.2 cargo-watch (자동 재빌드)

cargo-watch는 파일이 변경될 때마다 자동으로 cargo 명령(빌드, 테스트, 실행 등)을 다시 실행해 줍니다. 개발 중 코드를 저장할 때마다 자동으로 재컴파일되므로 큰 편의를 제공합니다.

# cargo-watch를 설치합니다.
cargo install cargo-watch

# 사용 예: 파일 변경 시 자동으로 실행
cargo watch -x run      # cargo run 자동 실행
cargo watch -x test     # cargo test 자동 실행
cargo watch -x "run -- --port 3000"  # 인자와 함께 실행

2.2.3 cargo-edit (의존성 관리)

cargo-edit은 Cargo.toml에 의존성을 쉽게 추가/제거할 수 있는 명령어(cargo add, cargo rm)를 제공합니다. 최신 Cargo(1.62+)에는 cargo add가 내장되어 있지만, cargo-edit은 추가 기능을 제공합니다.

# cargo-edit 설치
cargo install cargo-edit

# 의존성 추가 예
cargo add axum
cargo add tokio --features full
cargo add serde --features derive
cargo add tower-http --features cors

2.3 첫 Axum 프로젝트 생성

이제 모든 준비가 끝났습니다. 첫 Axum 프로젝트를 생성해 봅시다. cargo new 명령으로 새 바이너리 프로젝트를 만듭니다.

# hello_axum이라는 이름의 새 프로젝트를 생성합니다.
cargo new hello_axum --bin

# 프로젝트 디렉토리로 이동합니다.
cd hello_axum

# 생성된 파일 구조:
# hello_axum/
# ├── Cargo.toml    # 프로젝트 설정 파일
# ├── src/
# │   └── main.rs   # 메인 소스 파일
# └── .gitignore

2.3.1 Cargo.toml 설정

Cargo.toml은 Rust 프로젝트의 설정 파일입니다. 여기에 프로젝트 이름, 버전, 의존성 등을 정의합니다. Axum 애플리케이션을 만들려면 axum과 tokio(async 런타임)가 최소한으로 필요합니다.

# Cargo.toml — 프로젝트 매니페스트 파일

[package]
name = "hello_axum"          # 프로젝트 이름
version = "0.1.0"            # 시맨틱 버저닝을 따릅니다
edition = "2021"             # Rust 에디션 (2021 권장)

[dependencies]
# axum: 핵심 웹 프레임워크
axum = "0.8"

# tokio: 비동기 런타임
# "full" feature는 모든 기능(rt, macros, net, fs 등)을 활성화합니다.
tokio = { version = "1", features = ["full"] }

# serde: 직렬화/역직렬화 (JSON, 폼 데이터 파싱에 필수)
serde = { version = "1", features = ["derive"] }

2.3.2 첫 main.rs 작성

이제 가장 간단한 Axum 애플리케이션을 작성해 봅시다. ’Hello, World!’를 반환하는 서버입니다. 각 줄마다 자세한 주석을 달았으니 천천히 읽어보세요.

// src/main.rs — 첫 Axum 애플리케이션

use axum::{routing::get, Router};

// #[tokio::main] 매크로는 main 함수를 비동기로 만들고,
// tokio 런타임을 자동으로 설정합니다.
#[tokio::main]
async fn main() {
    // Router를 생성합니다.
    // .route(path, method_router)로 라우트를 추가합니다.
    // get(root)는 "GET 요청이 오면 root 함수를 실행하라"는 의미입니다.
    let app: Router = Router::new()
        .route("/", get(root));

    // TCP 리스너를 생성합니다.
    // 0.0.0.0:3000은 "모든 네트워크 인터페이스의 3000번 포트"를 의미합니다.
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .expect("포트 3000에 바인드할 수 없습니다");

    // 서버를 시작합니다.
    println!("서버가 http://localhost:3000 에서 실행 중입니다");
    axum::serve(listener, app)
        .await
        .expect("서버 실행 중 오류 발생");
}

// 핸들러 함수입니다.
// async fn이며, 반환 타입은 IntoResponse를 구현하는 어떤 타입이든 될 수 있습니다.
// &'static str은 텍스트 응답으로 자동 변환됩니다 (Content-Type: text/plain).
async fn root() -> &'static str {
    "Hello, World!"
}

2.3.3 프로젝트 실행

# 프로젝트를 빌드하고 실행합니다.
# 첫 빌드는 의존성을 컴파일하므로 시간이 오래 걸릴 수 있습니다.
cargo run

# 출력 예:
#   Compiling proc-macro2 v1.0.x
#   ... (수많은 의존성 컴파일)
#   Compiling hello_axum v0.1.0
#    Finished dev [unoptimized + debuginfo] target(s)
#     Running `target/debug/hello_axum`
# 서버가 http://localhost:3000 에서 실행 중입니다

# 다른 터미널에서 요청을 보내 봅니다.
curl http://localhost:3000/
# 출력: Hello, World!

【팁】 첫 빌드가 느린 것은 정상입니다. Cargo는 모든 의존성을 컴파일하여 캐싱합니다. 이후 빌드는 캐시를 사용하므로 훨씬 빠릅니다.

2.4 프로젝트 구조 모범 사례

프로젝트가 커지면 소스 코드를 여러 파일로 나누어 관리해야 합니다. 다음은 일반적으로 권장되는 Axum 프로젝트 구조입니다.

my_app/
├── Cargo.toml
├── src/
│   ├── main.rs           # 진입점: 서버 시작, 라우터 조립
│   ├── lib.rs            # 라이브러리 진입점 (통합 테스트용)
│   ├── routes/           # 라우트 핸들러 모음
│   │   ├── mod.rs
│   │   ├── users.rs
│   │   ├── posts.rs
│   │   └── health.rs
│   ├── models/           # 데이터 모델
│   │   ├── mod.rs
│   │   ├── user.rs
│   │   └── post.rs
│   ├── error.rs          # 커스텀 에러 타입
│   ├── state.rs          # AppState 정의
│   └── db.rs             # 데이터베이스 연결
├── migrations/           # 데이터베이스 마이그레이션
├── tests/                # 통합 테스트
│   └── integration_test.rs
└── static/               # 정적 파일 (CSS, JS, 이미지)

main.rs는 진입점 역할만 하고, 실제 로직은 lib.rs를 통해 노출된 모듈에 두는 패턴이 좋습니다. 이렇게 하면 통합 테스트에서 애플리케이션을 쉽게 임포트할 수 있습니다.

// src/lib.rs — 라이브러리 진입점
pub mod routes;
pub mod models;
pub mod error;
pub mod state;
pub mod db;

use axum::Router;
use state::AppState;

// 앱을 생성하는 함수를 별도로 두면 테스트하기 쉽습니다.
pub fn create_app(state: AppState) -> Router {
    Router::new()
        .merge(routes::users::router())
        .merge(routes::posts::router())
        .route("/health", axum::routing::get(routes::health::check))
        .with_state(state)
}

2.5 에디터 설정

VS Code를 사용하는 경우, 다음 설정을 권장합니다. .vscode/settings.json 파일에 작성하세요.

{
    "[rust]": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "rust-lang.rust-analyzer"
    },
    "rust-analyzer.linkedProjects": ["./Cargo.toml"],
    "rust-analyzer.cargo.features": "all",
    "rust-analyzer.checkOnSave.command": "clippy",
    "rust-analyzer.inlayHints.typeHints": true,
    "rust-analyzer.inlayHints.parameterHints": true,
    "rust-analyzer.inlayHints.chainingHints": true
}

2.6 자주 발생하는 문제와 해결법

2.6.1 컴파일이 너무 느린 경우

Rust는 컴파일 속도가 느린 편입니다. 특히 처음 의존성을 빌드할 때 오래 걸립니다. 다음 방법으로 개선할 수 있습니다.

  • sccache 설치: cargo install sccache, 환경 변수 RUSTC_WRAPPER=sccache 설정
  • mold 링커 사용 (Linux): 빌드 속도 크게 향상
  • cargo nextest 사용: 테스트 실행 속도 향상
  • cargo binstall 사용: 사전 빌드된 바이너리 설치

2.6.2 포트가 이미 사용 중인 경우

# macOS/Linux: 3000번 포트를 사용하는 프로세스 찾기
lsof -i :3000

# 프로세스 종료
kill -9 <PID>

2.6.3 ‘cannot find function get’ 에러

이 에러는 axum::routing 모듈을 import하지 않았을 때 발생합니다. use axum::routing::get;를 추가하세요. get 외에도 post, put, delete, patch, head, options 등이 있습니다.

2.7 요약

이 장에서는 Rust와 Axum 개발 환경을 구축하고, 첫 프로젝트를 생성하고 실행해 보았습니다. 핵심은 다음과 같습니다. Rust는 rustup으로 설치하고, rust-analyzer와 cargo-watch를 추가하면 개발 경험이 크게 향상됩니다. Axum 프로젝트는 cargo new로 시작하며, Cargo.toml에 axum, tokio, serde 의존성을 추가합니다. 프로젝트가 커지면 routes, models, error, state 등으로 모듈을 분리하는 것이 좋습니다.

다음 장에서는 본격적으로 라우팅을 다루겠습니다. HTTP 메서드별로 핸들러를 연결하고, 경로 매개변수를 추출하고, 라우트를 중첩하는 방법을 살펴보겠습니다.

로그인하고 진행률 저장<- 시리즈 목록으로 돌아가기
<- 이전 챕터
1장. Axum이란 무엇인가? - Rust 웹 생태계의 이해