---
title: Wizard
description: Signatures, navigation rules and panics for Wizard, WizardBuilder, WizardStep and StepOutcome.
tags: [reference, wizard]
---
# `Wizard`
A multi-step interactive form. Each step runs one or more
[`inquire`](https://crates.io/crates/inquire) prompts against a state
value the wizard owns, and returns a [`StepOutcome`](#stepoutcome) that
says whether to go forwards or backwards.
## `Wizard<S>`
```rust
pub struct Wizard<S> { /* private fields */ }
impl<S: Send + 'static> Wizard<S> {
#[must_use]
pub fn builder() -> WizardBuilder<S>;
pub async fn run(self) -> Result<S, WizardError>;
}
```
`S` is your own state type. The wizard owns it: `builder().initial(s)`
moves it in, and `run` moves it back out on success. There is no way to
borrow the state while the wizard is running.
`S: Send + 'static` is required. A state type holding a non-`Send` value
(an `Rc`, a raw pointer) will not compile.
### `Wizard::run` return values
`run` is `async` and needs an executor. The crate does not start one;
`tokio` is a dev-dependency only, used by the test suite.
| Every step returned `Next` | `Ok(state)` with all mutations applied |
| The wizard has zero steps | `Ok(state)`, the initial state unmodified, without prompting |
| A step returned `Back` while on the first step | `Err(WizardError::Cancelled)` |
| A step returned `Err(InquireError::OperationCanceled)` while on the first step | `Err(WizardError::Cancelled)` |
| Any step returned `Err(InquireError::OperationInterrupted)` | `Err(WizardError::Interrupted)` |
| Any step returned any other `InquireError` | `Err(WizardError::Step { step, message })` |
`run` never panics.
## Navigation rules
The driver loop holds an index into the step list and moves it by one.
- `Ok(StepOutcome::Next)` advances the index. If that takes it past the
last step, `run` returns `Ok(state)`.
- `Ok(StepOutcome::Back)` decrements the index. On index 0 it returns
`WizardError::Cancelled` instead.
- `Err(InquireError::OperationCanceled)`, which is what `inquire` returns
when the user presses <kbd>Esc</kbd>, is handled by the *same* match arm
as `Back`. Escape and an explicit `Back` are indistinguishable to the
wizard.
- `Err(InquireError::OperationInterrupted)` (<kbd>Ctrl</kbd>+<kbd>C</kbd>)
returns `WizardError::Interrupted` from any position, including the
first step.
- Every other `InquireError` variant is wrapped as
`WizardError::Step { step, message }`, where `step` is the failing
step's `name()` and `message` is the underlying error's `Display`
output.
There is no jump-to-step, no skip, and no conditional branching. Back
navigation moves exactly one step. See
[What rtb-tui does not do](../explanation/04-what-rtb-tui-does-not-do.md#the-wizard-only-moves-one-step-at-a-time).
## How state threads through the steps
Each step receives `&mut S`, so step *N+1* observes everything step *N*
wrote. Backing up does **not** roll anything back: the earlier step
re-runs against the state as it stands now, including values written by
later steps.
Two consequences worth planning for:
- A step must be safe to run more than once. Writing `state.name =
Some(prompt()?)` is fine; pushing onto a `Vec` is not, because a back
navigation will push a second time.
- Using the current state to pre-fill the prompt is the intended
pattern. That is how a user sees their previous answer when they
come back to change it.
## `WizardStep<S>`
```rust
#[async_trait]
pub trait WizardStep<S>: Send + Sync {
fn name(&self) -> &'static str;
async fn prompt(&self, state: &mut S) -> Result<StepOutcome, InquireError>;
}
```
`name` must return a `&'static str`. It appears in
`WizardError::Step { step, .. }` and nowhere else. It is not shown to
the user during the wizard, so make it something you would want in an
error message and in a log line.
`prompt` takes `&self`, not `&mut self`. Per-step mutable data has to
live in the wizard state or behind interior mutability.
The `Send + Sync` supertrait bound is on the trait itself, so a step
type holding a `Cell` or an `Rc` will not compile.
Implementations are boxed: `WizardBuilder::step` requires
`W: WizardStep<S> + 'static`.
### `InquireError` re-export
The crate re-exports `inquire::InquireError` from its root:
```rust
pub use inquire::InquireError;
```
It lets code name `InquireError` without depending on `inquire`: the
`WizardStep::prompt` signature, and a scripted test step like the one in
[Test a wizard without a terminal](../how-to/04-test-a-wizard-without-a-terminal.md).
A step that issues a real prompt calls `inquire::Text` or
`inquire::Select`, so it needs `inquire` in its own `Cargo.toml` anyway.
Keep that version aligned with the one `rtb-tui` depends on. A mismatch
produces two distinct `InquireError` types and a confusing type error at
the `?`.
## `StepOutcome`
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StepOutcome {
Next,
Back,
}
```
`Copy`, so returning it never moves anything. There is no `Finish`
variant: a wizard ends when its last step returns `Next`.
## `WizardBuilder<S>`
```rust
pub struct WizardBuilder<S> { /* private fields */ }
impl<S: Send + 'static> WizardBuilder<S> {
#[must_use] pub fn initial(self, state: S) -> Self;
#[must_use] pub fn step<W>(self, step: W) -> Self where W: WizardStep<S> + 'static;
#[must_use] pub fn build(self) -> Wizard<S>;
}
```
| `initial` | none; `build` panics | Calling it twice keeps the last value |
| `step` | no steps; `run` returns the initial state | Steps run in call order |
| `build` | — | Consumes the builder |
### `WizardBuilder::build` panics without `initial`
```
thread 'main' panicked at src/wizard.rs:
Wizard::builder requires .initial(...)
```
This is a runtime panic, not a compile error. The builder is a plain
accumulator, not a typestate builder, so nothing in the type system stops
you calling `build()` on a builder that has no initial state. Construct
the wizard in one chained expression and the mistake is hard to make:
```rust
let wizard = Wizard::<Profile>::builder()
.initial(Profile::default())
.step(AskName)
.build();
```