Skip to content

Rust - Input Output

This chapter covers the fundamentals of interacting with a user: reading from standard input (the keyboard) and writing to standard output (the console). We’ll also look at how to process command-line arguments.

Rust’s standard I/O library (std::io) is built around two fundamental traits: Read for input and Write for output.

  • Read: For sources you can read bytes from, like std::io::stdin() or a File.
  • Write: For destinations you can write bytes to, like std::io::stdout() or a File.

To read a line of text from the user, you can use the read_line method on the handle for standard input. This operation might fail (e.g., due to an OS-level error), so it returns a Result type, which you must handle.

use std::io;
fn main() {
println!("Please enter your name:");
let mut name = String::new();
// Get a handle to standard input and read a line.
// read_line returns a Result, so we use .expect() for simple error handling.
io::stdin()
.read_line(&mut name)
.expect("Failed to read line");
// The `read_line` includes the newline character, so we can trim it.
println!("Hello, {}. Nice to meet you!", name.trim());
}

The .expect("message") call is a shortcut. If the Result is Ok, it returns the value inside. If it’s Err, it will cause the program to panic (crash) and display the message you provided. It’s useful for examples and prototypes, but in production code, you should often use a match statement or other methods for more graceful error handling.

You’ve already seen the easiest way to write to the console: the println! and print! macros. They are convenient and handle formatting for you.

fn main() {
let name = "Alice";
let age = 30;
println!("Name: {}, Age: {}", name, age); // Prints with a newline
print!("This will be on the "); // Prints without a newline
print!("same line.\n");
}

Under the hood, these macros use the std::io::stdout() handle. You can use this handle directly with the Write trait if you need more control, but println! is usually sufficient.

Programs are often launched with arguments from the command line. Rust’s standard library provides a simple way to access these arguments via std::env::args().

std::env::args() returns an iterator that yields the arguments as strings. The first argument (args[0]) is always the path to your program’s executable.

// Save as main.rs and run with: `cargo run -- arg1 arg2`
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("All arguments: {:?}", args);
// The first argument is the program path
let program_path = &args[0];
println!("Program path: {}", program_path);
// Access the other arguments
if args.len() > 1 {
let first_arg = &args[1];
println!("First argument: {}", first_arg);
}
}

Let’s create a program that sums numbers passed as command-line arguments. This involves skipping the first argument and parsing the rest from strings into numbers. This is a great example of where iterators shine.

// Run with: `cargo run -- 10 20 5`
use std::env;
fn main() {
let mut total = 0;
// env::args() gives an iterator. .skip(1) creates a new iterator that
// skips the first element (the program path).
for arg in env::args().skip(1) {
// .parse() returns a Result, which we handle with a match.
match arg.parse::<i32>() {
Ok(num) => {
total += num;
}
Err(_) => {
eprintln!("Error: '{}' is not a valid integer. Skipping.", arg);
}
}
}
println!("Total sum: {}", total);
}

This example is much more robust. It uses .skip(1) for idiomatic iteration and a match to gracefully handle arguments that aren’t valid numbers, printing an error to standard error (eprintln!) instead of crashing.

Pro-Tip: Using a Crate for Complex Arguments

Section titled “Pro-Tip: Using a Crate for Complex Arguments”

For any real-world application, parsing command-line arguments manually can become tedious. The Rust ecosystem has excellent libraries (called ‘crates’) for this. A very popular one is clap. It allows you to declaratively define your application’s CLI interface, and it handles parsing, validation, and generating help messages for you automatically.

While std::env::args() is great for simple cases, consider using a crate like clap once your needs grow beyond a few positional arguments.