输出
打印 “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 表示符”的原因,你可以通过填充占位符的大括号,就像这样:{:?}
来获得debug 表示符。
例如:
#![allow(unused)] fn main() { let xs = vec![1, 2, 3]; println!("The list is: {:?}", xs); }
会打印
The list is: [1, 2, 3]
如果想要自己的数据类型可打印以调试和日志记录,大多数情况下可以在其定义上方添加 #[derive(Debug)]
。
打印 errors
应该通过 stderr
完成错误打印,使得用户和其他工具更容易将其输出传输到文件或更多工具。
在 Rust 中这是用 println!
和 eprintln!
实现的,前者打印到 stdout
,后者打印到 stderr
。
#![allow(unused)] fn main() { println!("This is information"); eprintln!("This is an error! :("); }
关于打印的性能的注意事项
打印到终端是非常慢的!如果你在循环中调用 println!
之类的,它会很容易成为其他快速型程序的瓶颈。为了提升速度,你可以做两件事。
首先,你可能需要减少实际 “刷新” 到终端的写入次数。println!
告诉系统每次都刷新终端,因为打印新行是常见的。如果不需要,可以将 stdout
句柄包装进 BufWriter
中, 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); // 如果你关心此处的 error,添加 `?` 。 }
其次,获取对stdout
(或 stderr
)的锁并使用 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); // 如果你关心此处的 error,添加 `?` 。 }
你还可以结合这两种实现。
显示进度条
一些命令行应用程序运行时间不到一分钟,其他的则会花几分钟或几小时。如果要编写后一种类型的程序,则可能需要向用户显示正在发生的事。为此,你应该尝试打印有用的状态更新,最好以一种易于使用的形式打印。
使用 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");
}
日志
为了更容易理解程序中发生的事情,我们可能需要添加一些日志语句。在编写应用程序时,这通常很容易。但当半年后再次运行这个程序时,它将变得非常有帮助。在某些方面,日志记录与使用println是相同的,除了可以指定消息的重要性。通常可以使用的级别是 error 、 warn 、 info 、 debug 和 trace ( error 的优先级最高, trace 的优先级最低)。
要将简单的日志记录添加到你的应用程序中,您需要做两件事: log crate (其中包含以日志级别命名的宏)和一个适配器(adapter),该适配器实际上将日志输出写入有用的地方。使用日志适配器的能力非常灵活:例如,您可以使用它们将日志不仅写到终端,也写到syslog或中央日志服务器。
由于我们现在只关心编写命令行应用程序,一个易于使用的适配器是 env_logger 。之所以称为 “ env” logger,是因为您可以使用环境变量来指定要记录的应用程序部分(以及要记录的级别)。它将在你的的日志消息前加上时间戳和日志消息来源的模块。由于库也可以使用 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=output_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="output_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=output_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
还包含一个构建器(builder),因此你可以以编程方式调整这些设置,而且,例如,默认情况下还显示 info 级别的消息。
有很多其他的日志适配器,以及 log
的扩展和替代方法。如果你知道应用程序有很多 log
,请确保对其进行 review ,并简化用户的使用。