Rust Interactive Prompts
An interactive prompt is a question a tool asks before it acts. The craft is asking only when a person is present.
Ask on stderr
The question goes to stderr. Stdout stays clean for data. The flush pushes the question out before the wait.
eprint!("Delete 3 files? [y/N] ");
io::stderr().flush()?;Read the answer
read_line keeps the newline on the answer. trim() strips it before the compare. Only the trimmed text decides.
let mut answer = String::new();
io::stdin().lock().read_line(&mut answer)?;
let yes = answer.trim() == "y";Anything but y means no
A bare Enter is a no. A stray word is a no. Only an exact y goes ahead. At a terminal, pressing Enter alone prints:
if answer.trim() == "y" {
println!("deleted 3 files");
} else {
println!("kept everything");
}Check for a human first
is_terminal() reports whether stdin is a person or a pipe. A pipe cannot answer questions. A piped run stops at once:
if !io::stdin().is_terminal() {
eprintln!("error: not interactive here; pass --yes to confirm");
return ExitCode::from(2);
}Example
use std::io::{self, BufRead, IsTerminal, Write};
use std::process::ExitCode;
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().collect();
if args.iter().any(|a| a == "--yes") {
println!("deleted 3 files");
return ExitCode::SUCCESS;
}
if !io::stdin().is_terminal() {
eprintln!("error: not interactive here; pass --yes to confirm");
return ExitCode::from(2);
}
eprint!("Delete 3 files? [y/N] ");
io::stderr().flush().unwrap();
let mut answer = String::new();
io::stdin().lock().read_line(&mut answer).unwrap();
if answer.trim() == "y" {
println!("deleted 3 files");
ExitCode::SUCCESS
} else {
println!("kept everything");
ExitCode::SUCCESS
}
}Key points
- An interactive prompt asks the person before the tool acts.
- Give scripts a way out.
- A --yes flag lets automation skip the question entirely.
- The import is std::io::IsTerminal.
- The check works on stdin, stdout, and stderr.
- The [y/N] shape advertises the default.
- The capital N promises that Enter alone chooses no.
- Ask at run time, not at build time.
- The same binary serves a person today and a cron job tonight.
In this example, one program serves three runs. With --yes, it prints deleted 3 files and exits 0. At a real terminal, it asks the question on stderr. The answer y prints deleted 3 files. The answer n, or a bare Enter, prints kept everything. In a pipe, stdin is not a terminal. The program refuses, prints its error, and exits with code 2. All four runs were tried for real.
Note
Never echo a password prompt.
The rpassword crate reads a password with the echo switched off.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why does the prompt go to stderr instead of stdout?