출력

“Hello World” 출력하기

#![allow(unused)]
fn main() {
println!("Hello World");
}

쉽습니다. 좋아요, 다음 주제로 넘어가죠.

println! 사용하기

여러분이 출력하고 싶은 모든 것을 println! 매크로를 통해 출력할 수 있습니다. 이 매크로는 놀라운 기능을 갖추고 있으며, 특수한 문법도 있습니다. println! 매크로에는 첫 파라미터로 문자열 리터럴을 전달해야 합니다. 이 파라미터는 추가 인자의 값으로 채워질 플레이스홀더(placeholder)를 포함해야 합니다.

예를 들어:

#![allow(unused)]
fn main() {
let x = 42;
println!("My lucky number is {}.", x);
}

위 코드는 아래와 같은 출력을 냅니다:

My lucky number is 42.

위 코드에서 문자열에 있는 중괄호({})는 플레이스홀더 중 하나로, 주어진 값을 사람이 읽을 수 있는 형태로 출력하는 기본 플레이스홀더입니다. 숫자와 문자열에 대해 아주 잘 동작하지만, 모든 타입에 대해 동작하지는 않습니다. 이는 “디버그 표현“이 있는 이유이기도 한데, {:?}처럼 플레이스홀더의 괄호를 채워서 사용할 수 있습니다.

예를 들어:

#![allow(unused)]
fn main() {
let xs = vec![1, 2, 3];
println!("The list is: {:?}", xs);
}

위 코드는 아래와 같은 출력을 냅니다:

The list is: [1, 2, 3]

만약 자기만의 데이터 타입을 디버깅과 로깅을 위해 출력 가능하게 만들고 싶다면, 대부분의 경우 타입 정의 위에 #[derive(Debug)]를 추가하면 됩니다.

에러 출력하기

에러는 stderr를 통해 출력해야 합니다. 그래야 사용자나 다른 프로그램이 파이프를 통해 프로그램의 출력을 파일이나 다른 프로그램에 전달하기 쉬워집니다.

러스트에서는 이를 println!eprintln!으로 구현할 수 있으며, 전자는 stdout으로 출력하고, 후자는 stderr로 출력합니다.

#![allow(unused)]
fn main() {
println!("This is information");
eprintln!("This is an error! :(");
}

출력 성능에 대한 참고사항

터미널에 뭔가를 출력하는 것은 끔찍하게 느립니다! 만약 루프에서 println!을 사용한다면, 빠른 프로그램에서도 쉽게 보틀넥이 될 것입니다. 성능을 높이기 위한 두 가지 대응 방법이 있습니다.

첫 번째는, 터미널을 실제로 “플러시(flush)“하는 쓰기 횟수를 줄이는 방법입니다. println!은 보통 새로운 라인에 내용을 출력하기 위해 시스템에게 매번 터미널을 플러시해달라고 요청합니다. 그럴 필요가 없다면, stdoutBufWriter에서 다루도록 래핑하면 됩니다. BufWriter는 기본적으로 최대 8kB까지 버퍼링 할 수 있습니다. (즉시 출력을 하고 싶을 때는 BufWriter.flush()를 호출하면 됩니다.)

#![allow(unused)]
fn main() {
use std::io::{self, Write};

let stdout = io::stdout(); // 글로벌 stdout 엔티티를 얻는다
let mut handle = io::BufWriter::new(stdout); // 선택사항: 버퍼로 다루도록 감싼다
writeln!(handle, "foo: {}", 42); // 에러가 신경쓰인다면 여기에 `?`를 추가한다
}

두 번째는, stdout (또는 stderr)에 대한 락(lock)을 획득하고 writeln!을 이용해 직접 출력하는 방법입니다. 이렇게 하면 시스템이 stdout을 매번 다시 잠그고 해제하는 것을 방지할 수 있습니다.

#![allow(unused)]
fn main() {
use std::io::{self, Write};

let stdout = io::stdout(); // 글로벌 stdout 엔티티를 얻는다
let mut handle = stdout.lock(); // 락을 얻는다
writeln!(handle, "foo: {}", 42);  // 에러가 신경쓰인다면 여기에 `?`를 추가한다
}

두 방법을 함께 사용할 수도 있습니다.

프로그래스 바 보여주기

