輸出

輸出「Hello World」


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

好,這很簡單。太棒了,進入下一個主題。

使用 println!

您可以使用 println! 巨集輸出您喜歡的所有內容。這個巨集有一些非常強大的功能,但也有特殊的語法。它預期您將字串字面值寫成第一個參數,這個字串字面值包含佔位符,這些佔位符將由緊接著後續參數的值填入。

例如,


#![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 進行,讓使用者和其它工具更容易將它們的輸出導向至檔案或更多工具。

在 Rust 中,這是透過 println!eprintln! 達成,前者會印到 stdout,後者則印到 stderr


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

關於印出效能的備註

印到終端機的效能令人驚訝的低落!如果你在迴圈內呼叫類似 println! 的東西,在原本快速的程式中可能會輕易造成瓶頸。可有兩件事能加速這件事。

首先,你可能想要減少實際「快取」到終端機的寫入次數。println! 會告知系統每次快取到終端機,因為通常會印出每一行。如果你不需要這樣,你可以用 BufWriter 包覆你的 stdout 句柄,預設緩衝大小至 8 kB。(你仍然可以在想要立即印出的時候呼叫 BufWriter.flush()。)


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

let stdout = io::stdout(); // get the global stdout entity
let mut handle = io::BufWriter::new(stdout); // optional: wrap that handle in a buffer
writeln!(handle, "foo: {}", 42); // add `?` if you care about errors here
}

其次,鎖定 stdout(或 stderr)並使用 writeln! 直接印到它裡頭會很有幫助。這將可避免系統反覆鎖定和解鎖 stdout


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

let stdout = io::stdout(); // get the global stdout entity
let mut handle = stdout.lock(); // acquire a lock on it
writeln!(handle, "foo: {}", 42); // add `?` if you care about errors here
}

你也可以同時採用這兩種方式。

顯示進度條

有些 CLI 應用程式執行的時間不到一秒,有些則需要幾分鐘或幾小時。如果你正在撰寫後者的程式,你或許會想要讓使用者知道正在執行某些事。為此,你應該嘗試印出有用的狀態更新,理想上以容易閱讀的形式。

使用 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! 相同,但你可以指定訊息的重要性。你通常可以使用以下層級:errorwarninfodebugtraceerror 的優先順序最高,trace 最低)。

要將簡單記錄加入你的應用程式,你需要兩個東西:log crate(它包含命名自記錄層級的巨集)和實際將記錄輸出寫入某個實用位置的介接器。能夠使用記錄介接器非常靈活:例如,你可以使用它們將記錄寫入終端機,也可以寫入 syslog,或是寫入集中式記錄伺服器。

由於我們現在只關注撰寫 CLI 應用程式,因此一個好用的介接器是 env_logger。它稱為「env」記錄器,是因為你可以使用環境變數來指定應用程式的哪些部分你要記錄(以及你要記錄的層級)。它會在記錄訊息中加上時間戳記和記錄訊息來自的模組。由於函式庫也可以使用 log,因此你也可以輕易設定他們的記錄輸出。

以下是快速範例

use log::{info, warn};

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

假設你的檔案為 src/bin/output-log.rs,在 Linux 和 macOS 上,你可以這樣執行

$ 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!

在 Windows PowerShell 中,你可以這樣執行

$ $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!

在 Windows 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 的替代方案或擴充功能。如果你知道你的應用程式有許多記錄需要處理,請務必檢閱它們,並讓使用者的生活更輕鬆。