Post

[RUST] 러스트 프로그래밍 공식 가이드(제2판) 9장 요약

에러처리

  • 러스트에는 예외 처리 기능이 없음
  • 러스트 에러의 종류
    • 복구 가능한(recoverable) 에러
      • Result<T, E> 타입
    • 복구 불가능한(unrecoverable) 에러
      • 프로그램을 종료하는 panic! 매크로

panic!으로 복구 불가능한 에러 처리하기

  • 패닉 : 프로그램이 더 이상 정상적으로 실행될 수 없는 심각한 오류 상황에서 발생
    • 실패 메시지를 출력
    • 되감기(unwind)
      • 러스트가 패닉을 발생시킨 함수로부터 스택을 거꾸로 훑어가면서 데이터를 청소한다는 의미
      • 데이터 정리 작업 없이 즉각 종료되는 그만두기(aborting)을 하고 싶다면 아래와 같이 설정
        • Cargo.toml
          • 1
            2
            
            [profile.XXX] // XXX  dev 또는 release    있음
            panic = 'abort'
            
    • 스택을 청소
    • 프로세스 종료
  • 패닉을 일으키는 두 가지 방법
    • 패닉을 일으킬 동작을 하는 것 (예: 배열 끝 부분을 넘어선 접근)
    • panic! 매크로를 명시적으로 호출

panic! 백트레이스 이용하기

  • 백트레이스란?
    • 패닉(panic) 발생 시 어떤 경로로 함수들이 호출되다가 에러가 발생했는지 보여주는 호출 스택 기록을 의미
    • 즉, “에러가 어디서 발생했는지”, “그 지점까지 어떤 경로를 거쳤는지” 알 수 있는 디버깅 도구
  • 백트레이스 출력하기
    • RUST_BACKTRACE=1 cargo run 와 같이 환경변수 RUST_BACKTRACE 를 켜주면 스택 추적이 자세히 나옴

Result로 복구 가능한 에러 처리하기

  • 복구 가능한에러는 Result 열거형으로 표현
    • Result<T, E> 타입
      • 배리언트
        • Ok(T): 성공 시 값 반환
        • Err(E): 실패 시 에러 반환
        • 제네릭 타입이라서, 성공값 타입(T) 와 오류 타입(E)을 원하는 타입으로 정할 수 있음
      • 처리 방식
        • match 구문으로 분기 처리
          • Ok : 성공에 대한 처리
          • Err : 에러에 대한 처리

서로 다른 에러에 대해 매칭하기

