Output

Printing “Hello World”

  1. #![allow(unused_variables)]
  2. fn main() {
  3. println!("Hello World");
  4. }

Well, that was easy.Great, onto the next topic.

Using println

You can pretty much print all the things you likewith the println! macro.This macro has some pretty amazing capabilities,but also a special syntax.It expects you to write a string literal as the first parameter,that contains placeholders that will be filled inby the values of the parameters that follow as further arguments.

For example:

  1. #![allow(unused_variables)]
  2. fn main() {
  3. let x = 42;
  4. println!("My lucky number is {}.", x);
  5. }

will print

  1. My lucky number is 42.

The curly braces ({}) in the string above is one of these placeholders.This is the default placeholder typethat tries to print the given value in a human readable way.For numbers and strings this works very well,but not all types can do that.This is why there is also a “debug representation”,that you can get by filling the braces of the placeholder like this: {:?}.

For example,

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

will print

  1. The list is: [1, 2, 3]

If you want your own data types to be printable for debugging and logging,you can in most cases add a #[derive(Debug)] above their definition.

Aside:“User-friendly” printing is done using the Display trait,debug output (human-readable but targeted at developers) uses the Debug trait.You can find more information about the syntax you can use in println!in the documentation for the std::fmt module.

Printing errors

Printing errors should be done via stderrto make it easier for usersand other toolsto pipe their outputs to filesor more tools.

Aside:On most operating systems,a program can write to two output streams, stdout and stderr.stdout is for the program’s actual output,while stderr allows errors and other messages to be kept separate from stdout.That way,output can be stored to a file or piped to another programwhile errors are shown to the user.

In Rust this is achievedwith println! and eprintln!,the former printing to stdoutand the latter to stderr.

  1. #![allow(unused_variables)]
  2. fn main() {
  3. println!("This is information");
  4. eprintln!("This is an error! :(");
  5. }

Beware: Printing escape codes can be dangerous,putting the user’s terminal into a weird state.Always be careful when manually printing them!

Ideally you should be using a crate like ansi_termwhen dealing with raw escape codesto make your (and your user’s) life easier.

A note on printing performance

Printing to the terminal is surprisingly slow!If you call things like println! in a loop,it can easily become a bottleneck in an otherwise fast program.To speed this up,there are two things you can do.

First,you might want to reduce the number of writesthat actually “flush” to the terminal.println! tells the system to flush to the terminal every time,because it is common to print each new line.If you don’t need that,you can wrap your stdout handle in a BufWriterwhich by default buffers up to 8 kB.(You can still call .flush() on this BufWriterwhen you want to print immediately.)

  1. #![allow(unused_variables)]
  2. fn main() {
  3. use std::io::{self, Write};
  4. let stdout = io::stdout(); // get the global stdout entity
  5. let mut handle = io::BufWriter::new(stdout); // optional: wrap that handle in a buffer
  6. writeln!(handle, "foo: {}", 42); // add `?` if you care about errors here
  7. }

Second,it helps to acquire a lock on stdout (or stderr)and use writeln! to print to it directly.This prevents the system from locking and unlocking stdout over and over again.

  1. #![allow(unused_variables)]
  2. fn main() {
  3. use std::io::{self, Write};
  4. let stdout = io::stdout(); // get the global stdout entity
  5. let mut handle = stdout.lock(); // acquire a lock on it
  6. writeln!(handle, "foo: {}", 42); // add `?` if you care about errors here
  7. }

You can also combine the two approaches.

Showing a progress bar

Some CLI applications run less than a second,others take minutes or hours.If you are writing one of the latter types of programs,you might want to show the user that something is happening.For this, you should try to print useful status updates,ideally in a form that can be easily consumed.

Using the indicatif crate,you can add progress barsand little spinners to your program.Here’s a quick example:

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

See the documentationand examplesfor more information.

Logging

To make it easier to understand what is happening in our program,we might want to add some log statements.This is usually easy while writing your application.But it will become super helpful when running this program again in half a year.In some regard,logging is the same as using println,except that you can specify the importance of a message.The levels you can usually use are error, warn, info, debug, and trace(error has the highest priority, trace the lowest).

To add simple logging to your application,you’ll need two things:The log crate (this contains macros named after the log level)and an adapter that actually writes the log output somewhere useful.Having the ability to use log adapters is very flexible:You can, for example, use them to write logs not only to the terminalbut also to syslog, or to a central log server.

Since we are right now only concerned with writing a CLI application,an easy adapter to use is env_logger.It’s called “env” logger because you canuse an environment variable to specify which parts of your applicationyou want to log(and at which level you want to log them).It will prefix your log messages with a timestampand the module where the log messages come from.Since libraries can also use log,you easily configure their log output, too.

Here’s a quick example:

  1. use log::{info, warn};
  2. fn main() {
  3. env_logger::init();
  4. info!("starting up");
  5. warn!("oops, nothing implemented!");
  6. }

Assuming you have this file as src/bin/output-log.rs,on Linux and macOS, you can run it like this:

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

In Windows PowerShell, you can run it like this:

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

In Windows CMD, you can run it like this:

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

RUSTLOG is the name of the environment variableyou can use to set your log settings.env_logger also contains a builderso you can programmatically adjust these settings,and, for example, also show _info level messages by default.

There are a lot of alternative logging adapters out there,and also alternatives or extensions to log.If you know your application will have a lot to log,make sure to review them,and make your users’ life easier.

Tip:Experience has shown that even mildly useful CLI programs can end up being used for years to come.(Especially if they were meant as a temporary solution.)If your application doesn’t workand someone (e.g., you, in the future) needs to figure out why,being able to pass —verbose to get additional log outputcan make the difference between minutes and hours of debugging.The clap-verbosity-flag crate contains a quick wayto add a —verbose to a project using structopt.