Step 03: Option과 Result로 에러 처리
core 60 min

Option과 Result로 에러 처리

Option과 Result로 안전하게 에러를 처리합니다.

Execute this step

Run from project root:
cargo run

Step 3: Option과 Result로 에러 처리

학습 목표

  • Option으로 "있을 수도, 없을 수도" 있는 값 표현하기
  • Result로 성공과 실패를 명시적으로 처리하기
  • if let으로 간결하게 패턴 매칭하기

핵심 개념

1. Option<T>

Rust는 null이 없습니다. 대신 Option<T>를 사용합니다.

1enum Option<T> {
2    Some(T),  // 값이 있음
3    None,     // 값이 없음
4}

예제:

1fn find_book(&self, id: u32) -> Option<&Book> {
2    self.books.get(&id)
3}
4
5// 사용
6match library.find_book(1) {
7    Some(book) => book.display(),
8    None => println!("책을 찾을 수 없습니다"),
9}

2. Result<T, E>

실패할 수 있는 연산의 결과를 표현합니다.

1enum Result<T, E> {
2    Ok(T),   // 성공
3    Err(E),  // 에러
4}

예제:

1fn borrow_book(&mut self, id: u32) -> Result<(), String> {
2    match self.find_book_mut(id) {
3        Some(book) => {
4            if book.is_available() {
5                book.borrow();
6                Ok(())
7            } else {
8                Err("이미 대여 중입니다".to_string())
9            }
10        }
11        None => Err("책을 찾을 수 없습니다".to_string()),
12    }
13}

3. if let 패턴

하나의 패턴만 처리할 때 match보다 간결합니다.

1// match 사용
2match library.find_book(1) {
3    Some(book) => book.display(),
4    None => {},
5}
6
7// if let 사용 (더 간결)
8if let Some(book) = library.find_book(1) {
9    book.display();
10}

소스 코드

1use std::collections::HashMap;
2
3#[derive(Debug)]
4enum Category {
5    Fiction,
6    NonFiction,
7    Science,
8    History,
9}
10
11enum BookStatus {
12    Available,
13    Borrowed { by: String, due_date: String },
14    Reserved { by: String },
15    Lost,
16}
17
18struct Book {
19    id: u32,
20    title: String,
21    author: String,
22    status: BookStatus,
23    category: Category,
24}
25
26impl Book {
27    fn new(id: u32, title: String, author: String, category: Category) -> Book {
28        Book {
29            id,
30            title,
31            author,
32            status: BookStatus::Available,
33            category,
34        }
35    }
36    
37    fn display(&self) {
38        print!("[{}] {} - {} ", self.id, self.title, self.author);
39        match &self.status {
40            BookStatus::Available => println!("(대여 가능)"),
41            BookStatus::Borrowed { by, due_date } => {
42                println!("(대여 중: {} - 반납일: {})", by, due_date)
43            }
44            BookStatus::Reserved { by } => println!("(예약됨: {})", by),
45            BookStatus::Lost => println!("(분실)"),
46        }
47    }
48    
49    fn borrow_book(&mut self, borrower: String, due_date: String) -> bool {
50        match self.status {
51            BookStatus::Available => {
52                self.status = BookStatus::Borrowed { by: borrower, due_date };
53                true
54            }
55            _ => false,
56        }
57    }
58    
59    fn return_book(&mut self) {
60        self.status = BookStatus::Available;
61    }
62    
63    fn get_category_name(&self) -> &str {
64        match self.category {
65            Category::Fiction => "소설",
66            Category::NonFiction => "비소설",
67            Category::Science => "과학",
68            Category::History => "역사",
69        }
70    }
71}
72
73struct Library {
74    books: HashMap<u32, Book>,
75    next_id: u32,
76}
77
78impl Library {
79    fn new() -> Library {
80        Library {
81            books: HashMap::new(),
82            next_id: 1,
83        }
84    }
85    
86    fn add_book(&mut self, title: String, author: String, category: Category) -> u32 {
87        let id = self.next_id;
88        let book = Book::new(id, title, author, category);
89        self.books.insert(id, book);
90        self.next_id += 1;
91        println!("✓ 책 추가 완료 (ID: {})", id);
92        id
93    }
94    
95    // Option 반환: 책이 있을 수도, 없을 수도
96    fn find_book(&self, id: u32) -> Option<&Book> {
97        self.books.get(&id)
98    }
99    
100    fn find_book_mut(&mut self, id: u32) -> Option<&mut Book> {
101        self.books.get_mut(&id)
102    }
103    
104    // Result 반환: 성공(Ok) 또는 실패(Err)
105    fn borrow_book(&mut self, id: u32, borrower: String, due_date: String) -> Result<(), String> {
106        match self.find_book_mut(id) {
107            Some(book) => {
108                if book.borrow_book(borrower.clone(), due_date) {
109                    println!("✓ '{}'을(를) {}님이 대여했습니다.", book.title, borrower);
110                    Ok(())
111                } else {
112                    Err(format!("'{}' 대여 불가 (이미 대여 중)", book.title))
113                }
114            }
115            None => Err(format!("ID {}번 책을 찾을 수 없습니다", id)),
116        }
117    }
118    
119    fn return_book(&mut self, id: u32) -> Result<(), String> {
120        match self.find_book_mut(id) {
121            Some(book) => {
122                book.return_book();
123                println!("✓ '{}' 반납 완료!", book.title);
124                Ok(())
125            }
126            None => Err(format!("ID {}번 책을 찾을 수 없습니다", id)),
127        }
128    }
129    
130    fn list_all(&self) {
131        if self.books.is_empty() {
132            println!("등록된 책이 없습니다.");
133            return;
134        }
135        println!("\n=== 전체 도서 목록 ===");
136        for book in self.books.values() {
137            book.display();
138        }
139    }
140}
141
142fn main() {
143    println!("=== 도서 관리 시스템 v0.4 ===\n");
144    
145    let mut library = Library::new();
146    
147    // 책 추가
148    let id1 = library.add_book(
149        String::from("러스트 프로그래밍"),
150        String::from("스티브"),
151        Category::Science,
152    );
153    let id2 = library.add_book(
154        String::from("해리포터"),
155        String::from("J.K. 롤링"),
156        Category::Fiction,
157    );
158    let id3 = library.add_book(
159        String::from("사피엔스"),
160        String::from("유발 하라리"),
161        Category::History,
162    );
163    
164    library.list_all();
165    
166    println!();
167    
168    // Option 처리: if let 사용
169    if let Some(book) = library.find_book(id1) {
170        println!("\n책 찾기 성공:");
171        book.display();
172        println!("카테고리: {}", book.get_category_name());
173    } else {
174        println!("책을 찾을 수 없습니다");
175    }
176    
177    println!();
178    
179    // Result 처리: match 사용
180    match library.borrow_book(id1, String::from("김철수"), String::from("2026-02-20")) {
181        Ok(_) => println!("대여 처리 완료"),
182        Err(e) => println!("대여 실패: {}", e),
183    }
184    
185    // Result 처리: if let Err 사용
186    if let Err(e) = library.borrow_book(id1, String::from("이영희"), String::from("2026-02-25")) {
187        println!("대여 실패: {}", e);
188    }
189    
190    println!();
191    
192    // 성공하는 대여
193    if let Ok(_) = library.borrow_book(id2, String::from("박민수"), String::from("2026-02-18")) {
194        println!("대여 처리 완료");
195    }
196    
197    library.list_all();
198    
199    println!();
200    
201    // 반납
202    match library.return_book(id1) {
203        Ok(_) => {},
204        Err(e) => println!("반납 실패: {}", e),
205    }
206    
207    // 존재하지 않는 책
208    if let Err(e) = library.return_book(999) {
209        println!("반납 실패: {}", e);
210    }
211    
212    library.list_all();
213}