다른 종류의 에러를 다른 방식으로 처리하기

  • File::Open 이 반환하는 Err 배리언트 값의 타입은 io::Error 구조체
    • io::Error 구조체는 kind 메서드를 지원 (io::ErrorKind를 얻을 수 있음)
      • io::ErrorKind 는 io 연산으로부터 발생할 수 있는 다양한 종류의 에러를 나타내는 배리언트가 있는 열거형
      • 즉, error.kind() 에 대한 내부 매칭을 할 수 있음
        • 1
          2
          3
          4
          
          let f = File::open("hello.txt");
          let f = match f {
              Ok(file) => file,
              Err(error) => match error.kind() {} // 파일이 존재하지 않을때에 대한 처리
          
  • 위와 같이 match 표현식은 매우 용하지만 매우 원시적이어서 너무 많은 match 가 생길 수 있음

에러 발생 시 패닉을 위한 숏컷: unwrap과 expect

  • match 의 사용은 충분히 잘 작동하지만, 코드가 장황하기도 하고 의도를 항상 잘 전달하는 것은 아님, 따라서 Result<T,E> 는 다양한 특정 작업을 수행하기 위한 도우미 메서드 들을 제공
    • match 구문과 비슷한 구현을 한 숏컷 메서드
      • unwrap()
        • Result 값이 Ok 배리언트라면, 값을 반환
        • Result 값이 Err 배리언트라면 panic! 매크로를 호출로 기본 메시지 출력
      • expect() ( 권장 )
        • Result 값이 Ok 배리언트라면, 값을 반환
        • Result 값이 Err 배리언트라면 panic! 매크로를 호출로 매개변수로 전달한 에러 메시지 출력

에러 전파하기

  • 에러 전파하기 : 어떠한 함수에서 에러가 발생했을 때 에러를 함수 안에서 처리는 대신 이 함수를 호출하는 코드 쪽으로 에러를 반환하여 그쪽에서 수행할 작업을 결정하도록 하는 것을 말함
    • 예로 Result를 반환하여 성공 또는 에러 발생시에 대한 처리를 함수 호출부에서 선택할 수 있도록 할 수 있음
      • matchErr(e) 갈래에서 return Err(e) 를 하는 방식

에러를 전파하기 위한 숏컷: ? 연산자

  • Result<T, E> 타입을 반환하는 함수에서 사용할 수 있음
    • Result의 값이 Ok라면, Ok 안의 값이 얻어짐
    • Result의 값이 Err라면, Err 의 값이 반환
  • match 표현식과 ? 연산자의 차이점
    • ? 연산자를 사용할 때의 에러값은 from 함수를 거침
      • from 함수는 표준 라이브러리 내의 From 트레이트에 정의
      • 어떤 값의 타입을 다른 타입으로 변환하는데 사용
      • ? 연산자가 from 함수를 호출하면, ? 연산자가 얻게되는 에러를 ? 연산자가 사용된 현재 함수의 반환 타입에 정의된 에러타입으로 변환
  • File::open("test.txt")?.read_to_string(&mut username)?; 과 같이 호출을 연결하여 사용할 수 있음
    • 만약 File::open("test.txt")? 에서 에러가 발생하면 read_to_string 은 실행되지 않고, 파일 열기에 대한 Err(e) 를 반환
  • ?연산자는 어떤 함수가 다양한 종류의 에러로 인해 실패할 수 있지만, 모든 에러를 하나의 에러 타입으로 반환할 때 유용
  • ?연산자는 많은 양의 보일러플레이트(match, if let)를 제거해주고 함수의 구현을 더 단순하게 만들어줌
  • ? 연산자가 사용될 수 있는 곳
    • ??가 사용된 값과 호환 가능한 반환 타입을 가진 함수에서만 사용될 수 있음
      • Result<T, E> 또는 Option<T> 에서 사용할 수 있음
      • ? 는 오류가 발생했을 때 함수에서 조기에 Err(...) 혹은 None 을 반환하는 역할을 함

Panic!이냐, panic!이 아니냐, 그것이 문제로다

예제, 프로토타입 코드, 그리고 테스트

  • 에러를 어떻게 처리할지 결정할 준비가 되기 전이라면, unwrapexpect 메서드가 프로토타이핑을 할 떄 매우 편리
  • 테스트 내에서 메서드 호출이 실패한다면, 해당 메서드가 테스트 중인 기능이 아니더라도 전체 테스트를 실패시키도록 함

여러분이 컴파일러보다 더 많은 정보를 가지고 있을 때

  • ResultOk 값을 가지고 있을 거라 확신할 만한 논리적 근거가 있지만, 컴파일러가 그 논리를 이해할 수 없는 경우라면, unwrap 혹은 expect를 호출하는 것이 적절할 수 있음

에러 처리를 위한 가이드라인

  • 복구 가능한 에러는 Result로 처리
  • 복구 불가능한 에러는 panic! 으로 처리

유효성을 위한 커스텀 타입 생성하기

  • 새로운 타입을 만들어서 그 타입의 인스턴스를 생성하는 함수에서 유효성을 확인하는 방식으로 유효성 확인을 모든 곳에서 반복하지 않게 할 수 있음
    • 함수가 새로운 타입을 시그니처에 사용하여 받은 값을 자신있게 사용할 수 있어 안전
This post is licensed under CC BY 4.0 by the author.