표준 출력 대신 표준 에러로 에러 메시지 작성하기

이 시점에서는 터미널로 출력되는 모든 것이 println! 매크로를 사용하여 작성되고 있는 상태입니다. 대부분의 터미널에는 두 종류의 출력이 있습니다: 범용적인 정보를 위한 표준 출력 (standard output) (stdout)과 에러 메시지를 위한 표준 에러 (standard error) (stderr) 두 가지죠. 이러한 구분은 사용자로 하여금 성공한 프로그램의 출력값을 파일로 향하게끔 하지만 에러 메시지는 여전히 화면에 나타나도록 해줄 수 있습니다.

println! 매크로는 표준 출력으로의 출력 기능만 있으므로, 표준 에러로 출력하기 위해서는 다른 무언가를 사용해야 합니다.

에러가 기록되었는지 검사하기

먼저 minigrep이 출력하는 내용들이 현재 표준 에러 쪽에 출력하고 싶은 에러 메시지를 포함하여 어떤 식으로 표준 출력에 기록되는지 관찰해 봅시다. 의도적으로 에러를 발생시키면서 표준 출력 스트림을 파일 쪽으로 리디렉션하여 이를 확인할 것입니다. 표준 에러 스트림은 리디렉션하지 않을 것이므로 표준 에러 쪽으로 보내진 내용들은 계속 화면에 나타날 것입니다.

커맨드 라인 프로그램은 표준 에러 스트림 쪽으로 에러 메시지를 보내야 하므로 표준 출력 스트림이 파일로 리디렉션되더라도 여전히 에러 메시지는 화면에서 볼 수 있습니다. 이 프로그램은 현재 잘 제대로 동작하지 않습니다: 에러 메시지가 대신 파일 쪽에 저장되는 것을 막 보려는 참입니다!

이 동작을 확인해 보기 위해서 프로그램을 >과 파일 경로 output.txt과 함께 실행해 보려 하는데, 이 파일 경로는 표준 출력 스트림이 리디렉션될 곳입니다. 아무런 인수를 넣지 않을 것인데, 이는 에러를 발생시켜야 합니다:

$ cargo run > output.txt

> 문법은 셸에게 표준 출력의 내용을 화면 대신 output.txt에 작성하라고 알려줍니다. 화면 출력되리라 기대되었던 에러 메시지는 보이지 않으므로, 이는 결국 파일 안으로 들어갔음에 틀림없다는 의미입니다. 아래가 output.txt이 담고 있는 내용입니다:

Problem parsing arguments: not enough arguments

네, 에러 메시지가 표준 출력에 기록되고 있네요. 이런 종류의 에러 메시지는 표준 에러로 출력되게 함으로써 성공적인 실행으로부터 나온 데이터만 파일로 향하게 만드는 것이 훨씬 유용합니다. 그렇게 바꿔보겠습니다.

표준 에러로 에러 출력하기

에러 메시지 출력 방식을 변경하기 위해 예제 12-24의 코드를 사용해 보겠습니다. 이 장에서 앞서 했던 리팩터링 덕분에 에러 메시지를 출력하는 모든 코드는 단 하나의 함수 main 안에 있습니다. 표준 라이브러리는 표준 에러 스트림으로 출력하는 eprintln! 매크로를 제공하므로, 에러 출력을 위해 println!을 호출하고 있는 두 군데를 eprintln!로 바꿔봅시다.

파일명: src/main.rs

use std::env;
use std::process;

use minigrep::Config;

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = Config::build(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {err}");
        process::exit(1);
    });

    if let Err(e) = minigrep::run(config) {
        eprintln!("Application error: {e}");
        process::exit(1);
    }
}

예제 12-24: eprintln!를 사용하여 표준 출력 대신 표준 에러로 에러 메시지 작성하기

이제 동일한 방식, 즉 아무런 인수 없이 >로 표준 출력을 리디렉션하여 프로그램을 다시 실행해 봅시다:

$ cargo run > output.txt
Problem parsing arguments: not enough arguments

이제는 에러가 화면에 보여지고 output.txt에는 아무것도 없는데, 이것이 커맨드 라인 프로그램에 대해 기대한 동작입니다.

다시 한번 프로그램을 시키는데 이번에는 다음과 같이 에러를 내지 않는 인수를 사용하고 표준 출력을 파일로 리디렉션 시켜봅시다:

$ cargo run -- to poem.txt > output.txt

터미널에는 아무런 출력을 볼 수 없고, output.txt에는 결과물이 담겨 있을 것입니다:

파일명: output.txt

Are you nobody, too?
How dreary to be somebody!

이는 이제 성공적인 출력에 대해서는 표준 출력을, 에러 출력에 대해서는 표준 에러를 적절히 사용하고 있음을 입증합니다.

정리

이번 장에서는 여태껏 배운 몇몇 주요 개념들을 재점검하고 러스트에서의 통상적인 입출력 연산이 수행되는 방식을 다루었습니다. 커맨드 라인 인수, 파일, 환경 변수, 그리고 에러 출력을 위한 eprintln! 매크로를 사용함으로써, 여러분은 이제 커맨드 라인 애플리케이션을 작성할 준비가 되었습니다. 이전 장의 개념들과의 조합을 통하여 여러분의 코드는 잘 조직되고, 적절한 데이터 구조에 효율적으로 데이터를 저장하고, 에러를 잘 처리하고, 잘 테스트하게 될 것입니다.

다음으로는 함수형 언어로부터 영향을 받은 러스트의 기능 몇 가지를 탐구해 보겠습니다: 바로 클로저와 반복자죠.