Rust 학습 로드맵 — C++ 하던 사람이 무엇을 먼저
C++ 배경에서 Rust로 넘어올 때의 학습 순서를 기초·소유권·타입·트레이트·반복자·에러·동시성·도구로 묶고, 각 주제를 필수·나중·선택으로 표시한 로드맵. C++ 개념(RAII·이동·스마트 포인터)과의 대응뿐 아니라 반복자·클로저·매크로·소유권 기반 설계 같은 Rust 고유 축을 줄기로 넣었다. 정본 교재(The Rust Book·rustlings) 링크와 자주 막히는 지점을 함께 정리한, 배우면서 채우는 진행형 인덱스.
이 로드맵을 만든 이유
Rust를 처음 시작하면서, “무슨 문법이 있나”를 목차 순서로 훑는 대신 C++을 아는 사람이 무엇을 먼저 배우고 무엇을 나중으로 미뤄도 되는지를 지도로 만들었습니다.
Rust는 C++ 배경이 가장 크게 빛나는 언어입니다. 소유권·빌림(borrow)은 결국 C++의 RAII·이동 시맨틱·unique_ptr/shared_ptr·댕글링 포인터 문제를 컴파일러가 강제하는 버전이거든요. C++에서 “조심해서 지켜야 했던 규칙”을 Rust는 안 지키면 컴파일이 안 됩니다. 대신 초반에 borrow checker와 싸우는 구간이 가파릅니다 — 이 로드맵은 그 벽을 가장 효율적인 순서로 넘는 걸 목표로 합니다.
다만 C++ 대응만으로 Rust를 다 배우는 건 아닙니다. 반복자·클로저, 매크로, 소유권 기반 API 설계처럼 C++ 대응이 없거나 Rust에서만 배우는 축이 따로 있고, 그게 오히려 Rust를 Rust답게 쓰는 핵심입니다. 그래서 그것들을 각주가 아니라 줄기 안에 넣었습니다(아래 “고유 영역” 참고).
각 주제에는 세 가지 우선순위를 붙였습니다.
- [필수] — 이걸 건너뛰면 다음이 막힙니다. 순서대로 잡고 갑니다.
- [나중] — 중요하지만 급하지 않습니다. 필요해질 때 돌아와도 됩니다.
- [선택] — 특정 상황(라이브러리 저자, 고성능)에서만 필요합니다. 개념만 알고 넘어가도 됩니다.
그리고 이 로드맵은 진행형입니다. 상세 글은 아직 없고, 배우면서 직접 채워 나갈 예정이라 각 단계에 학습 상태를 표시합니다.
- ⬜ 미학습 · 🚧 진행 중 · ✅ 정리 완료 (상세 글 링크가 붙으면 완료)
지금 단계에서 각 주제의 “목적지”는 개인 글이 아니라 정본 교재입니다. Rust는 공식 책이 사실상 유일한 표준이라, 새로 쓰기보다 그걸 타는 게 맞습니다.
📖 정본 교재 — 이 로드맵은 아래를 가리키는 인덱스입니다.
- The Rust Book — 공식·무료. 거의 모든 러스타시안이 여기서 시작. 여기가 본체.
- rustlings — borrow checker와 강제로 싸우게 하는 연습문제. 책과 병행 필수.
- Rust by Example — 주제별 실행 가능한 예제.
C++ 대응으로는 안 잡히는 Rust 고유 영역
대응표(부록)는 “이게 C++의 뭐냐”를 빠르게 잡아 주지만, Rust에는 대응이 없거나 Rust에서만 배우는 축이 따로 있습니다. C++ 지식이 오히려 방해가 될 수도 있는 지점이라 줄기 안에 못박아 뒀습니다.
- 반복자·클로저(iterator·
Fn/FnMut/FnOnce) (⑦):for문 대신iter().map().filter().collect()로 짜는 함수형 체인이 idiomatic Rust의 본체. 게다가 zero-cost라 손으로 쓴 루프만큼 빠릅니다. - 소유권 기반 API 설계 (②·④ 이후 전반): “이 함수는 값을 가져갈까(move) 빌릴까(&)”를 타입으로 설계하는 사고. C++엔 없는 축.
- enum +
match의 표현력 (③): 상태를 타입으로 강제.Option/Result가 여기서 나옴. - 내부 가변성(
Cell/RefCell) (⑧): 빌림 검사를 런타임으로 미루는 Rust 특유 개념. C++에 직접 대응 없음. - 매크로(
macro_rules!·derive·proc-macro) (부록):#[derive(...)]·println!의 정체. 코드가 코드를 생성. unsafe·FFI (부록): 안전 보장을 명시적으로 끄는 문. C에 연결하거나 저수준을 만질 때.
한눈에 보기
| 단계 | 주제 | 우선순위 | 상태 |
|---|---|---|---|
| 기초 | ① 문법·불변성·cargo | 필수 | ⬜ |
| 핵심 | ② 소유권·빌림·수명 | 필수 · 뼈대 | ⬜ |
| 타입 | ③ struct·enum·match | 필수 | ⬜ |
| 타입 | ④ 컬렉션·String vs &str | 필수 | ⬜ |
| 흐름 | ⑤ error 처리 — Result·Option·? | 필수 | ⬜ |
| 추상화 | ⑥ trait·제네릭 (+심화·dyn·orphan) | 필수 | ⬜ |
| 관용구 | ⑦ 반복자·클로저 (Rust다움의 본체) | 필수 · 고유 | ⬜ |
| 소유권 심화 | ⑧ 스마트 포인터 — Box·Rc·RefCell | 나중 | ⬜ |
| 동시성 | ⑨ thread·Send/Sync·async | 나중 | ⬜ |
| 마지막 | ⑩ 도구 — cargo·crates·모듈 | 필수 | ⬜ |
| 부록 | 매크로 — macro_rules!·derive | 나중 | ⬜ |
| 부록 | unsafe·FFI | 선택 | ⬜ |
| 부록 | C++ → Rust 개념 대응표 | 참고 | ✅ |
②(소유권)가 전부의 뼈대입니다. 여기서 막히면 이후 모든 게 막히고, 여기를 넘으면 나머지는 빠릅니다. 아래는 각 단계를 왜 그 순서로 두는지 풀어 쓴 것입니다.
① 기초 — 문법·불변성·cargo
이 단계 목표는 암기가 아니라 C++과 다른 Rust의 기본 태도입니다.
- [필수] 기본 문법 —
let(기본 불변!),mut로만 가변, 함수, 표현식 기반(if·블록이 값을 반환). - [필수] cargo — 프로젝트 생성·빌드·실행·테스트·의존성이
cargo하나로. C++의 CMake+패키지 매니저를 합친 것. - [필수] 기본 타입 — 정수/실수, 튜플, 배열. 그리고 shadowing(같은 이름 재선언) 개념.
자주 막히는 지점: 변수가 기본 불변이라는 것. C++ 습관으로 값을 바꾸려다 컴파일 에러를 만납니다. mut를 명시해야 가변.
통과 기준:
cargo new로 만든 프로젝트를 빌드·실행하고, 불변/가변 변수의 차이를 설명할 수 있으면 다음으로.
② 핵심 — 소유권·빌림·수명 (뼈대, 최우선)
Rust의 전부. 여기에 학습 시간의 절반을 쓸 각오로 들어갑니다. C++의 이동 시맨틱·RAII를 안다면 직관이 크게 도와줍니다.
- [필수] 소유권(ownership) — 값에는 소유자가 하나. 소유자가 스코프를 벗어나면 자동 해제(RAII와 동일). 대입/전달이 이동(move)이라 원본은 무효화됩니다 — C++의
std::move가 기본 동작인 셈. - [필수] 빌림(borrow) —
&(불변 참조)·&mut(가변 참조). “가변 참조는 동시에 하나만” 규칙이 데이터 레이스를 컴파일 타임에 막습니다. - [필수] 수명(lifetime) — 참조가 원본보다 오래 살 수 없음. 댕글링 포인터를 컴파일러가 거절. 처음엔
'a문법이 낯설지만 대부분 자동 추론됩니다.
자주 막히는 지점: “borrow checker와 싸운다”는 바로 이 구간. 값을 옮긴 뒤 또 쓰려 하거나(use after move), 불변 참조가 살아있는데 가변으로 빌리려는 것. 에러 메시지를 적으로 보지 말고 선생으로 보세요 — Rust 컴파일러 에러는 해결책까지 알려줍니다.
통과 기준: use-after-move와 mutable borrow 충돌 에러를 스스로 진단하고 고칠 수 있으면, 가장 큰 산을 넘은 것입니다.
③ 타입 — struct·enum·match
Rust의 타입 시스템은 C++보다 강력하고, 특히 enum이 다릅니다.
- [필수] struct와 method(
impl) — 데이터는struct, 동작은impl블록. 클래스는 없습니다. - [필수] enum (대수적 타입) — C++ enum과 차원이 다릅니다. 각 variant가 데이터를 가질 수 있어(
Option,Result가 이걸로 만들어짐) 상태를 타입으로 표현. - [필수] 패턴 매칭(
match) — enum을 완전하게 분해. 모든 경우를 안 다루면 컴파일 에러(exhaustiveness).if let·while let·가드(if)·바인딩까지 패턴은 Rust 전반에 깔립니다.
자주 막히는 지점: enum을 C++처럼 “이름 붙은 정수”로만 생각하는 것. Rust enum은 데이터를 품은 합 타입(sum type)이라는 게 핵심입니다.
④ 타입 — 컬렉션·String vs &str
- [필수] Vec·HashMap —
std::vector·unordered_map대응. - [필수] String vs &str — Rust 입문자를 가장 헷갈리게 하는 지점.
String(소유, 힙)과&str(빌린 문자열 슬라이스)의 구분은 ②의 소유권과 직결됩니다. 여기서 소유권이 다시 몸에 붙습니다.
자주 막히는 지점: 함수 인자로 String을 받을지 &str을 받을지. 대부분 &str을 받는 게 맞습니다(소유권을 뺏지 않으니까). 이 “받을 때 빌리고, 줄 때만 소유”가 소유권 기반 API 설계의 첫 감각입니다.
⑤ 흐름 — error 처리 (Result·Option·?)
C++/Java에서 온 사람에게 문화 충격. Rust에도 예외가 없습니다. 대신 ③의 enum으로 에러를 표현합니다. 파일 하나만 열어도 바로 Result를 만나므로, 추상화(trait)보다 먼저 잡습니다(정본 Book도 이 순서).
- [필수] Option — 값이 있을 수도/없을 수도(
Some/None). null이 없는 Rust의 null 대체. - [필수] Result — 성공/실패(
Ok/Err)를 값으로.match나?로 처리. - [필수]
?연산자 — 에러를 상위로 전파하는 축약. Go의if err != nil을 한 글자로. - [나중] 에러 생태계 관용구 —
thiserror(라이브러리용 에러 타입 정의)·anyhow(앱용 간편 에러). 실전 Rust 코드는 거의 이 둘을 씁니다. de-facto 표준 crate. - [나중] panic! — 복구 불가 상황용. C++ 예외처럼 흐름 제어에 쓰면 안 됩니다.
자주 막히는 지점: Option/Result를 벗기려고 .unwrap()을 남발하는 것(패닉 위험). match·?·if let으로 제대로 다루는 습관.
통과 기준:
?로 에러를 전파하는 함수를 만들고, 호출부에서match로Ok/Err를 갈라 처리할 수 있으면 통과.
⑥ 추상화 — trait·제네릭
C++의 인터페이스·템플릿·concept에 해당하는 축. Rust 추상화의 중심이라 심화까지 여기서 짚습니다.
- [필수] trait — 공유 동작의 정의. C++20 concept + 인터페이스에 가깝습니다.
impl Trait for Type으로 구현. - [필수] 제네릭 + trait bound —
fn foo<T: Display>. C++ 템플릿과 달리 제약을 미리 선언해서 에러가 훨씬 친절합니다. - [나중] 트레이트 객체(
dyn) vs 정적 디스패치 — 런타임 다형성(dyn Trait, vtable)과 컴파일 타임 단형화(제네릭)의 트레이드오프. C++의 가상 함수 vs 템플릿과 같은 갈림. - [선택] 심화 — associated type·blanket impl·orphan rule — 라이브러리를 설계할 때 만나는 규칙들. “왜 이 trait을 이 타입에 구현 못 하지”(orphan rule)의 답이 여기.
자주 막히는 지점: C++ 템플릿의 “일단 쓰고 안 되면 에러” 습관. Rust는 trait bound를 미리 선언해야 하고, 그 덕에 에러가 명확합니다.
⑦ 관용구 — 반복자·클로저 (Rust다움의 본체)
C++ 대응이 흐릿한, Rust에서 새로 배우는 축. 문법만 알고 여길 건너뛰면 “C++을 Rust 문법으로 쓴” 코드가 나옵니다. Rust 코드가 실제로 어떻게 생겼는지가 여기서 갈립니다.
- [필수] 클로저(
Fn/FnMut/FnOnce) — 익명 함수. 환경을 어떻게 캡처하느냐(빌림·가변 빌림·소유 이동)가 세 트레이트로 나뉘는데, 이게 ②의 소유권과 정확히 맞물립니다.move클로저. - [필수] 반복자(iterator) —
for문 대신iter().map().filter().fold().collect()체인. lazy하고 zero-cost(손 루프만큼 빠름).Iteratortrait 하나로 굴러갑니다. - [나중] 커스텀 iterator — 내 타입에
Iterator를 구현해for로 돌게 만들기.
자주 막히는 지점: C++ 습관으로 인덱스 for 루프를 쓰는 것. Rust에서는 대부분 iterator 체인이 더 짧고 안전하고 빠릅니다. collect()의 타입 추론(어디로 모을지)도 처음엔 헷갈립니다.
통과 기준: 인덱스
for루프로 짠 로직을map/filter/collect체인으로 바꿔 쓸 수 있으면, Rust식 사고가 붙기 시작한 것입니다.
⑧ 소유권 심화 — 스마트 포인터
②의 소유권 규칙이 너무 빡빡할 때 푸는 도구. C++ 스마트 포인터와 거의 1:1 대응이라 C++ 배경이 크게 유리한 구간입니다.
- [나중] Box — 힙 할당.
unique_ptr대응(유일 소유). - [나중] Rc / Arc — 참조 카운트 공유 소유.
shared_ptr대응(Arc는 스레드 안전 버전). - [나중] RefCell·Cell — 내부 가변성(interior mutability) — 빌림 검사를 런타임으로 미룸. C++엔 직접 대응이 없는 Rust 특유 개념으로,
Rc<RefCell<T>>조합은 공유+가변이 필요한 자료구조(그래프·트리)의 관용구. - [선택] Weak — 순환 참조 끊기.
weak_ptr대응.
⑨ 동시성 — thread·Send/Sync·async
Rust의 슬로건 “fearless concurrency”. 소유권 시스템이 데이터 레이스를 컴파일 타임에 막습니다.
- [나중] thread + 채널 —
std::thread,mpsc채널. C++보다 안전. - [나중] Send / Sync — 타입이 스레드 간 이동/공유 가능한지 표시하는 마커 트레이트. Rust 동시성 안전성의 근간.
- [선택] async/await (Book Ch.17) — 비동기.
tokio런타임 생태계. Book에서도 동시성(Ch.16) 다음의 별도 챕터이니, 동기 동시성을 뗀 뒤에.
⑩ 마지막 — 도구
Rust는 도구 경험이 언어의 강점입니다.
- [필수] cargo — 빌드·테스트·의존성·문서화가 하나로. C++ 생태계의 파편화된 도구들을 통합.
- [필수] 모듈 시스템(
mod·use) — 헤더 없이 코드 조직화. 가시성(pub). - [필수] crates.io — 패키지 저장소.
cargo add로 의존성 추가. - [나중] clippy / rustfmt — 린터·포매터.
cargo clippy의 조언은 학습에도 좋습니다.
여기까지가 Rust를 배우는 학습 줄기입니다.
부록 — 매크로 (macro_rules!·derive)
[필수] 경로 밖이지만 Rust 고유 축. #[derive(Debug, Clone)]·println!·vec!의 정체가 매크로입니다. 쓰는 건 처음부터 하지만, 직접 만드는 것은 미뤄도 됩니다.
- [나중] 선언적 매크로(
macro_rules!) — 패턴으로 코드를 생성. 반복 보일러플레이트 제거. - [선택] 절차적 매크로(proc-macro)·derive 매크로 작성 — 라이브러리 저자용.
serde가 이걸로 동작.
부록 — unsafe·FFI
[선택] 안전 보장을 명시적으로 끄는 문. 대부분의 앱 코드는 평생 안 써도 됩니다.
- [선택] unsafe — 원시 포인터 역참조 등. 컴파일러가 못 지켜 주는 영역을 사람이 책임.
- [선택] FFI — C 라이브러리 연결(
extern "C"). C++ 자산을 Rust에서 부를 때.
부록 — C++ → Rust 개념 대응표 (참고)
C++을 알고 오는 사람의 지도. Rust가 특히 잘 대응되는 언어라 이 표가 강력합니다. 단, 위 “고유 영역”(반복자·클로저·매크로 등)은 이 표에 안 잡힙니다 — 대응표는 절반의 지도입니다.
| C++ | Rust | 핵심 차이 |
|---|---|---|
std::move (수동, 선택) | 이동(move, 기본 동작) | 대입/전달이 기본 이동. 원본은 무효화 |
| RAII 소멸자 | 소유권 drop | 동일 개념을 컴파일러가 강제 |
| 댕글링 포인터 (런타임 UB) | 수명(lifetime) | 컴파일 타임에 거절 |
const T& | &T (불변 빌림) | 기본이 불변. 규칙을 컴파일러가 검사 |
T& (가변) | &mut T | “가변 참조는 동시에 하나” 강제 |
unique_ptr | Box | 유일 소유 |
shared_ptr | Rc / Arc | 참조 카운트. Arc는 스레드 안전 |
weak_ptr | Weak | 순환 참조 끊기 |
mutable (const 안 가변) | RefCell / Cell | 내부 가변성. 런타임 빌림 검사 |
class | struct + impl | 데이터와 동작 분리 |
| 순수 가상 인터페이스 | trait | C++20 concept에 더 가까움 |
| enum (정수) | enum (합 타입) | variant가 데이터를 가짐 |
std::optional | Option | null 자체가 없음 |
예외 (try/catch) | Result + ? | 예외 없음. 에러는 값 |
| 템플릿 | 제네릭 + trait bound | 제약을 미리 선언 → 친절한 에러 |
| 가상 함수 vs 템플릿 | dyn Trait vs 제네릭 | 런타임 vs 컴파일 타임 디스패치 |
| 람다 | 클로저(Fn/FnMut/FnOnce) | 캡처 방식이 소유권과 맞물림 |
<algorithm> + 범위 for | iterator 체인(map·filter) | lazy·zero-cost. Rust의 기본 스타일 |
전처리기 매크로(#define) | macro_rules!·derive | 텍스트 치환이 아니라 위생적(hygienic) |
std::thread | thread + Send/Sync | 데이터 레이스를 컴파일 타임 차단 |
| CMake + Conan/vcpkg | cargo | 빌드·의존성·테스트 통합 |
extern "C" | unsafe + FFI(extern "C") | 안전 경계를 명시적으로 넘음 |
이 로드맵을 쓰는 법
- ②(소유권)에 시간을 몰아주세요. 여기가 전부입니다. rustlings의 move/borrow 연습을 반복.
- ⑦(반복자·클로저)을 건너뛰지 마세요. 대응표에 안 잡히는, “Rust답게” 쓰는 법의 핵심입니다.
- 컴파일러 에러는 선생입니다. Rust 에러 메시지는 원인과 해결책을 알려줍니다 — 읽으면서 배우세요.
- 진도는 “봤다”가 아니라 각 단계의 통과 기준을 직접 구현할 수 있는지로 판단합니다.
- 한 단계를 정리하면 상태를 ⬜→✅로 바꾸고, borrow checker와 싸우며 얻은 깨달음을 상세 글로 남깁니다.
- C++ 습관(수동 메모리 관리, 예외, 인덱스 루프)이 튀어나오면 대응표·고유 영역으로 돌아와 Rust식 사고로 교정합니다.
Reference
- The Rust Book — 공식·무료. 본체.
- rustlings — 손으로 borrow checker 넘기.
- Rust by Example — 실행 가능한 예제.
- The Cargo Book — 도구·의존성 1차 레퍼런스.
- Rust by Practice — rustlings 이후의 심화 연습.