# [Rust Guide]12.8. Writing Error Messages to Standard Error

> Source: <https://dev.to/someb1oody/rust-guide128-writing-error-messages-to-standard-error-2jbp>
> Published: 2026-05-23 22:19:13+00:00

## 12.8.0 Before We Begin

Chapter 12 builds a sample project: a command-line program. The program is `grep`

(**Global Regular Expression Print**), a tool for global regular-expression searching and output. Its function is to **search for specified text in a specified file**.

This project is divided into these steps:

- Receiving command-line arguments
- Reading files
- Refactoring: improving modules and error handling
- Using TDD (test-driven development) to develop library functionality
- Using environment variables
**Writing error messages to standard error instead of standard output (this article)**

**If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.**

## 12.8.1 Review

Here is all the code written up to the previous article.

`lib.rs`

:

```
use std::error::Error;
use std::fs;

pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool,
}

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("Not enough arguments");
        }
        let query = args[1].clone();
        let filename = args[2].clone();
        let case_sensitive = std::env::var("IGNORE_CASE").is_err();
        Ok(Config {
            query,
            filename,
            case_sensitive,
        })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.filename)?;
    let results = if config.case_sensitive {
        search(&config.query, &contents)
    } else {
        search_case_insensitive(&config.query, &contents)
    };
    for line in results {
        println!("{}", line);
    }
    Ok(())
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();
    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }
    results
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();
    let query = query.to_lowercase();
    for line in contents.lines() {
        if line.to_lowercase().contains(&query) {
            results.push(line);
        }
    }
    results
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
    }

    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        );
    }
}
```

`main.rs`

:

``` js
use std::env;
use std::process;
use minigrep::Config;

fn main() {
    let args: Vec<String> = env::args().collect();
    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        process::exit(1);
    });
    if let Err(e) = minigrep::run(config) {
        println!("Application error: {}", e);
        process::exit(1);
    }
}
```

## 12.8.2 Standard Output vs. Standard Error

The current code prints all information, including error messages, to the terminal. Most terminals provide two output streams: **standard output ( stdout)** and

**standard error (**.

`stderr`

)**General information should go to standard output, while error messages should go to standard error.** The advantage of this separation is that normal output can be redirected into a file while error messages still appear on the screen.

The `println!`

macro can only print to standard output. The `eprintln!`

macro can print to standard error.

Using the current code, run this command in the terminal:

```
cargo run > output.txt
```

This redirects output to `output.txt`

, but the command does not include any arguments, so the program should error. Because the error messages are also written to standard output, they end up in `output.txt`

.

A better approach is to print error messages to standard error, which keeps standard output clean and separate from errors.

## 12.8.3 Modifying the Code

Changing the code so that error messages go to standard error is quite simple. We only need to change all error printing from `println!`

to `eprintln!`

. Because all error handling is in `main.rs`

, we only need a small change there, and `lib.rs`

does not need to be modified at all:

``` js
use std::env;
use std::process;
use minigrep::Config;

fn main() {
    let args: Vec<String> = env::args().collect();
    let config = Config::new(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {}", err);
        process::exit(1);
    });
    if let Err(e) = minigrep::run(config) {
        eprintln!("Application error: {}", e);
        process::exit(1);
    }
}
```

Now run the previous command again. It still has no arguments, so the program errors, but this time it will not put the error message into `output.txt`

; instead, it will print directly in the terminal:

``` bash
$ cargo run > output.txt
Problem parsing arguments: not enough arguments
```

Then try a normal run with arguments:

``` bash
$ cargo run -- to poem.txt > output.txt
```

The output is redirected to `output.txt`

. Open it:

```
Are you nobody, too?
How dreary to be somebody!
```

That is the result we want: errors are printed directly in the terminal, while normal output is redirected into the file.
