It is common to learn how to detect user input (stdin) when learning a new programming language, and Rust is not the exception. In this article, you will learn how to read the user input from a terminal.
Here are the steps to read user input (stdin) in Rust:
- Import the
std::io
crate - Generate a mutable String variable
- Generate a variable
stdin
with an instance ofio::stdin()
- Trigger the method
.read_line()
fromstdin
- Run your code: read the user input
This article will walk you step-by-step to help you understand how to read the user input using the reader io::stdin()
. Also, you will learn how to read multiple lines by locking the reader and storing user input data line by line until the user sends no data.
Table of Contents
How to read a single line from stdin
For explanation purposes, you will read the user input from themain()
function, which is generated by default when creating a new project in the main.rs file.
Import the std::io
crate
You will need to create an instance of io::stdin()
which is accessible via the std::io
. Therefore, import the std::io
module in the rust file (.rs) you will read the user input.
use std::io;
Note: There is no need to add std
dependency in the dependencies section of the Cargo.toml file. std
is Rust Standard Library and is available to all crates by default. std
is Rust
Generate a mutable String variable
In the main()
function, generate a mutable String variable called user_input
. This variable will store at some point the user input.
fn main() -> io::Result<()> {
let mut user_input = String::new();
Ok(())
}
Notice the main()
function return type definition in the previous snippet of code is io::Result<()>
. While that is not necessary, the io::Result
type is the type used for functions using the std::io
module that can cause errors.
Note: The main()
function doesn’t return anything other than ()
. This is typically used for operations that read user input and log it or print it within the same function. The io:Result<()>
type also prevents returning an error in case you don’t want to return errors.
Generate a variable stdin
with an instance of io::stdin()
Next, generate an instance io::stdin()
and store it in a variable. Storing the instance of io::stdin()
in a variable is not required. However, to provide more clarity this tutorial recommends doing so.
let stdin = io::stdin();
What does io::stdin()
do?
According to Rust’s documentation, the stdin()
function constructs a new handle to the standard input of the current process.
Constructs a new handle to the standard input of the current process.
Rust Function std::io::stdin documentation
Each handle returned is a reference to a shared global buffer whose access is synchronized via a mutex.
What is mutex?
If you read Rust’s documentation definition, you noticed the concept of a mutex.
A mutex is a primitive struct designed to safely mutate data. In other words, mutex allows changing values without the need of using the mut
keyword.
Trigger the method .read_line()
from stdin
In the next line of code right after generating an instance of io::stdin()
, use the stdin.read_line()
function to read the user’s input. Pass the mutable reference of the user_input
variable.
stdin.read_line(&mut user_input)?;
The read_line()
function will read the user’s input in the terminal and store and assign the values to user_input
. To verify user_input
contains the values the user entered in the terminal, use the println!
macro.
println!("input {} ", user_input);
Run your code: read the user input
Feel free to compare your code against the following snippet of code to make sure the logic works as expected.
use std::io;
fn main() -> io::Result<()> {
let mut user_input = String::new();
let stdin = io::stdin(); // We get `Stdin` here.
stdin.read_line(&mut user_input);
println!("input {} ", user_input);
Ok(())
}
If all is good, open the terminal and run the code using the following command:
cargo run
Note: At the beginning you will think nothing is happening. However, the code is waiting for you to submit your input in the terminal. Feel free to type anything you want and press Enter.
If everything is good, you should expect a log from the code with your text you wrote in the terminal. Hence, if you typed Rust is cool!, you should get a log equal to input Rust is cool!
How to read multiple lines from stdin
In the previous example, you learned how to read a single line from stdin. What if you would want to read multiple lines from stdin? The good news is you can achieve this by using io::stdin().lock()
function.
Solution
The following snippet of code reads multiple lines from stdin until the user sends an empty input.
use std::io::{self, BufRead};
fn main() -> io::Result<()> {
let mut lines = io::stdin().lock().lines();
let mut user_input = String::new();
while let Some(line) = lines.next() {
let last_input = line.unwrap();
// stop reading
if last_input.len() == 0 {
break;
}
// add a new line once user_input starts storing user input
if user_input.len() > 0 {
user_input.push_str("\n");
}
// store user input
user_input.push_str(&last_input);
}
println!("\nMulti-line user input \n{}", user_input);
// the lock is released after it goes out of scope
Ok(())
}
Code explanation
The mutable lines
variable stores an iterator over the lines of an instance of the reader. Notice a couple of things:
- Using the
lock()
function to lock the handle to the standard input (stdin) stream. This means your program will constantly read bytes from, .i.e., the terminal until the lock is released once it goes out of scope. - Importing the trait
BufRead
orstd::io::{self, BufRead};
to access thelines()
function. Failing to import the traitBufRead
will lead to the following error when compiling the code:error[E0599]: no method named lines found for struct StdinLock in the current scope
.
let mut lines = io::stdin().lock().lines();
A mutable user_input
variable is created to store input data.
let mut user_input = String::new();
Then, a while
is created to iterate over the lines of this reader io::stdin()
.
while let Some(line) = lines.next() {
}
Notice how the line
variable is defined while determining if the next line (lines.next()
) contains a String or an Error.
Inside the loop, the last_input
variable stores the string extracted from the line
variable using unwrap()
.
while let Some(line) = lines.next() {
let last_input = line.unwrap();
// stop storing the user input
if last_input.len() == 0 {
break;
}
// add a new line once user_input starts storing user input
if user_input.len() > 0 {
user_input.push_str("\n");
}
// store user input
user_input.push_str(&last_input);
}
Note: You can also unpack the result using the ?
operator. Hence, you could replace let last_input = line.unwrap();
for let last_input = line?
.
The main logic inside the while
loop is to store the user input in user_input
variable line by line. Notice, that the code checks whether the user sends data. If the user doesn’t send data and only presses Enter, it will break the loop. Hence, it will stop storing the user input in theuser_input
variable.
Finally, log the user_input
values to verify the reader read multiple lines from stdin.
println!("\nMulti-line user input \n{}", user_input);
Note: The lock is released after the reader goes out of scope. In the solution presented, this means the reader will stop reading user input once the main function is executed.
Conclusion
In this article, you learned how to read user input in rust by using an instance of io::stdin()
and accessing the read_line(&mut user_input)
function along with a mutable reference of a String variable to store the user input. Also, you learn how to read multiple lines by locking the reader and detecting and storing each time the user sends an input.
Feel free to check out the code in this article in my repository https://github.com/arealesramirez/rust-read-from-stdin
Did you learn something new?
Share your thoughts about this article on our Twitter account of Become A Better Programmer or on my Twitter account.