print!, println! and reading input
A Rust command line program talks to three streams: standard output for its results, standard error for its diagnostics, and standard input for what the user types. Rust gives each of them a macro or a handle, and the two output macros are the first ones the course used.
This lesson separates the three streams, because a script that captures output should not have to filter error messages out of it. It then reads a line from standard input and shows the trap of a prompt that is printed but not flushed. The last section is the error for using a method of Write without importing the trait.
Standard output
print! writes without a newline and println! writes one after its arguments. The empty println!() writes a bare newline, which is how a line built from several print! calls is closed.
The program prints two words on one line and then ends the line. The output has no trailing space and exactly one newline, which is what a line-oriented tool expects.
fn main() {
print!("one ");
print!("two");
println!();
}
rustc --edition 2024 stdout.rs
./stdout
one two
Standard output is line buffered when it goes to a terminal and block buffered when it goes to a pipe, which is why a program that prints without a newline may show nothing until it exits. The next section on flushing is about that difference.
Standard error
eprintln! writes to standard error and adds a newline, and eprint! writes without one. The stream is unbuffered, so a diagnostic appears even when the program crashes before standard output is flushed.
The program writes one line to each stream. Running it twice with one stream discarded shows each line separately, which is what a shell does with the redirections.
fn main() {
println!("to stdout");
eprintln!("to stderr");
}
rustc --edition 2024 stderr.rs
./stderr 2>/dev/null
./stderr 1>/dev/null
to stdout
to stderr
The first run keeps the standard output line and drops the error; the second does the opposite. A test that checks a program result reads the first stream and leaves the second to the terminal.
Reading a line
io::stdin().read_line(&mut line) appends one line, including the newline, to a String and returns the number of bytes read as a Result. trim removes the newline, which is why the print does not show it.
The program reads one line and prints it after the word read. The input comes from a pipe, which is the way to test a program that reads standard input without typing at it.
use std::io;
fn main() {
let mut line = String::new();
io::stdin().read_line(&mut line).unwrap();
println!("read: {}", line.trim());
}
rustc --edition 2024 stdin.rs
printf 'Ada\n' | ./stdin
read: Ada
At the end of the input read_line returns zero and appends nothing, which is how a loop reads until the stream ends. The lesson on arguments covers the other input channel.
Flushing a prompt
A prompt printed with print! has no newline, so it may stay in the buffer while the program waits for input. flush forces it out, and the user sees the prompt before the program blocks.
The program prints a prompt, flushes, and then prints a word. The output is one line, which looks the same whether the flush happened or not; the difference is visible only when the input arrives slowly. The call is still the correct habit.
use std::io::{self, Write};
fn main() {
print!("name: ");
io::stdout().flush().unwrap();
println!("ready");
}
rustc --edition 2024 flush.rs
./flush
name: ready
flush comes from the Write trait, not from the type, so the trait must be in scope. That is the subject of the next section.
A trait that is not in scope
Calling flush without importing std::io::Write is E0599: the method exists, but only as part of a trait that is not in scope. The help names the trait and writes the import, which is the fix.
The program below is the previous one without the import. The error lists the method as not found and then explains that the trait which provides it is implemented but not in scope.
use std::io;
fn main() {
print!("name: ");
io::stdout().flush().unwrap();
}
rustc --edition 2024 flush_err.rs
error[E0599]: no method named `flush` found for struct `Stdout` in the current scope
--> flush_err.rs:5:18
|
5 | io::stdout().flush().unwrap();
| ^^^^^ method not found in `Stdout`
|
--> /rustc/31fca3adb283cc9dfd56b49cdee9a96eb9c96ffd/library/std/src/io/mod.rs:1823:7
|
= note: the method is available for `Stdout` here
|
= help: items from traits can only be used if the trait is in scope
help: trait `Write` which provides `flush` is implemented but not in scope; perhaps you want to import it
|
1 + use std::io::Write;
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0599`.
The same error appears for every trait method that needs an import, such as read_to_string from Read or write_all from Write. The lesson on traits explains why the method needs the trait in scope.
Exercises
- Print a progress line with print! and a carriage return, then a second one that overwrites it. Which stream did you use?
- Read two lines from standard input and print them in reverse order. What does the second call return at the end of the stream?
- Remove the flush from the prompt program and run it with a slow input. Where does the prompt appear, and why?
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
Which stream should a diagnostic message use?
Answer
Standard error
Standard error keeps diagnostics out of the output a script captures.
-
What does
println!()with no arguments print?Answer
A newline
The macro always ends its line, so with no arguments it writes only the newline.
-
What does
read_linedo?Answer
Appends a line to a String and reports how many bytes it read
The line goes into the mutable buffer, and the Result carries the byte count.
-
Why call
flushafter a prompt printed withprint!?Answer
To make the prompt visible before the program waits for input
Without a newline the text may stay buffered, so the user sees nothing while the program blocks.
-
What does this program print?
fn main() { print!("one "); print!("two"); println!(); }Answer
one two
The two prints share a line, and the empty println ends it.
-
Which macro writes to standard error?
Answer
eprintln!
The e-prefixed macros write to standard error, which is why diagnostics use them.
-
What does this program print?
use std::io::{self, Write}; fn main() { print!("a"); io::stdout().flush().unwrap(); println!("b"); }Answer
ab
The first macro leaves the cursor after a, and the second appends b and a newline.
-
What does this program print?
use std::io::{self, Write}; fn main() { print!("name: "); io::stdout().flush().unwrap(); println!("ready"); }Answer
name: ready
The prompt has no newline, so the next print continues the same line.
-
What is the first line of the compiler output for this program?
use std::io; fn main() { print!("name: "); io::stdout().flush().unwrap(); }Answer
error[E0599]: no method named
flushfound for structStdoutin the current scopeThe method belongs to the Write trait, and the help adds the import that brings it into scope.
-
What does this program print?
fn main() { let text = "hello"; print!("{}", text); println!("!"); }Answer
hello!
The first macro leaves the cursor after hello, and the second appends the exclamation mark and a newline.
The code on this page was built with rustc 1.96.1 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev