Rust Result
Option이 없을 수도 있는 값을 나타낸다면, Result 타입은 실패할 수도 있는 작업을 나타내요. 그리고 단순히 값이 없는 것과 달리, 실패에는 보통 이유가 함께 붙어 있어요.
Result는 이렇게 생겼어요
섹션 제목: “Result는 이렇게 생겼어요”Result도 표준 라이브러리에 있는 열거형이에요. 이번에는 두 배리언트 모두 데이터를 담고 있어요.
enum Result<T, E> { Ok(T), // 성공, 값을 담고 있어요 Err(E), // 실패, 에러를 담고 있어요}실패할 수 있는 함수
섹션 제목: “실패할 수 있는 함수”타입 변환 페이지에서 만났던 .parse()가 좋은 실제 예예요. 텍스트를 숫자로 바꾸는 작업은 텍스트가 숫자가 아니면 실패할 수 있으니까요.
fn main() { let good: Result<i32, std::num::ParseIntError> = "42".parse(); let bad: Result<i32, std::num::ParseIntError> = "abc".parse();
println!("{:?}", good); println!("{:?}", bad);}실행 결과:
Ok(42)Err(ParseIntError { kind: InvalidDigit })InvalidDigit은 “숫자가 아닌 글자가 들어 있다”는 뜻이에요.
match로 Result 다루기
섹션 제목: “match로 Result 다루기”fn main() { let input = "42";
match input.parse::<i32>() { Ok(number) => println!("변환 결과: {number}"), Err(e) => println!("변환 실패: {e}"), }}match로 Option을 다룰 때와 모양이 같아요. 다만 Err 갈래에서는 “아무것도 없었다”가 아니라, 살펴보거나 알려 줄 수 있는 실제 에러 값을 받아요.
Result를 반환하는 함수 직접 만들기
섹션 제목: “Result를 반환하는 함수 직접 만들기”fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("0으로 나눌 수 없어요")) } else { Ok(a / b) }}
fn main() { match divide(10.0, 2.0) { Ok(result) => println!("결과: {result}"), Err(e) => println!("에러: {e}"), }
match divide(10.0, 0.0) { Ok(result) => println!("결과: {result}"), Err(e) => println!("에러: {e}"), }}실행 결과:
결과: 5에러: 0으로 나눌 수 없어요Result<f64, String>을 반환하면 모든 호출자가 실패할 가능성을 처리해야 해요. 0으로 나눈 결과를 실수로 평범한 숫자처럼 다룰 방법이 아예 없어요.
? 연산자
섹션 제목: “? 연산자”실패할 수 있는 작업을 여러 개 이어서 하다 보면 match 블록이 깊게 중첩돼요. ? 연산자를 쓰면 이걸 평평하게 펼 수 있어요. ?의 뜻은 “이게 Err이면 지금 함수에서 그 에러를 바로 반환해. 아니면 Ok 안의 값을 줘”예요.
fn parse_and_double(input: &str) -> Result<i32, std::num::ParseIntError> { let number = input.parse::<i32>()?; // 변환에 실패하면 여기서 바로 반환해요 Ok(number * 2)}
fn main() { println!("{:?}", parse_and_double("21")); println!("{:?}", parse_and_double("oops"));}실행 결과:
Ok(42)Err(ParseIntError { kind: InvalidDigit })?가 없다면 그 줄에도 number를 꺼내거나 에러와 함께 일찍 빠져나가기 위한 match가 따로 필요했을 거예요. ?는 그 일을 글자 하나로 해 줘요. 단, ?는 그 자신도 Result(또는 Option)를 반환하는 함수 안에서만 쓸 수 있어요. 그래야 에러를 돌려보낼 곳이 생기니까요.
Result와 panic!
섹션 제목: “Result와 panic!”Rust에는 프로그램을 즉시 멈추는 panic!도 있어요. 다음 페이지에서 더 자세히 다뤄요. 기본 원칙은 이래요. 잘못된 입력이나 없는 파일처럼 호출자가 처리하고 싶어 할 만한 실패에는 Result를 쓰고, 계속 진행하는 것이 멈추는 것보다 더 나쁜, 진짜 버그를 뜻하는 상황에만 panic!을 쓰세요.
함수가 여러 가지 방식으로 실패할 수 있게 되면, 그냥 String 에러로는 부족해져요. 사용자 정의 에러 페이지에서는 나만의 에러 타입을 정의하는 법을, anyhow와 thiserror 페이지에서는 반복되는 코드를 줄이려고 대부분의 Rust 프로젝트가 쓰는 크레이트를 소개해요.
연습 문제
섹션 제목: “연습 문제”1. 나이 확인하기
섹션 제목: “1. 나이 확인하기”fn parse_age(input: &str) -> Result<u32, String>을 작성해 보세요. 텍스트가 숫자가 아니거나, 숫자가 150보다 크면 도움이 되는 메시지와 함께 Err를 반환해야 해요. "34", "abc", "200"으로 테스트해 보세요. 힌트: input.parse::<u32>()를 match하고, Ok 갈래 안에서 if로 크기를 확인하세요.
정답 보기
fn parse_age(input: &str) -> Result<u32, String> { match input.parse::<u32>() { Ok(age) => { if age > 150 { Err(format!("{age}살은 실제 나이로 보기 어려워요")) } else { Ok(age) } } Err(_) => Err(format!("'{input}'은(는) 숫자가 아니에요")), }}
fn main() { for input in ["34", "abc", "200"] { match parse_age(input) { Ok(age) => println!("나이: {age}"), Err(e) => println!("에러: {e}"), } }}실행 결과:
나이: 34에러: 'abc'은(는) 숫자가 아니에요에러: 200살은 실제 나이로 보기 어려워요2. ?로 두 수 더하기
섹션 제목: “2. ?로 두 수 더하기”두 문자열을 숫자로 변환해서 합을 반환하는 fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError>를 작성해 보세요. match 대신 ?를 쓰세요. ("10", "20")과 ("10", "스물")의 결과를 출력하세요. 힌트: parse 하나에 ? 하나씩 필요하고, 마지막 줄에서는 합을 Ok(...)로 감싸세요.
정답 보기
fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> { let x = a.parse::<i32>()?; let y = b.parse::<i32>()?; Ok(x + y)}
fn main() { println!("{:?}", add_strings("10", "20")); println!("{:?}", add_strings("10", "스물"));}실행 결과:
Ok(30)Err(ParseIntError { kind: InvalidDigit })Result<T, E>는 성공(Ok)하거나 에러 값과 함께 실패(Err)할 수 있는 작업을 나타내요.match로 두 경우를 명시적으로 처리해요.Option과 같은 패턴이에요.?연산자는Err를 자동으로 일찍 반환해 줘서, 실패할 수 있는 호출마다match를 쓸 필요가 없어요.- 호출자가 충분히 복구할 수 있는 에러라면
panic!보다Result를 쓰세요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. Result<T, E>의 두 배리언트는 무엇인가요?
2. Result를 반환하는 함수 안에서 ? 연산자는 무슨 일을 하나요?
3. 이 페이지에 따르면, 언제 panic!보다 Result를 써야 하나요?
4. 이 페이지의 divide 예제에서 divide(10.0, 0.0)은 무엇을 반환하나요?
5. ? 연산자는 왜 Result(또는 Option)를 반환하는 함수 안에서만 쓸 수 있나요?
점수: 0 / 5
더 알아보기
섹션 제목: “더 알아보기”- Result로 복구 가능한 에러 처리하기 — The Rust Programming Language 한국어판: 공식 책의
Result장이에요. 파일을 여는 예제와 함께unwrap,expect,?연산자를 설명해요. std::result— 표준 라이브러리 문서 (영어):Result를 다루는 방법을 한눈에 정리했어요.map_err,unwrap_or,ok같은 메서드도 나와요.- Result — Rust by Example (영어):
Result를 처리하고main에서 반환하는 실행 예제예요. - The ? operator — Rust by Example (영어):
?가 중첩된match블록을 어떻게 대신하는지 짧게 집중해서 보여 줘요.