어떤 CLI 애플리케이션은 1초 이내에 실행되기도 하지만, 어떤 애플리케이션은 수 분, 수 시간을 소요하기도 합니다. 시간이 오래 걸리는 프로그램을 작성한다면, 사용자에게 프로그램이 동작하고 있다는 것을 보여주고 싶을 수 있습니다. 이를 위해서는 상태가 업데이트되고 있다는 정보를 사용하기 쉬운 형태로 출력해줘야 합니다.

indicatif 크레이트를 사용하면 프로그램에 프로그래스 바와 작은 스피너를 추가할 수 있습니다. 여기 간단한 예시가 있습니다:

fn main() {
    let pb = indicatif::ProgressBar::new(100);
    for i in 0..100 {
        do_hard_work();
        pb.println(format!("[+] finished #{}", i));
        pb.inc(1);
    }
    pb.finish_with_message("done");
}

더 자세한 정보는 문서예시를 참고하세요.

로그

프로그램에서 무슨 일이 일어나는지 보다 쉽게 이해하기 위해 로그 구문을 추가하고 싶을 수 있습니다. 보통 애플리케이션을 작성할 때 쉽게 로그를 남길 수 있습니다. 로그는 반년 뒤에 프로그램을 다시 실행할 때 대단히 유용해집니다. 한편, 로그를 남기는 것은 메시지의 중요도를 명시하는 것만 빼면 println!을 사용하는 것과 같습니다. 주로 사용하는 로그 레벨에는 error, warn, info, debug, trace 가 있습니다. (error 는 중요도가 가장 높고, trace 는 가장 낮습니다.)

애플리케이션에 간단한 로그를 남기기 위해서는 log 크레이트 (로그 레벨의 이름을 딴 매크로 포함)와 로그 출력을 작성할 때 유용한 어댑터가 필요합니다. 로그 어댑터는 매우 유연하게 상용할 수 있습니다. 예를 들어, 어댑터를 이용해 터미널이 아닌 syslog에 로그를 남길 수도 있고, 아니면 중앙 로그 서버에 로그를 남길 수도 있습니다.

우리는 CLI 애플리케이션을 작성하는 데만 집중하고 있으므로, 당장 사용하기 쉬운 어댑터는 env_logger입니다. env_logger를 사용하면 애플리케이션의 어느 부분에 어떤 레벨의 로그를 남길지 환경 변수를 통해 명시할 수 있기 때문에 이를 “env” 로거라고 합니다. env_logger는 로그 메시지 앞에 타임스탬프와 로그를 남긴 모듈의 이름을 붙입니다. 라이브러리도 log를 사용할 수 있기 때문에 로그 출력을 쉽게 구성할 수 있습니다.

여기 간단한 예시가 있습니다:

use log::{info, warn};

fn main() {
    env_logger::init();
    info!("starting up");
    warn!("oops, nothing implemented!");
}

리눅스나 macOS에서 위 코드를 src/bin/output-log.rs 파일로 작성했다면, 아래와 같이 실행할 수 있습니다:

$ env RUST_LOG=info cargo run --bin output-log
    Finished dev [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/output-log`
[2018-11-30T20:25:52Z INFO  output_log] starting up
[2018-11-30T20:25:52Z WARN  output_log] oops, nothing implemented!

윈도우즈 파워셸에서는 아래와 같이 실행할 수 있습니다:

$ $env:RUST_LOG="info"
$ cargo run --bin output-log
    Finished dev [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/output-log.exe`
[2018-11-30T20:25:52Z INFO  output_log] starting up
[2018-11-30T20:25:52Z WARN  output_log] oops, nothing implemented!

윈도우즈 CMD에서는 아래와 같이 실행합니다:

$ set RUST_LOG=info
$ cargo run --bin output-log
    Finished dev [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/output-log.exe`
[2018-11-30T20:25:52Z INFO  output_log] starting up
[2018-11-30T20:25:52Z WARN  output_log] oops, nothing implemented!

RUST_LOG는 로그 설정에 사용하는 환경 변수의 이름입니다. env_logger에는 빌더가 있기 때문에 프로그래밍적으로 로그를 설정할 수도 있으며, 가령 기본적으로 info 레벨 메시지가 출력됩니다.

이외에도 많은 로그 어댑터가 있으며, log를 대체하거나 확장할 수 있는 어댑터들이 있습니다. 만약 애플리케이션에 많은 양의 로그가 필요할 것 같다면 다른 것들을 검토해보고 사용자의 삶의 질을 높여주세요.