🎓 A1Learner
🔍

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?