실행 결과

1=== 도서 관리 시스템 v0.4 ===
2
3✓ 책 추가 완료 (ID: 1)
4✓ 책 추가 완료 (ID: 2)
5✓ 책 추가 완료 (ID: 3)
6
7=== 전체 도서 목록 ===
8[1] 러스트 프로그래밍 - 스티브 (대여 가능)
9[2] 해리포터 - J.K. 롤링 (대여 가능)
10[3] 사피엔스 - 유발 하라리 (대여 가능)
11
12책 찾기 성공:
13[1] 러스트 프로그래밍 - 스티브 (대여 가능)
14카테고리: 과학
15
16✓ '러스트 프로그래밍'을(를) 김철수님이 대여했습니다.
17대여 처리 완료
18대여 실패: '러스트 프로그래밍' 대여 불가 (이미 대여 중)
19
20✓ '해리포터'을(를) 박민수님이 대여했습니다.
21대여 처리 완료
22
23=== 전체 도서 목록 ===
24[1] 러스트 프로그래밍 - 스티브 (대여 중: 김철수 - 반납일: 2026-02-20)
25[2] 해리포터 - J.K. 롤링 (대여 중: 박민수 - 반납일: 2026-02-18)
26[3] 사피엔스 - 유발 하라리 (대여 가능)
27
28✓ '러스트 프로그래밍' 반납 완료!
29반납 실패: ID 999번 책을 찾을 수 없습니다

체크리스트

  • [ ] Option<T>를 반환하는 함수를 만들었습니다
  • [ ] Result<T, E>를 반환하는 함수를 만들었습니다
  • [ ] match로 Option과 Result를 처리했습니다
  • [ ] if let으로 간결하게 패턴 매칭했습니다
  • [ ] HashMap을 사용했습니다

다음 단계

Step 4에서는 소유권과 차용의 핵심 개념을 깊이 있게 배웁니다.

Did you find this helpful? Give it a cheer!