Skip to content

필사 모드: Rust 완전 가이드 — Ownership·Lifetime·async·Tokio·Axum·실전 프로젝트까지 (Season 2 Ep 2, 2025)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며 — 왜 2025년에도 Rust인가

Rust는 2015년 1.0 이후 10년 동안 "가능성 있는 언어"에서 "시스템 프로그래밍의 현대적 기본값"으로 이동했다.

**2024~2025 Rust의 현재 위치:**

- **Linux Kernel**: Rust for Linux 프로젝트가 커널 드라이버 작성의 공식 선택지 (2024~)

- **Windows**: MS가 Windows 커널 일부를 Rust로 재작성 (2023~)

- **Chromium**: Rust 컴포넌트 점진 도입 (QR 코드 생성기 등)

- **Cloudflare Pingora**: Rust 기반 차세대 프록시, NGINX 대체

- **Discord**: Read States 서비스 Go→Rust 전환으로 레이턴시 70% 감소

- **AWS Firecracker**: Rust로 작성된 MicroVM, Lambda·Fargate의 심장

- **Polkadot·Solana**: 블록체인 노드 기본 언어

- **Stack Overflow Developer Survey 2024**: 9년 연속 "가장 사랑받는 언어"

Rust는 더 이상 "배우기 어려워서 미루는 언어"가 아니라, 시니어 엔지니어가 읽어야 할 코드 베이스가 매년 늘어나는 언어다.

이 글은 Rust를 처음 시작하는 사람이 Ownership을 "마침내" 이해하고, 중급자가 Lifetime을 "마침내" 설계하고, 고급자가 async를 "마침내" 뚫을 수 있도록 썼다.

1부 — Rust의 언어적 야망: 3대 목표

1.1 메모리 안전성 (Memory Safety)

C/C++의 30년 악몽 — Use-After-Free, Double-Free, Data Race — 을 **컴파일 타임에 불가능하게 만드는 것**.

MS 보안팀 분석 (2019): MS가 수정한 보안 취약점의 70%가 메모리 안전성 이슈.

Google Android 팀 분석 (2024): Rust 도입 후 신규 메모리 안전성 버그 52%→26%→7%로 급락.

1.2 Zero-cost Abstraction

추상화를 썼다고 런타임 비용이 들면 안 된다. C처럼 빠르되, Haskell처럼 표현력 있게.

// 이 이터레이터 체인은 컴파일 후

// 손으로 쓴 C 루프와 동일한 어셈블리로 컴파일됨

let sum: i32 = (1..=100)

.filter(|n| n % 2 == 0)

.map(|n| n * n)

.sum();

1.3 Fearless Concurrency

"동시성은 어렵다"를 "동시성은 타입 시스템이 검증한다"로 바꾸는 것. `Send`, `Sync` trait가 컴파일러의 감시관.

2부 — Ownership: Rust의 첫 번째 관문

2.1 세 가지 규칙

1. 모든 값은 **단 하나의 소유자(Owner)**를 갖는다

2. 소유자가 Scope를 벗어나면 값은 **Drop**된다

3. 소유권은 **이동(Move)**할 수 있다

fn main() {

let s1 = String::from("hello");

let s2 = s1; // s1의 소유권이 s2로 이동

// println!("{}", s1); // 컴파일 에러! s1은 더 이상 유효하지 않음

println!("{}", s2); // OK

}

2.2 Copy vs Move

스택에 저장되는 Primitive 타입 (i32, bool, char 등)은 **Copy trait**를 구현하여 이동이 아닌 복사가 일어난다.

let x = 5;

let y = x;

println!("{}, {}", x, y); // OK — x는 여전히 유효

Heap 할당이 포함된 String, Vec, Box는 기본적으로 Move된다.

2.3 왜 Ownership인가 — 역사적 맥락

GC 없이 안전하려면 "이 메모리를 누가 해제할 것인가"를 명확히 해야 한다.

- **C**: 프로그래머가 전적으로 책임 → 실수하면 Use-After-Free

- **C++ RAII**: 객체 수명에 묶어둠 → 이동 시멘틱 복잡

