输出

打印 “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.

上面字符串中的花括号(‘{}’)就是占位符中的一种。这是默认的占位符类型, 它尝试以人类可读的方式打印出给定的参数的值。对于数字和字符串,这很好用, 但并不是所有的类型都可行。这就是为什么还有一个 “debug representation”, 你可以使用这个占位符来调用它 {:?}

比如,

#![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 crate 来为你的程序添加进度条,这是一个简单的例子:

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");
}

细节可查看 indicatif 文档示例

日志

为了更方便了解到我们的程序做了什么,我们需要给它添加上一些日志语句,这很简单。 但在长时间后,例半年后再运行这个程序时,日志就变得非常有用了。在某些方面来说, 日志的使用方法同 println 类似,只是它可以指定消息的重要性(级别)。 通常可以使用的级别包括 error, warn, info, debug, and traceerror 优先级最高,trace 最低)。

只需这两样东西,你就可以给你的程序添加简单的日志功能: Log 箱(其中包含以日志级别命名的宏)和一个 adapter, 它会将日志写到有用的地方。日志适配器的使用是十分灵活的: 例如,你可以不仅将日志写入终端,同时也可写入 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 是设置 log 的环境变量名。 env_logger 还包含一个构建器,因此你可以以编程的方式调整这些设置, 例如,还默认显示 info 级别的日志。

还有很多可选的日志适配器,以及日志库或其扩展。 如果你确定你的应用将生成很多日志,请务必查看它们,以便解决发现的问题。