Rust

(Rust) 에러 전파 연산자 정리

lgvv 2026. 9. 9. 00:25

(Rust) 에러 전파 연산자 정리

Rust의 ?는 성공 값은 꺼내고 실패 값은 현재 함수의 반환값으로 바꾸어 호출자에게 전달하는 연산자.

 

write!와 ?의 흐름 차이

write!(formatter, ", ")?;

 

write!의 반환 타입은 출력 대상의 write_fmt가 정함. 위 코드처럼 fmt::Formatter에 쓰면 fmt::Result, std::io::Write 구현에 쓰면 보통 io::Result를 반환함.

  • Ok(())이면 다음 코드로 진행함
  • Err(error)이면 현재 함수를 끝내고 에러를 호출자에게 반환함

 

match로 작성해보기

Result에 사용한 ?는 단순화하면 아래 흐름으로 볼 수 있음.

match write!(formatter, ", ") {
    Ok(()) => {}
    Err(error) => return Err(From::from(error)),
}

 

From::from이 보이는 이유는 원래 에러와 함수가 반환하는 에러의 타입이 다를 수 있기 때문.

실제 언어 정의는 Try와 FromResidual을 사용하지만, Result의 일반적인 동작은 이 형태로 이해할 수 있음.

 

Display 구현

Display::fmt는 fmt::Result를 반환하므로 같은 타입을 반환하는 write! 뒤에 ?를 붙일 수 있음.

use std::fmt;

struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "({}, ", self.x)?;
        write!(formatter, "{})", self.y)
    }
}

 

첫 번째 write!가 실패하면 두 번째 호출은 실행되지 않음.
성공하면 다음 줄로 넘어가고, 마지막 write!의 결과를 그대로 반환함.

 

?를 쓰는 함수의 반환 타입은 전달할 실패 값을 받을 수 있어야 함.
Result 안에서는 Result, Option 안에서는 Option을 전파하며 서로 임의로 섞이지 않음.

 

use std::num::ParseIntError;

fn parse_number(text: &str) -> Result<i32, ParseIntError> {
    let value = text.parse::<i32>()?;
    Ok(value)
}

 

text.parse()가 Err를 반환하면 parse_number도 그 지점에서 끝남.
Ok라면 내부의 i32를 value에 넣고 다음 줄을 실행.


(참고)