- **Rust Ownership**: 컴파일러가 규칙을 **강제** → 실수 자체를 불가능하게

2.4 가장 흔한 초보자 실수 5가지

1. **함수에 소유권을 넘기고 이후 사용**

let s = String::from("hi");

print_it(s);

println!("{}", s); // 에러

2. **for loop에서 Vec 소비**

let v = vec![1, 2, 3];

for x in v { /* v 소비됨 */ }

// v 더 이상 사용 불가 → for &x in &v 사용

3. **Clone 남발**: 해결은 되지만 성능 저하

4. **`&mut` 두 개 동시 사용 시도**: Borrow Checker에 막힘

5. **Struct 필드만 이동하려 시도**: Partial Move의 복잡성

3부 — Borrowing: 빌려주고 빌려받기

3.1 두 가지 규칙 (Borrow Checker의 심장)

1. 한 시점에 **`&mut` 참조는 단 하나**만 존재할 수 있다

2. 또는 **여러 개의 `&` (불변 참조)**를 가질 수 있다. 둘을 섞을 수는 없다.

이것이 **데이터 경쟁을 컴파일 타임에 제거**하는 핵심이다.

let mut s = String::from("hello");

let r1 = &s;

let r2 = &s;

// let r3 = &mut s; // 에러! 불변 참조가 살아있는 동안 가변 참조 불가

println!("{}, {}", r1, r2);

// 여기서 r1, r2 마지막 사용 → 이후 r3 가능

let r3 = &mut s;

r3.push_str(" world");

3.2 Non-Lexical Lifetimes (NLL, 2018 Edition)

Rust 2018부터 참조의 유효 범위가 "마지막 사용 시점"까지로 축소. 훨씬 유연해짐.

3.3 Slice — 참조의 응용

fn first_word(s: &str) -> &str {

let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {

if item == b' ' {

return &s[0..i];

}

}

&s[..]

}

`&str`은 String의 부분 참조. 매우 흔한 패턴.

4부 — Lifetime: Rust의 두 번째 관문

4.1 Lifetime이란

**참조가 유효한 범위**를 컴파일러가 추적하는 시스템. 대부분 생략 가능하지만, 모호할 때 명시해야 함.

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {

if x.len() > y.len() { x } else { y }

}

`'a`는 "x와 y 중 짧은 쪽의 수명"을 의미. 반환 참조가 이 수명을 벗어나지 않도록 보장.

4.2 Lifetime Elision Rules (생략 규칙 3가지)

1. 입력 참조마다 각자의 Lifetime이 주어짐

2. 입력 Lifetime이 하나면 모든 출력 Lifetime은 그것과 같음

3. `&self`가 있으면 출력 Lifetime은 `&self`와 같음

이 규칙 덕에 90% 이상의 함수는 Lifetime을 명시할 필요 없음.

4.3 Struct의 Lifetime

struct Important<'a> {

part: &'a str,

}

impl<'a> Important<'a> {

fn announce(&self, announcement: &str) -> &str {

println!("Attention! {}", announcement);

self.part

}

}

Struct가 참조를 필드로 가질 때 Lifetime 필수.

4.4 `'static` — 프로그램 전체 수명

let s: &'static str = "hello"; // 문자열 리터럴

문자열 리터럴은 바이너리에 박혀 있어 프로그램 전체 생명주기.

4.5 Lifetime이 어려운 이유 3가지

1. **Invariant vs Covariant**: `&'a mut T`는 T에 대해 Invariant — 초심자는 알 필요 없음

2. **HRTB (Higher-Ranked Trait Bounds)**: `for<'a> Fn(&'a T)` — 클로저에서 가끔 등장

3. **Self-referential struct**: 불가능에 가까움. 필요하면 `Pin` 또는 외부 crate

5부 — Trait & Generic: Zero-cost Abstraction

5.1 Trait — Rust의 인터페이스

trait Area {

fn area(&self) -> f64;

}

struct Circle { r: f64 }

struct Square { s: f64 }

impl Area for Circle {

fn area(&self) -> f64 { std::f64::consts::PI * self.r * self.r }

}

impl Area for Square {

fn area(&self) -> f64 { self.s * self.s }

}

