Rust는 명시적인 오류 관리를 기반으로 합니다: 예외가 없고, 대신 Result<T, E>라는 반환값을 사용합니다. 이는 코드의 안전성과 예측 가능성을 보장합니다.
문제의 역사:
많은 언어들이 예외 처리 경로를 따랐고, 이는 예기치 않은 상황을 초래하며 런타임에서 예외를 명시적으로 처리해야 했습니다. Rust는 처음부터 타입을 통한 제어에 중점을 두었고, 모든 오류는 함수의 시그니처의 일부가 되어야 합니다.
문제:
주요 과제는 오류가 무시되거나 숨겨지지 않고, "크래시" 함수가 없으면서도 코드가 간결하고 읽기 쉬워야 한다는 것입니다. 올바르게 오류를 위로 전달하면서도 오류 유형에 대한 정보를 잃지 않고 논리를 복잡하게 만들지 않아야 합니다.
해결책:
핵심 도구는 Result<T, E> 타입과 ? 연산자이며, 이는 자동으로 결과를 "언래핑"합니다: 오류가 발생하면 함수에서 즉시 반환되고, 성공적일 경우 값을 반환합니다.
코드 예시:
fn read_number(file: &str) -> Result<i32, std::io::Error> { let content = std::fs::read_to_string(file)?; let num: i32 = content.trim().parse()?; Ok(num) }
주요 특징:
? 연산자는 중첩을 단순화할 수 있음 (if-let/unwrap/expect 제거).map_err() 등 여러 메서드 사용)Unit (void)를 반환하는 함수에서 ?를 사용할 수 있나요?
아니요, ? 연산자는 Result나 Option을 반환하는 함수 내에서만 사용 가능합니다. 함수가 ()를 반환하면 ?를 사용할 수 없습니다.
여러 ? 호출에서 오류 타입이 다르면 어떻게 되나요?
컴파일 오류가 발생합니다: 오류 타입은 명확히 정의되어야 합니다. 모든 오류를 .map_err()를 사용하여 동일한 타입으로 변환하거나 thiserror를 사용하거나 API 수준에서 enum 래퍼를 정의해야 합니다. 예시:
fn foo() -> Result<_, MyError> { let a = bar()?; let b = baz().map_err(MyError::Baz)?; Ok(…) }
내부 논리에서 .unwrap()이 위험한 이유는 무엇인가요?
"주요" 코드에서 unwrap()을 자유롭게 사용할 수 있다는 잘못된 믿음이 퍼져 있습니다. 실제로 작은 보이지 않는 오류도 실행 중 패닉을 초래할 수 있으며 — 프로그램의 안전성이 손상됩니다.
unwrap/expect로 잡는 것? 없이 각 오류를 수동으로 처리할 때 과도한 중첩프로덕션 코드에서 빠른 디버깅 후 많은 unwrap()를 남겼습니다. 그 결과 사용자의 잘못된 입력으로 인한 파싱 오류로 앱이 중단되었습니다.
장점:
단점:
전체 Result<T, E> 스택을 사용하여 오류를 명시적으로 설명하고, ?와 .map_err()만을 사용하여 모든 실패 정보를 보존했습니다.
장점:
단점: