Output

Printing “Hello World”


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

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

Using println

You can pretty much print all the things you like with 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 in by the values of the parameters that follow as further arguments.

For example:


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

will print

My lucky number is 42.

The curly braces ({}) in the string above is one of these placeholders. This is the default placeholder type that 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,


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

will print

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.

Printing errors

Printing errors should be done via stderr to make it easier for users and other tools to pipe their outputs to files or more tools.

In Rust this is achieved with println! and eprintln!, the former printing to stdout and the latter to stderr.


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

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’s two things you can do.

First, you might want to reduce the number of writes that 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 BufWriter which by default buffers up to 8 kB. (You can still call .flush() on this BufWriter when you want to print immediately.)


# #![allow(unused_variables)]
#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
#}

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.


# #![allow(unused_variables)]
#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
#}

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 bars and little spinners to your program. Here’s a quick example:

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

See the documentation and examples for 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 terminal but 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 can use an environment variable to specify which parts of your application you want to log (and at which level you want to log them). It will prefix your log messages with a timestamp and the module where the log messages comes from. Since libraries can also use log, you easily configure their log output, too.

Here’s a quick example:

use log::{info, warn};

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

Assuming you have this as a src/bin/output-log.rs, you can run it like this:

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

RUST_LOG is the name of the environment variable you can use to set your log settings. env_logger also contains a builder so 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 extension to log. If you know your application will have a lot to log, make sure to review them, and make your users’ life easier.