5.2 Trait Bound vs impl Trait vs dyn Trait

// 1. Trait Bound — 컴파일 타임 단형화(Monomorphization)

fn print_area<T: Area>(shape: &T) {

println!("{}", shape.area());

}

// 2. impl Trait — 위와 동일, 문법 설탕

fn print_area_impl(shape: &impl Area) { /* ... */ }

// 3. dyn Trait — 런타임 동적 디스패치 (vtable 조회)

fn print_area_dyn(shape: &dyn Area) { /* ... */ }

**선택 기준:**

- 대부분은 Generic (1, 2) — 빠르지만 바이너리 크기 증가

- Vec에 여러 타입 섞어 넣어야 하면 `Box<dyn Trait>` (3)

- 라이브러리 API는 impl Trait 선호

5.3 주요 Trait 15개 (외워두면 평생 써먹는)

| Trait | 용도 |

|-------|-----|

| `Clone` | 명시적 복사 |

| `Copy` | 암시적 복사 (Primitive) |

| `Debug` | `{:?}` 출력 |

| `Display` | `{}` 출력 |

| `PartialEq`/`Eq` | 동등 비교 |

| `PartialOrd`/`Ord` | 크기 비교 |

| `Hash` | HashMap 키 |

| `Default` | 기본값 |

| `From`/`Into` | 타입 변환 |

| `TryFrom`/`TryInto` | 실패 가능한 변환 |

| `Iterator` | for loop 가능 |

| `IntoIterator` | for loop 가능 (소비) |

| `Drop` | 소멸자 |

| `Send`/`Sync` | 스레드 안전성 |

| `AsRef`/`AsMut` | 저비용 참조 변환 |

5.4 Derive Macro — 반복 제거

#[derive(Debug, Clone, PartialEq, Eq, Hash)]

struct User {

id: u64,

name: String,

}

6부 — Error Handling: Result와 `?`

6.1 Panic vs Result

- **`panic!`**: 복구 불가능한 에러. 프로그램 종료.

- **`Result<T, E>`**: 복구 가능한 에러. 호출자가 처리.

use std::fs::File;

use std::io::{self, Read};

fn read_username() -> Result<String, io::Error> {

let mut s = String::new();

File::open("hello.txt")?.read_to_string(&mut s)?;

Ok(s)

}

`?` 연산자: Err면 즉시 반환, Ok면 값 추출. 에러 전파의 황금 패턴.

6.2 anyhow vs thiserror (2025 표준)

- **Application 코드**: `anyhow::Result<T>` — 단순, 편리

- **Library 코드**: `thiserror::Error` derive — 타입 명시, API 계약

use thiserror::Error;

#[derive(Error, Debug)]

