Skip to content

Prompts

rich::prompt asks the user a question on the terminal, validates the answer, and asks again until it gets a valid one.

Type Returns Accepts
Prompt String any text, or one of a list of choices
Confirm bool y or n, any case
IntPrompt i64 a whole number
FloatPrompt f64 a number

The prompts are not re-exported at the crate root:

use rich::prompt::{Confirm, FloatPrompt, IntPrompt, Prompt, ScriptedInput};
use rich::{Console, Text};

Asking

fn interactive() -> std::io::Result<()> {
    let console = Console::new();

    let name = Prompt::new("What is your [bold]name[/]").ask(&console, Some("World"))?;
    let colour = Prompt::new("Favourite colour")
        .choices(["red", "green", "blue"])
        .case_sensitive(false)
        .ask(&console, None)?;
    let age = IntPrompt::new("Age").ask(&console, None)?;
    let ok = Confirm::new("Continue?").ask(&console, Some(true))?;

    console.print_str(&format!("{name} likes {colour}, is {age}, continue={ok}"));
    Ok(())
}
  • ask(&console, default) writes the question, reads a line from standard input, and returns std::io::Result<T>. An empty answer returns the default, when there is one.
  • End of input (stdin closed, Ctrl-D) returns the default — or the type's zero value ("", false, 0) when there is none — instead of looping.
  • An invalid answer prints a message and asks again.

This page has no screenshot of ask itself — it waits for a keyboard. The pictures below show the same questions rendered through the API that ask uses.

What the user sees

The question is console markup, followed by the choices in prompt.choices style, the default in prompt.default style, and :. make_prompt returns that line as a Text without asking anything:

fn questions(console: &Console) {
    // make_prompt renders a question exactly as ask() prints it.
    console.print(&Prompt::new("What is your [bold]name[/]").make_prompt(console, Some("World")));
    console.print(
        &Prompt::new("Favourite colour")
            .choices(["red", "green", "blue"])
            .make_prompt(console, None),
    );
    console.print(&IntPrompt::new("Age").make_prompt(console, Some(30)));
    console.print(&Confirm::new("Continue?").make_prompt(console, Some(true)));
    console.print(
        &Prompt::new("Password")
            .choices(["hunter2"])
            .show_choices(false)
            .show_default(false)
            .make_prompt(console, Some("hunter2")),
    );
}

Five prompt questions: with a default, with choices, a number, a confirmation and a hidden-choices password

Option On Effect
choices([...]) Prompt only these answers are accepted; shown as [a/b/c]
case_sensitive(false) Prompt match choices in any case; returns the choice as spelled in the list
show_choices(false) Prompt, Confirm hide [a/b/c] but still enforce it
show_default(false) all hide (default) but still use it

A rejected answer is reported in the prompt.invalid or prompt.invalid.choice style, and the question is asked again:

A session: an invalid choice rejected then a valid one, a non-number rejected then a number

Without a keyboard

Every ask has an ask_from twin that reads from an InputSource instead of stdin. ScriptedInput replays a list of lines, so the whole loop — defaults, rejections, re-asking — runs in a test:

fn scripted() {
    let console = Console::new();

    // ScriptedInput feeds canned lines instead of reading the keyboard.
    let mut input = ScriptedInput::new(["", "purple", "GREEN"]);
    let name = Prompt::new("Name")
        .ask_from(&console, &mut input, Some("World"))
        .unwrap();
    assert_eq!(name, "World"); // empty answer → the default

    // "purple" is rejected, the question is asked again, "GREEN" matches.
    let colour = Prompt::new("Colour")
        .choices(["red", "green", "blue"])
        .case_sensitive(false)
        .ask_from(&console, &mut input, None)
        .unwrap();
    assert_eq!(colour, "green"); // the choice as spelled in the list

    let mut input = ScriptedInput::new(["forty", "42", "y", "3.5"]);
    assert_eq!(
        IntPrompt::new("Age")
            .ask_from(&console, &mut input, None)
            .unwrap(),
        42
    );
    assert!(Confirm::new("Sure?")
        .ask_from(&console, &mut input, None)
        .unwrap());
    assert_eq!(
        FloatPrompt::new("Ratio")
            .ask_from(&console, &mut input, None)
            .unwrap(),
        3.5
    );
}

Implement InputSource (one method, read_line) to read from anything else: a socket, a GUI field, a file of answers.

To test only the validation, call process_response, which does no I/O at all:

fn validation() {
    // The validation step on its own: no I/O at all.
    assert_eq!(IntPrompt::new("n").process_response(" 7 "), Ok(7));
    let error = IntPrompt::new("n").process_response("seven").unwrap_err();
    assert_eq!(
        error.0,
        "[prompt.invalid]Please enter a valid integer number"
    );
    assert_eq!(Confirm::new("ok?").process_response("Y"), Ok(true));
}

Gotchas

  • No hidden input. Upstream's password=True (no echo) is not ported; read secrets with a crate such as rpassword.
  • IntPrompt is i64, FloatPrompt is f64. Convert and range-check after asking; there is no custom validator hook.

See also