In this article, we are going to create a simple prompt app as an interactive shell. The application will continue to ask for input until it receives an exit command. We will use the standard Rust library std::io::stdin() for input. We will also list some of the prompt-related crates that support more complex input functionality.
As already mentioned, the app will have an exit command. For demonstration purposes, we are also going to add another command to this interactive shell that will output the current time as a Unix timestamp. So, let's get started.
The first step is to create a new Rust project.
Creating a new project
To create a project, we are going to use cargo package manager. It should be included when Rust was installed.
The steps here are for Windows, but the cargo command (shown in just a moment) works for other platforms too.
In Windows Explorer, go to the folder where you want the project folder to be created and type cmd in the address bar as shown below:
A command prompt windows should open. Typing the following commands below will create a new Rust project.
If everything was successful, you should get the following message:
Created binary (application) `myprompt` package
Then, we enter cd myprompt
command to enter the folder of the newly created project.
The project should contain a src/main.rs
file containing a very simple "hello-world" program. We will add our code to this file. Open the main.rs
file with the text editor of your choice. Personally, I use Visual Studio Code for all of the Rust projects.
If you also have VS Code installed, enter the following command in the command prompt to open the VS Code in the current folder.
Once in VS Code, the structure of the project with all the files and folders should be visible in the sidebar.
Expand the src
folder and open the main.rs
file. Remove everything from that "hello world" generated code so that the main.rs
is completely empty. Now, let's add the code for our interactive shell prompt app.
Creating a terminal app with its own interactive prompt
The app will run in an infinite loop, accepting input and again re-prompt waiting for another input. The app will check if the entered input is a specific command. We are going to add two commands:
- now
This will display the current unix time. - exit
This will exit the app.
The complete code is below. Copy it into the main.rs
file.
use std::time::{SystemTime};
use std::io::Write;
fn prompt(name:&str) -> String {
let mut line = String::new();
print!("{}", name);
std::io::stdout().flush().unwrap();
std::io::stdin().read_line(&mut line).expect("Error: Could not read a line");
return line.trim().to_string()
}
fn main() {
loop {
let input=prompt("> ");
if input=="now" {
let unixtime = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
print!("Current Unix time is {:?}\n", unixtime);
}
else if input=="exit" {
break;
};
}
}
To build and run the code in VS Code, under the menu, go to Terminal > New Terminal. In the terminal window that opened, type:
In the image below, the app starts with >
as a prompt name. After typing unrecognized command xxxx, it prompts again. After typing now command it displays the current unixtime and after typing exit, it exits the application.
Examining the above code in more detail
-
Line 1-2:
use std::time::{SystemTime}; use std::io::Write;
Our app will have a command that will display the current unix timestamp, so we need to import the SystemTime module.
In line 2, we need to import the std::io::Write trait that implements the flush() method used in line 7, otherwise we get the compiler error on this line.
-
Line 4-11:
fn prompt(name:&str) -> String { let mut line = String::new(); print!("{}", name); std::io::stdout().flush().unwrap(); std::io::stdin().read_line(&mut line).expect("Error: Could not read a line"); return line.trim().to_string() }
The
prompt()
function returns the input that was entered by the user. The name argument is used as a "prompt name". This is the text displayed in the terminal before the cursor.The prompt name is displayed in line 6, but to make the prompt name display before the input, we need to use flush() method in line 7 to display it immediately, otherwise it might get displayed after the input.
The input is received in line 8, but the received input also contains the ending newline
\n
character. We use trim() method in line 10 to remove the newline and also any whitespace characters at the start and at the end of the input. -
Line 13-24:
fn main() { loop { let input=prompt("> "); if input=="now" { let unixtime = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); print!("Current Unix time is {:?}\n", unixtime); } else if input=="exit" { break; }; } }
In the main function, we have an infinite loop. The input is received in line 15 and then checked for specific commands. In our case, these are:
- now command (lines 16-19)
This one prints the current unix time with the help of the duration_since() method and UNIX_EPOCH constant. - exit command (lines 20-22)
this one is used to break from the infinite loop and exit the program.
- now command (lines 16-19)
This was just a simple example of how we could use a prompt that persists until we give it an exit command.
Crates related to input prompt functionality
For more sophisticated prompt scenarios, we could install additional crates. Let's examine a few of them.
dialoguer crate
The dialoguer crate seems to be very popular, even though it's lacking readme information. Luckily, there are plenty of examples on the dialoguer GitHub page.
promtly crate
Another such crate is the promptly crate. It is described as:
Simple, opinionated CLI prompting helper.
Among the features mentioned are:
- Reprompting until valid.
- Prompts for types.
- Path completion when prompting for paths.
requestty crate
The requestty crate is described as:
is an easy-to-use collection of interactive cli prompts inspired by Inquirer.js.
The main features mentioned are:
- Prompts can be used standalone or chained together.
- Dynamically deciding what questions should be asked based on previous questions.
- Every prompt is accompanied by an example.
The readme file of this crate contains ten examples as animated gifs, so you can easily check if the crate will be useful to your needs.
Conclusion
In this article, we created a persisting prompt application in Rust. Due to the infinite loop in the code, it's working as an interactive shell, constantly reprompting until we give it an exit command. Then we examined the code in more detail and finally, we listed some of the crates that focus on prompts and input.
Do you use any other crate for prompt purposes? Let us know and we might add it to the list.