pub enum AppError {

#[error("데이터베이스 오류")]

Database(#[from] sqlx::Error),

#[error("사용자를 찾을 수 없음: id={0}")]

NotFound(u64),

#[error("인증 실패")]

Unauthorized,

}

7부 — async/await와 Tokio

7.1 async가 어려운 이유 — Future 모델

Rust의 `async fn`은 **Future를 반환**한다. Future는 **Poll될 때까지 아무것도 하지 않는다**.

async fn fetch_data() -> String {

// 호출 시점에는 실행되지 않음

// .await 하거나 executor에 spawn되어야 진행

"data".to_string()

}

let fut = fetch_data(); // Future 생성만 됨

let data = fut.await; // 실제 실행

**"async fn은 함수가 아니라 상태 기계 생성기"** — 컴파일러가 내부적으로 state machine으로 변환.

7.2 Tokio — 사실상 표준 런타임

[dependencies]

tokio = { version = "1.40", features = ["full"] }

#[tokio::main]

async fn main() {

let handle = tokio::spawn(async {

println!("task running");

});

handle.await.unwrap();

}

7.3 `async`의 3가지 함정

1. **블로킹 코드 혼합**: `std::fs::read`를 async 함수 안에서 쓰면 스레드 전체가 멈춤 → `tokio::fs::read` 또는 `spawn_blocking` 사용

2. **`Mutex` 혼용**: `std::sync::Mutex`는 await 포인트를 넘나들면 데드락 위험 → `tokio::sync::Mutex` 사용

3. **Send 오류**: `spawn`된 Future는 Send여야 함. Rc 같은 것 사용 시 컴파일 에러

7.4 Tokio Runtime 2가지

- **Multi-threaded** (기본, `#[tokio::main]`): 여러 Worker 스레드

- **Current-thread** (`#[tokio::main(flavor = "current_thread")]`): 단일 스레드, 테스트·임베디드에 적합

7.5 Channel — 비동기 메시지 전달

use tokio::sync::mpsc;

let (tx, mut rx) = mpsc::channel(32);

tokio::spawn(async move {

tx.send("hello").await.unwrap();

});

while let Some(msg) = rx.recv().await {

println!("{}", msg);

}

- `mpsc`: 다중 생산자, 단일 소비자

- `broadcast`: 다중 생산자, 다중 소비자 (Pub/Sub)

- `oneshot`: 일회성 (요청-응답)

- `watch`: 최신값만 유지 (설정 변경 등)

8부 — Web Framework: Axum vs Actix-Web

8.1 2025년 선택 기준

| 프레임워크 | 특징 | 적합 |

|-----------|-----|-----|

| **Axum** | Tokio 팀 제작, tower 미들웨어 생태계 | 신규 프로젝트 기본값 |

| **Actix-Web** | 오래된 성숙도, 성능 최상위 | 레거시·극한 퍼포먼스 |

| **Rocket** | 매크로 중심, 편리 | 교육·프로토타입 |

| **Poem** | OpenAPI 우선 | API 퍼스트 |

**추천 (2025)**: 새로 시작한다면 **Axum**. Tokio 생태계와 완벽 통합.

8.2 Axum 실전 예제

use axum::{

routing::{get, post},

Router, Json,

extract::{State, Path},

};

use serde::{Deserialize, Serialize};

use std::sync::Arc;

use tokio::sync::RwLock;

#[derive(Clone)]

struct AppState {

users: Arc<RwLock<Vec<User>>>,

}

#[derive(Clone, Serialize, Deserialize)]

struct User {

id: u64,

name: String,

}

async fn get_users(State(state): State<AppState>) -> Json<Vec<User>> {

let users = state.users.read().await;

Json(users.clone())

}

async fn create_user(

State(state): State<AppState>,

Json(user): Json<User>,

) -> Json<User> {

state.users.write().await.push(user.clone());

Json(user)

}

#[tokio::main]

async fn main() {

let state = AppState {

users: Arc::new(RwLock::new(vec![])),

};

let app = Router::new()

.route("/users", get(get_users).post(create_user))

.with_state(state);

let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();

axum::serve(listener, app).await.unwrap();

}

8.3 Tower 미들웨어 — 표준 재사용

Axum은 `tower` 미들웨어 생태계를 공유한다:

- `tower-http::trace::TraceLayer` — 로깅

- `tower-http::cors::CorsLayer` — CORS

- `tower_governor::GovernorLayer` — Rate Limit

- `axum-extra::extract::cookie` — 쿠키

9부 — 실전 프로젝트 5가지 (레벨별 로드맵)

9.1 Lv.1 — CLI 도구 (1주)

- **목표**: Ownership·Error 처리 체득

- **예시**: `wc`, `grep`, `todo.txt` 매니저

- **주요 crate**: `clap` (CLI 파싱), `anyhow` (에러)

9.2 Lv.2 — WebSocket 채팅 서버 (2주)

- **목표**: async·Channel·공유 상태

- **예시**: Axum + `tokio-tungstenite` 채팅방

- **주요 crate**: `axum`, `tokio`, `futures`

9.3 Lv.3 — 병렬 웹 크롤러 (2주)

- **목표**: 동시성 제어·Rate Limit

- **예시**: Sitemap 크롤러, 링크 그래프 생성

- **주요 crate**: `reqwest`, `scraper`, `tokio::sync::Semaphore`

9.4 Lv.4 — 미니 Redis (3주)

- **목표**: 네트워크 프로토콜·상태 관리

- **예시**: RESP 프로토콜 구현, SET/GET/EXPIRE

- **주요 crate**: `tokio`, `bytes`

9.5 Lv.5 — 분산 KV Store (4~8주)

- **목표**: Raft·Replication

- **예시**: 3노드 KV Store, Log-structured storage

- **주요 crate**: `openraft`, `sled` 또는 자작 B+tree

이 5개를 다 하면 Rust 엔지니어로 면접 통과 가능.

10부 — Rust 생태계 2024~2025 현황

10.1 필수 crate 20개

| 카테고리 | Crate |

|---------|-------|

| **비동기** | `tokio`, `futures`, `async-trait` |

| **웹** | `axum`, `reqwest`, `hyper`, `tonic` (gRPC) |

| **DB** | `sqlx` (컴파일 타임 쿼리 검증), `diesel`, `sea-orm` |

| **직렬화** | `serde`, `serde_json`, `bincode` |

| **에러** | `anyhow`, `thiserror` |

| **로깅** | `tracing`, `tracing-subscriber` |

| **테스트** | `proptest` (속성 기반), `mockall` |

| **CLI** | `clap`, `indicatif` (프로그레스 바) |

| **날짜** | `chrono`, `time` |

| **암호** | `ring`, `rustls` |

10.2 2024~2025 생태계 뉴스

- **Rust 2024 Edition** (2024년 11월): `let-else`, `async closures` 안정화, `gen` 블록 실험

- **Axum 0.8** (2024): `impl Handler` 개선

- **sqlx 0.8**: 컴파일 타임 쿼리 검증 개선

- **tokio 1.40**: `spawn_blocking` 성능 향상

- **Cargo workspace lints**: 단일 설정으로 전체 워크스페이스 lint

10.3 학습 자료 Best

1. **The Rust Programming Language** (공식, 무료) — "The Book"

2. **Rust By Example** (공식, 무료)

3. **Rustlings** (공식 연습문제)

4. **Jon Gjengset YouTube** — 현존 최고의 Rust 교육자

5. **Zero to Production in Rust** (Luca Palmieri) — 실전 서버 개발

6. **Rust for Rustaceans** (Jon Gjengset) — 중급 이상 필독

7. **Programming Rust 2nd Ed** (O'Reilly) — 참조서

8. **tokio Tutorial** — async 최고의 문서

9. **Crust of Rust** 시리즈 — Jon Gjengset 유튜브

10. **This Week in Rust** — 매주 뉴스레터

11부 — Rust를 쓰지 말아야 할 때

Rust는 만능이 아니다. 쓰지 말아야 할 신호:

1. **프로토타이핑 속도가 생명**: Python, TypeScript가 더 빠르다

2. **팀에 Rust 경험자 전무**: 학습 곡선 3~6개월 각오 필요

3. **비즈니스 로직 위주 CRUD**: Go가 더 생산적일 수 있음

4. **GPU/SIMD 극한 퍼포먼스**: C++이 여전히 더 성숙

5. **모바일 앱 UI**: Swift/Kotlin이 표준

**Rust가 진가를 발휘할 때:**

- 높은 동시성 + 낮은 레이턴시 서버 (Discord, Cloudflare)

- 시스템 프로그래밍 (커널, 드라이버, DB)

- 임베디드 + IoT

- CLI 도구 (단일 바이너리, 빠름)

- WebAssembly 대상

- 금융/암호화처럼 "틀리면 안 됨"

12부 — 시니어로 가는 Rust 로드맵 (12개월)

Month 1~2: Ownership·Borrowing 체화

- The Book 1~10장

- Rustlings 완주

- CLI 프로젝트 1개

Month 3~4: Trait·Generic·Error

- The Book 11~17장

- Rust By Example 심화

- 미니 라이브러리 1개 (crates.io 등록까지)

Month 5~6: async·Tokio

- Tokio Tutorial

- Jon Gjengset "Crust of Rust: Channels"

- WebSocket 서버 프로젝트

Month 7~8: 실전 서버

- Zero to Production in Rust

- Axum + sqlx + PostgreSQL 서비스

- 관측성 (tracing, metrics)

Month 9~10: 고급 주제

- `unsafe` 정확히 사용하기

- FFI (C 라이브러리 바인딩)

- 매크로 (declarative + procedural)

Month 11~12: 기여·분야 선택

- 오픈소스 crate에 PR 3개 이상

- 분야 선택: 웹 백엔드 / 블록체인 / 임베디드 / OS / 게임

13부 — Rust 체크리스트 12

1. **소유권 규칙 3개**를 한 문장으로 설명할 수 있다

2. **Borrow 규칙**을 예시와 함께 말할 수 있다 (1 mut or N immut)

3. **Lifetime `'a`** 없이 컴파일 안 되는 함수 예시를 쓸 수 있다

4. **Clone vs Copy vs Move** 차이를 안다

5. **`Box`, `Rc`, `Arc`** 차이를 안다

6. **`RefCell`, `Mutex`, `RwLock`** 차이를 안다

7. **`?` 연산자**의 정확한 동작을 설명할 수 있다

8. **async fn이 반환하는 Future**의 의미를 안다

9. **Send vs Sync** 차이를 안다

10. **Trait Bound vs dyn Trait** 중 언제 어느 것을 쓸지 판단할 수 있다

11. **sqlx compile-time 쿼리 검증**의 장점을 안다

12. **Rust 2024 Edition** 주요 변경점 3개를 안다

14부 — Rust 안티패턴 10

1. **`unwrap()` 남발**: 프로덕션에서 패닉. `?`, `expect()`, 또는 매치로 처리

2. **`clone()` 남발**: 성능 저하. 참조로 해결 가능한지 먼저 고민

3. **`Rc<RefCell<T>>`에 의존**: 설계가 잘못됐을 가능성

4. **`async` 안에서 `std::sync::Mutex`**: 데드락 위험. `tokio::sync::Mutex`

5. **`async fn` 안에서 블로킹 호출**: 런타임 스타베이션. `spawn_blocking`

6. **거대한 `match` 블록**: Trait 다형성으로 리팩터 가능

7. **`String`만 사용**: `&str`로 받을 수 있으면 받자

8. **`unsafe` 블록 난발**: 정말로 필요한지 재검토. 대부분은 안전한 대안 존재

9. **Error를 `String`으로 반환**: `thiserror` 써서 타입화

10. **의미 없는 Generic**: "혹시 몰라서"의 Generic은 복잡도만 증가

마치며 — Rust의 학습 곡선은 평생의 자산이다

Rust를 배우는 것은 단지 언어 하나를 배우는 것이 아니다. **메모리·동시성·타입 시스템을 처음부터 다시 생각하는 것**이다.

이 과정에서 얻는 것:

- C/C++ 레거시를 읽는 능력

- Go·Java에서도 동시성 버그를 미리 감지하는 직관

- 타입으로 설계하는 사고방식

- "틀리면 컴파일 안 됨"의 철학

Rust는 도구이지 종교가 아니다. 하지만 도구로서 극한의 완성도를 가졌다.

2025년, 시니어 엔지니어라면 **Rust 코드를 읽을 수 있는 것은 기본, 쓸 수 있는 것은 경쟁력**이다.

다음 글 예고 — "Go 완전 가이드: Goroutine·Channel·Context·실전 마이크로서비스까지"

Season 2 Ep 3은 Rust의 대척점이자 동반자, **Go**. 다음 글은:

- Go의 철학: "Less is more"의 진짜 의미

- Goroutine과 Channel이 Rust와 어떻게 다른가

- Context·errgroup·sync 패키지 완전 이해

- 2024~2025 Go 생태계 (Gin, Echo, Chi, Fiber)

- gRPC·마이크로서비스 실전

- Rust와 Go 중 무엇을 선택할까

"배우기 쉽고 쓰기 어려운" Go의 진짜 얼굴, 다음 글에서 이어진다.

현재 단락 (1/353)

Rust는 2015년 1.0 이후 10년 동안 "가능성 있는 언어"에서 "시스템 프로그래밍의 현대적 기본값"으로 이동했다.

작성 글자: 0원문 글자: 11,467작성 단락: 0/353