프로그래밍시스템 프로그래머

Result<T, E>를 통해 오류 관리를 어떻게 수행하는지 설명하고, 코드의 안전성과 가독성을 해치지 않으면서 올바르게 물음표 연산자(?)를 사용하는 방법을 설명하십시오.

Hintsage AI 어시스턴트로 면접 통과

답변.

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)를 반환하는 함수에서 ?를 사용할 수 있나요?

아니요, ? 연산자는 ResultOption을 반환하는 함수 내에서만 사용 가능합니다. 함수가 ()를 반환하면 ?를 사용할 수 없습니다.

여러 ? 호출에서 오류 타입이 다르면 어떻게 되나요?

컴파일 오류가 발생합니다: 오류 타입은 명확히 정의되어야 합니다. 모든 오류를 .map_err()를 사용하여 동일한 타입으로 변환하거나 thiserror를 사용하거나 API 수준에서 enum 래퍼를 정의해야 합니다. 예시:

fn foo() -> Result<_, MyError> { let a = bar()?; let b = baz().map_err(MyError::Baz)?; Ok() }

내부 논리에서 .unwrap()이 위험한 이유는 무엇인가요?

"주요" 코드에서 unwrap()을 자유롭게 사용할 수 있다는 잘못된 믿음이 퍼져 있습니다. 실제로 작은 보이지 않는 오류도 실행 중 패닉을 초래할 수 있으며 — 프로그램의 안전성이 손상됩니다.

전형적인 오류와 안티 패턴

  • 내부 논리에서 모든 오류를 unwrap/expect로 잡는 것
  • 오류에 대한 컨텍스트 손실 (map_err(|_| …)에서 원래 정보를 보존하지 않고 오류를 감싸는 경우)
  • ? 없이 각 오류를 수동으로 처리할 때 과도한 중첩

실제 사례

부정적인 사례

프로덕션 코드에서 빠른 디버깅 후 많은 unwrap()를 남겼습니다. 그 결과 사용자의 잘못된 입력으로 인한 파싱 오류로 앱이 중단되었습니다.

장점:

  • 빠른 디버깅

단점:

  • 잠재적인 애플리케이션 크래시, 원인 찾기 어려움

긍정적인 사례

전체 Result<T, E> 스택을 사용하여 오류를 명시적으로 설명하고, ?.map_err()만을 사용하여 모든 실패 정보를 보존했습니다.

장점:

  • 디버깅 용이성, 예측 가능한 행동

단점:

  • 약간 더 많은 "잡음" 오류 타입들