Go to Rust: #0. Overview
이 글은 Claude Opus 4.5 을 이용해 초안이 작성되었으며, 이후 퇴고를 거쳤습니다.
From Go to Rust: A Comprehensive Guide for Go Developers
Go 언어에 익숙한 개발자가 Rust를 체계적으로 학습할 수 있도록 구성된 가이드입니다. Go와의 비교를 통해 새로운 개념을 빠르게 이해하고, 실무에서 바로 적용할 수 있는 실용적인 내용을 담았습니다.
가이드의 특징#
- Go 개발자 관점: 모든 개념을 Go와 비교하여 설명, 익숙한 개념에서 출발
- 실용적 접근: 이론보다 실제 코드 예제 중심
- 점진적 난이도: 기초부터 고급까지 체계적 진행
- 실전 지향: 각 섹션에 Go ↔ Rust 코드 변환 예제 포함
- 함정 주의: Go 개발자가 흔히 하는 실수와 해결법 명시
섹션별 상세 목차#
Section 1: 시작하기 - Rust 소개와 환경 설정#
Rust의 설계 철학을 이해하고, Go와의 포지셔닝 차이를 명확히 합니다. 개발 환경을 설정하고 첫 프로젝트를 생성합니다.
1.1 Rust는 왜 배워야 하는가?#
- Rust의 설계 철학: 안전성(Safety), 속도(Speed), 동시성(Concurrency)
- Go vs Rust: 설계 목표의 차이
- Rust가 빛나는 영역
- 시스템 프로그래밍
- WebAssembly
- 임베디드 시스템
- 고성능 네트워크 서비스
- 언제 Go를, 언제 Rust를 선택할 것인가
1.2 개발 환경 설정#
- rustup을 통한 설치
- Cargo 기초: Rust의 빌드 시스템이자 패키지 매니저
cargovsgo명령어 비교- IDE 설정
- VS Code + rust-analyzer
- JetBrains RustRover
- 유용한 Cargo 명령어와 확장 도구
1.3 첫 번째 Rust 프로젝트#
- Hello, World! 작성 및 분석
cargo newvsgo mod init- 프로젝트 구조 비교
- Go workspace vs Cargo workspace
go.modvsCargo.toml
- 빌드와 실행:
cargo build,cargo run
1.4 REPL과 빠른 실험 환경#
- Rust Playground (play.rust-lang.org)
cargo run --example- evcxr: Rust REPL
- 문서 내 예제 실행
1.5 Go 개발자가 알아야 할 Rust 생태계#
- crates.io vs pkg.go.dev
- docs.rs: 자동 생성 문서
- 필수 크레이트 소개
- serde: 직렬화/역직렬화
- tokio: 비동기 런타임
- anyhow/thiserror: 에러 처리
- clap: CLI 파싱
- tracing: 로깅과 추적
- 문서화 문화와 예제 코드의 중요성
Section 2: 기본 문법과 타입 시스템#
Rust의 기본 문법을 Go와 비교하며 학습합니다. 불변성 기본 원칙, 타입 시스템, 함수, 제어 흐름을 다룹니다.
2.1 변수와 가변성#
letvsvar: 불변이 기본인 이유mut키워드: 명시적 가변성- 섀도잉(Shadowing): Go에 없는 개념
- 상수(
const)와 정적 변수(static) - 변수 선언 비교 예제
2.2 기본 데이터 타입#
- 스칼라 타입
- 정수:
i8,i16,i32,i64,i128,isize - 부호 없는 정수:
u8,u16,u32,u64,u128,usize - 부동소수점:
f32,f64 - 불리언:
bool - 문자:
char(4바이트 유니코드)
- 정수:
- Go ↔ Rust 타입 매핑 테이블
- 타입 추론과 명시적 타입 지정
- 타입 변환:
as키워드 vs Go의 타입 캐스팅 - 리터럴 표기법과 타입 접미사
2.3 복합 타입#
- 튜플(Tuple)
- 정의와 구조 분해
- Go의 다중 반환값과의 비교
- 배열(Array)
- 고정 크기, 스택 할당
[T; N]문법
- 슬라이스(Slice) 참조
&[T]문법- Go slice와의 결정적 차이점
2.4 함수#
- 함수 선언 문법:
fn키워드 - 매개변수와 반환 타입 명시
- 표현식(Expression) vs 문장(Statement)
- 세미콜론의 의미
- 마지막 표현식이 반환값
- 다중 반환: 튜플 활용
- Go 함수와의 문법 비교
2.5 제어 흐름#
if표현식- Go와 달리 표현식이다!
let과 함께 사용
- 반복문
loop: 무한 루프와 값 반환while: 조건부 루프for: 이터레이터 기반 루프
- Go의
for와 Rust의for비교 break,continue, 레이블- Range 표현식 맛보기
2.6 주석과 문서화#
- 일반 주석:
//,/* */ - 문서 주석:
///,//! - rustdoc vs godoc
- 문서 내 예제 코드
- 문서 테스트(Doc Tests)
Section 3: 소유권과 빌림 (Ownership & Borrowing)#
Rust의 가장 핵심적이고 독특한 개념입니다. Go의 GC 기반 메모리 관리와 완전히 다른 접근법을 이해합니다.
3.1 소유권이란 무엇인가?#
- 메모리 관리의 세 가지 방식
- 가비지 컬렉션 (Go, Java, Python)
- 수동 관리 (C, C++)
- 소유권 시스템 (Rust)
- Go의 GC vs Rust의 소유권: 트레이드오프
- 스택(Stack) vs 힙(Heap) 메모리
- 소유권이 해결하는 문제들
3.2 소유권 규칙#
- 세 가지 핵심 규칙
- 각 값은 하나의 소유자를 가진다
- 한 번에 하나의 소유자만 존재한다
- 소유자가 스코프를 벗어나면 값이 드롭된다
- 소유권 이동(Move)
- Go에서는 볼 수 없는 개념
- 이동 후 원본 사용 불가
Copy트레이트- 이동 대신 복사되는 타입들
- 스택 전용 타입
Clone트레이트- 명시적 깊은 복사
.clone()메서드
3.3 참조와 빌림 (References & Borrowing)#
- 불변 참조:
&T- 읽기 전용 빌림
- 여러 개 동시 가능
- 가변 참조:
&mut T- 수정 가능한 빌림
- 배타적 접근
- 빌림 규칙
- 여러 불변 참조 OR 하나의 가변 참조
- 불변과 가변의 동시 존재 불가
- Go 포인터와 Rust 참조의 차이
- 널 불가능
- 항상 유효함 보장
- 자동 역참조
3.4 슬라이스 타입#
- 문자열 슬라이스:
&strStringvs&str- 문자열 리터럴의 타입
- 배열/벡터 슬라이스:
&[T] - 슬라이스 생성: 범위 문법
- Go slice와의 비교
- 소유권 관점에서의 차이
- 용량(capacity) 개념
3.5 댕글링 참조 방지#
- 댕글링 참조란?
- Rust가 컴파일 타임에 잡아주는 버그들
- Go에서 발생할 수 있는 동시성 버그 예시
- Rust에서는 왜 불가능한가
3.6 라이프타임 기초#
- 라이프타임이 필요한 이유
- 라이프타임 추론
- 라이프타임 생략 규칙 (Elision Rules)
- 명시적 라이프타임 문법:
'a - 함수에서의 라이프타임
- 구조체에서의 라이프타임
- 흔한 라이프타임 오류와 해결법
3.7 실전 연습: 소유권 사고방식 익히기#
- 일반적인 소유권 패턴들
- Go 코드를 Rust로 변환할 때 주의점
- 컴파일러 에러 메시지 읽는 법
- 소유권 문제 해결 전략
- 클론 사용
- 참조 사용
- 구조 변경
Section 4: 구조체, 열거형, 패턴 매칭#
데이터를 구조화하는 방법을 학습합니다. Rust의 enum은 Go의 const/iota보다 훨씬 강력합니다.
4.1 구조체 (Struct)#
- 구조체 정의와 인스턴스 생성
- Go struct와의 문법 비교
- 필드 초기화 축약 문법
- 구조체 업데이트 문법:
..스프레드 - 튜플 구조체:
struct Point(i32, i32) - 유닛 구조체:
struct Marker - 가시성과 pub 키워드
4.2 메서드와 연관 함수#
impl블록- 메서드 정의
self: 소유권 가져감&self: 불변 빌림&mut self: 가변 빌림
- Go의 리시버 메서드와 비교
- 연관 함수 (Associated Functions)
Self키워드- 생성자 패턴:
new(),default()
- 여러
impl블록
4.3 열거형 (Enum)#
- 기본 열거형
- Go의
const+iotavs Rustenum - 데이터를 담는 열거형 (Go에 없는 강력한 기능)
- 튜플 variant
- 구조체 variant
Option<T>: null 없는 세상Some(T)와None- Go의 nil/zero value와 비교
Result<T, E>: 에러 처리의 새로운 패러다임Ok(T)와Err(E)- Go의
(value, error)반환과 비교
4.4 패턴 매칭#
match표현식- 기본 문법
- 모든 경우 처리 필수 (Exhaustive)
- Go의
switch와의 비교- fallthrough 없음
- 표현식으로 사용 가능
if let: 단일 패턴 매칭while let: 반복 패턴 매칭- 패턴 문법 총정리
- 리터럴 매칭
- 변수 바인딩
- 구조 분해 (Destructuring)
- 가드 조건 (
if) @바인딩_와일드카드..나머지 무시
4.5 실전 패턴: 타입으로 상태 표현하기#
- 상태 머신 패턴
- Go interface vs Rust enum: 선택 기준
- newtype 패턴: 타입 안전성 강화
- 빌더 패턴 맛보기
Section 5: 트레이트와 제네릭#
Rust의 다형성을 담당하는 트레이트와 제네릭을 학습합니다. Go interface와의 근본적인 차이를 이해합니다.
5.1 트레이트 기초#
- 트레이트란?
- 트레이트 정의:
trait키워드 - Go interface vs Rust trait
- 구조적(Structural) vs 명시적(Nominal)
- 암묵적 구현 vs 명시적 구현
- 트레이트 구현:
impl Trait for Type - 기본 구현 (Default Implementation)
- 고아 규칙 (Orphan Rule)
5.2 표준 라이브러리의 주요 트레이트#
Debug: 디버그 출력 ({:?})Display: 사용자 친화적 출력 ({})Clone: 명시적 복사Copy: 암묵적 복사PartialEq,Eq: 동등성 비교PartialOrd,Ord: 순서 비교Default: 기본값 생성From,Into: 타입 변환TryFrom,TryInto: 실패 가능한 변환Iterator: 이터레이터 프로토콜Drop: 소멸자
5.3 제네릭#
- 제네릭 함수
- 제네릭 구조체
- 제네릭 열거형
- 제네릭 메서드
- Go 1.18+ 제네릭과의 비교
- 문법 차이
- 기능 차이
- 단형화 (Monomorphization)
- 런타임 비용 제로
- Go interface의 동적 디스패치와 비교
5.4 트레이트 바운드#
- 기본 문법:
T: Trait - 다중 바운드:
T: Trait1 + Trait2 where절: 복잡한 바운드 표현- Go의 타입 제약과 비교
- 조건부 구현
5.5 고급 트레이트 기능#
- 연관 타입 (Associated Types)
- 제네릭 파라미터 vs 연관 타입
- Iterator의 Item 타입
- 트레이트 객체:
dyn Trait- 동적 디스패치
- 객체 안전성 (Object Safety)
impl Trait- 반환 타입에서의 사용
- 매개변수에서의 사용
- Go interface와의 상세 비교
- vtable 구조
- 성능 특성
5.6 Derive 매크로#
#[derive(...)]속성- 자동 구현 가능한 트레이트들
- serde와 직렬화/역직렬화
#[derive(Serialize, Deserialize)]- Go의 encoding/json 태그와 비교
Section 6: 에러 처리#
Rust의 에러 처리 철학과 실전 패턴을 학습합니다. Go의 if err != nil 패턴과 비교합니다.
6.1 Rust의 에러 처리 철학#
- 복구 가능한 에러 vs 복구 불가능한 에러
Result<T, E>vspanic!- Go의 error 인터페이스와 비교
- Rust에서 panic이 적절한 경우
6.2 Result<T, E> 심화#
- Result의 구조
- 패턴 매칭으로 처리
if let으로 처리- 주요 메서드들
unwrap(): 성공 시 값, 실패 시 panicexpect(msg): 커스텀 panic 메시지unwrap_or(default): 기본값 제공unwrap_or_else(f): 지연 기본값unwrap_or_default(): Default 트레이트 활용
- 언제
unwrap을 사용해도 되는가
6.3 ? 연산자#
- 에러 전파의 편리한 문법
?동작 원리- Go의
if err != nil보일러플레이트와 비교 From트레이트와의 연계- 체이닝 사용법
main에서Result반환하기
6.4 Option 활용#
- null의 부재
- Option 주요 메서드들
map(): 값 변환and_then(): 체이닝 (flatMap)or_else(): 대체값filter(): 조건부 유지ok_or(): Option → Result 변환
- Go의 nil 검사 패턴과 비교
?연산자와 Option
6.5 커스텀 에러 타입#
- 에러 타입 정의하기
std::error::Error트레이트 구현std::fmt::Display구현- thiserror 크레이트
#[derive(Error)]#[error("...")]속성
- anyhow 크레이트
- 빠른 프로토타이핑
anyhow::Result<T>- 컨텍스트 추가
6.6 에러 처리 패턴과 베스트 프랙티스#
- 라이브러리 vs 애플리케이션 에러 전략
- 에러 컨텍스트 추가하기
- Go의
errors.Wrap/fmt.Errorf와 비교 - 에러 다운캐스팅
- 에러 처리 계층 설계
Section 7: 컬렉션과 이터레이터#
Rust의 주요 컬렉션과 강력한 이터레이터 시스템을 학습합니다.
7.1 표준 컬렉션 개요#
Vec<T>: 동적 배열String: 소유된 문자열HashMap<K, V>: 해시 맵HashSet<T>: 해시 셋BTreeMap<K, V>: 정렬된 맵BTreeSet<T>: 정렬된 셋VecDeque<T>: 양방향 큐- Go 컬렉션과의 매핑
7.2 Vec 심화#
- 생성 방법들
Vec::new()vec![]매크로Vec::with_capacity()
- 기본 연산: push, pop, insert, remove
- 용량 관리: capacity vs length
- 슬라이싱과 범위 인덱싱
- Go의
appendvs Rust의push - 소유권과 Vec
- 요소 이동
- 요소 빌림
7.3 문자열 처리#
Stringvs&str- 소유권 관점
- 힙 vs 스택/정적
- UTF-8 인코딩과 인덱싱
- Go의 string과 비교
- 문자열 생성과 변환
- 문자 순회
chars(): 유니코드 스칼라bytes(): 바이트- Go의
rune순회와 비교
- 문자열 연결과 포맷팅
+연산자format!매크로push_str,push
7.4 HashMap 활용#
- 생성과 기본 연산
- Entry API
entry(),or_insert(),or_insert_with()- Go에 없는 강력한 기능
- 해싱과
Eq,Hash트레이트 - 커스텀 타입을 키로 사용
- 동시성과 HashMap
- Go의
sync.Map과 비교 RwLock<HashMap<K, V>>- dashmap 크레이트
- Go의
7.5 이터레이터 기초#
Iterator트레이트- 이터레이터 생성
iter():&T이터레이터iter_mut():&mut T이터레이터into_iter():T이터레이터 (소유권 이동)
- Go의
range와 비교 for루프와 이터레이터
7.6 이터레이터 어댑터#
- 변환 어댑터
map(): 요소 변환filter(): 요소 필터링filter_map(): 변환 + 필터링flat_map(): 평탄화 + 변환
- 제어 어댑터
take(),skip()take_while(),skip_while()step_by()
- 조합 어댑터
chain(): 이터레이터 연결zip(): 이터레이터 결합enumerate(): 인덱스 추가
- 지연 평가 (Lazy Evaluation)
7.7 이터레이터 컨슈머#
collect(): 컬렉션으로 수집fold(): 누적 연산reduce(): fold의 초기값 없는 버전for_each(): 부수 효과 실행count(),sum(),product()any(),all(),find()max(),min(),max_by(),min_by()
7.8 클로저와 함수형 패턴#
- 클로저 문법
- 캡처 방식
- 불변 빌림
- 가변 빌림
- 소유권 이동 (
move)
- Go의 클로저와 비교
Fn,FnMut,FnOnce트레이트- 이터레이터 체이닝 실전 예제
- Go 스타일 vs Rust 함수형 스타일 비교
Section 8: 동시성 프로그래밍#
Rust의 “Fearless Concurrency"를 실현하는 동시성 프로그래밍을 학습합니다.
8.1 Rust의 동시성 철학#
- “Fearless Concurrency"란?
- 컴파일 타임 데이터 레이스 방지
- Go의 “Don’t communicate by sharing memory” 철학과 비교
- Rust는 어떻게 메모리 안전성을 보장하는가
8.2 스레드 기초#
std::thread::spawn- 클로저와 스레드
JoinHandle과 반환값- Go goroutine과의 차이
- OS 스레드 vs 그린 스레드
- 스택 크기
- 스케줄링
move키워드와 소유권
8.3 메시지 패싱#
- 채널 기초:
std::sync::mpscchannel(): 무한 버퍼sync_channel(): 유한 버퍼
- 송신자(
Sender)와 수신자(Receiver) - Go channel과의 비교
- 버퍼링 방식
- 닫기 의미론
- 여러 생산자:
Sender::clone() - 채널을 통한 소유권 전달
8.4 선택적 수신#
- crossbeam의
select! - Go의
select와 비교 - 타임아웃 처리
- 비블로킹 연산
8.5 공유 상태 동시성#
Mutex<T>- 락 획득과 해제
lock()vstry_lock()- Go의
sync.Mutex와 비교
RwLock<T>- 읽기 락과 쓰기 락
- Go의
sync.RWMutex와 비교
- 데드락 방지 전략
- 락 가드와 스코프
8.6 Arc: 스레드 안전한 참조 카운팅#
Rc<T>vsArc<T>Arc<Mutex<T>>패턴- Go에서 공유 상태 vs Rust에서 공유 상태
8.7 Send와 Sync 트레이트#
Send: 스레드 간 소유권 전달 가능Sync: 스레드 간 참조 공유 가능- 타입 시스템을 통한 동시성 안전성
- Go에서 흔히 발생하는 동시성 버그
- Rust에서는 왜 불가능한가
8.8 동시성 패턴#
- Worker Pool 패턴
- Fan-out/Fan-in 패턴
- Go와 Rust 코드 비교
8.9 Rayon: 데이터 병렬성#
- 병렬 이터레이터
par_iter(),par_iter_mut()- Go의 errgroup과 비교
- 작업 분할과 조인
Section 9: 비동기 프로그래밍#
Rust의 async/await 모델을 학습합니다. Go의 goroutine 모델과의 근본적 차이를 이해합니다.
9.1 비동기의 필요성#
- 동기 vs 비동기
- 블로킹 vs 논블로킹
- Go의 goroutine 모델
- 런타임 내장
- 블로킹 코드가 비동기처럼 동작
- Rust의 async 모델
- 런타임 선택 가능
- 명시적 비동기
- 언제 스레드, 언제 async
9.2 Future 트레이트#
Future란?Poll열거형- 지연 실행 (Lazy Execution)
- Go goroutine과의 개념적 차이
9.3 async/await 기초#
async fn: 비동기 함수 정의.await: Future 실행- 비동기 블록:
async { } - 반환 타입:
impl Future<Output = T> - 에러 처리와
?
9.4 Tokio 런타임#
- 런타임이 필요한 이유
- Tokio 설치와 설정
#[tokio::main]매크로tokio::spawn: 태스크 생성- Go 런타임과의 비교
- 스케줄러
- 작업 훔치기 (Work Stealing)
9.5 비동기 IO#
tokio::fs: 파일 시스템tokio::net: 네트워크TcpListener,TcpStreamUdpSocket
tokio::io: AsyncRead, AsyncWrite- Go의
io.Reader/io.Writer와 비교 - 버퍼링과 BufReader/BufWriter
9.6 동시 실행#
tokio::join!: 모두 완료 대기tokio::select!: 첫 번째 완료 대기- Go의
select와 비교 tokio::spawnvsjoin!- 타임아웃:
tokio::time::timeout - 취소와 드롭
9.7 비동기 스트림#
Stream트레이트StreamExt어댑터- Go의 channel과 비교
tokio::sync::mpsc: 비동기 채널tokio_stream크레이트
9.8 동기화 프리미티브#
tokio::sync::Mutex: 비동기 뮤텍스tokio::sync::RwLock: 비동기 읽기-쓰기 락tokio::sync::Semaphore: 세마포어tokio::sync::oneshot: 일회성 채널tokio::sync::broadcast: 브로드캐스트 채널tokio::sync::watch: 감시 채널
9.9 실전 비동기 패턴#
- HTTP 클라이언트: reqwest
- 웹 서버: axum
- 라우팅
- 미들웨어
- 상태 공유
- Go의
net/http와 비교 - 데이터베이스: sqlx
- 그레이스풀 셧다운
Section 10: 프로젝트 관리와 실전 패턴#
실제 Rust 프로젝트를 구성하고 관리하는 방법, 그리고 실전에서 사용되는 패턴들을 학습합니다.
10.1 Cargo 심화#
Cargo.toml상세 설정[package]섹션[dependencies]섹션[dev-dependencies]섹션[build-dependencies]섹션[features]섹션
- 의존성 관리
- 버전 지정
- Git 의존성
- 경로 의존성
- 피처 플래그
- Go modules와 비교
- 프라이빗 레지스트리
10.2 모듈 시스템#
mod키워드- 파일 시스템과 모듈 구조
pub: 가시성 제어pub(crate),pub(super): 제한된 가시성use: 경로 가져오기self,super,crate- Go 패키지 시스템과의 비교
- 워크스페이스
- 멀티 크레이트 프로젝트
- Go workspace와 비교
10.3 테스트#
- 유닛 테스트
#[test]속성#[cfg(test)]모듈- assert 매크로들
- 통합 테스트
tests/디렉토리- 각 파일이 별도 크레이트
- 문서 테스트
- 테이블 드리븐 테스트
- Go의 테스팅과 비교
_test.govs#[test]go testvscargo test
- 테스트 설정과 정리
10.4 벤치마킹과 프로파일링#
- 내장 벤치마크 (nightly)
- Criterion 크레이트
- 벤치마크 작성법
- flamegraph로 프로파일링
- Go의 pprof와 비교
10.5 빌드와 릴리스#
- 빌드 프로파일
[profile.dev][profile.release]- 커스텀 프로파일
- 컴파일러 최적화 옵션
- 크로스 컴파일
rustup target add.cargo/config.toml
- Go의 크로스 컴파일과 비교
- 바이너리 크기 최적화
- LTO (Link Time Optimization)
- 심볼 스트리핑
- 의존성 최소화
10.6 unsafe Rust#
- unsafe가 필요한 경우
- unsafe 블록
- unsafe 함수와 메서드
- unsafe 트레이트
- 안전한 추상화 만들기
- FFI와 C 연동
- extern “C”
#[repr(C)]- bindgen, cbindgen
- Go의 cgo와 비교
10.7 실전 설계 패턴#
- Builder 패턴
- 복잡한 객체 생성
- 메서드 체이닝
- Type State 패턴
- 컴파일 타임 상태 검증
- Go에서는 구현 불가능한 패턴
- Newtype 패턴
- 타입 안전성
- 트레이트 구현
- 에러 처리 전략
- 라이브러리 설계
- 애플리케이션 설계
10.8 Go 코드를 Rust로 포팅하기#
- 마이그레이션 전략
- 일반적인 변환 패턴
- 구조체
- 인터페이스
- 에러 처리
- 동시성
- 흔한 함정과 해결법
- 성능 비교
10.9 생태계와 크레이트 추천#
- 웹 프레임워크
- axum
- actix-web
- rocket
- CLI
- clap
- structopt (구버전)
- 직렬화
- serde
- serde_json, serde_yaml
- 로깅과 추적
- tracing
- log
- env_logger
- 데이터베이스
- sqlx
- diesel
- sea-orm
- HTTP 클라이언트
- reqwest
- 유틸리티
- anyhow, thiserror
- itertools
- chrono
- regex
부록#
A. Go ↔ Rust 치트시트#
- 문법 대응표
- 타입 매핑
- 표준 라이브러리 대응
B. 자주 묻는 질문 (FAQ)#
C. 추가 학습 자료#
- 공식 문서
- 추천 도서
- 온라인 강좌
- 커뮤니티
버전 정보#
- 작성일: 2025년 1월
- Rust 버전: 1.75+ (2024 에디션 기준)
- 대상 독자: Go 언어에 익숙한 개발자
이 가이드는 Go 개발자의 관점에서 Rust를 학습할 수 있도록 구성되었습니다. 각 섹션은 독립적으로 읽을 수 있지만, 순서대로 진행하는 것을 권장합니다.