Output
Printing “Hello World”
#![allow(unused)] 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)] 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)] 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)] 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 are 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)] 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)] 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 come 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 file as src/bin/output-log.rs
,
on Linux and macOS, you can run it like this:
$ 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!
In Windows PowerShell, you can run it like this:
$ $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!
In Windows CMD, you can run it like this:
$ 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
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 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.