Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Redirecionando erros para a saída de erro padrão

Neste momento, estamos escrevendo toda a saída no terminal usando a macro println!. Na maioria dos terminais, existem dois tipos de saída: saída padrão (stdout) para informações gerais e saída de erro padrão (stderr) para mensagens de erro. Essa distinção permite que as pessoas direcionem a saída bem-sucedida de um programa para um arquivo e, ainda assim, vejam as mensagens de erro na tela.

A macro println! só consegue imprimir em stdout, então precisamos usar outra coisa para imprimir em stderr.

Verificando para onde os erros estão sendo escritos

Primeiro, vamos observar como o conteúdo impresso por minigrep está sendo gravado atualmente em stdout, incluindo as mensagens de erro que gostaríamos de enviar a stderr no lugar. Faremos isso redirecionando o fluxo de saída padrão para um arquivo enquanto causamos um erro de propósito. Não redirecionaremos o fluxo de erro padrão, então qualquer conteúdo enviado para stderr continuará aparecendo na tela.

Espera-se que programas de linha de comando enviem mensagens de erro para o fluxo de erro padrão, para que ainda possamos vê-las na tela mesmo que redirecionemos stdout para um arquivo. Nosso programa ainda não está se comportando bem: estamos prestes a ver que ele salva a mensagem de erro no arquivo em vez disso!

Para demonstrar esse comportamento, vamos executar o programa com > e o caminho do arquivo _output.txt_, para o qual queremos redirecionar stdout. Não passaremos nenhum argumento, o que deve causar um erro:

$ cargo run > output.txt

A sintaxe > instrui o shell a gravar o conteúdo de stdout em output.txt em vez de mostrá-lo na tela. Como não vimos a mensagem de erro aparecer no terminal, isso significa que ela deve ter ido parar no arquivo. Veja o conteúdo de output.txt:

Problem parsing arguments: not enough arguments

Isso mesmo: a mensagem de erro está sendo impressa em stdout. É muito mais útil que mensagens de erro como essa sejam impressas em stderr, para que apenas os dados de uma execução bem-sucedida terminem no arquivo. Vamos mudar isso.

Imprimindo erros em stderr

Usaremos o código da Listagem 12-24 para alterar a forma como as mensagens de erro são impressas. Por causa da refatoração feita anteriormente neste capítulo, todo o código que imprime mensagens de erro está concentrado em uma única função, main. A biblioteca padrão fornece a macro eprintln!, que imprime no fluxo de erro padrão, então vamos trocar os dois pontos em que estávamos chamando println! para imprimir erros e passar a usar eprintln!.

Filename: src/main.rs
use std::env;
use std::error::Error;
use std::fs;
use std::process;

use minigrep::{search, search_case_insensitive};

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = Config::build(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {err}");
        process::exit(1);
    });

    if let Err(e) = run(config) {
        eprintln!("Application error: {e}");
        process::exit(1);
    }
}

pub struct Config {
    pub query: String,
    pub file_path: String,
    pub ignore_case: bool,
}

impl Config {
    fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }

        let query = args[1].clone();
        let file_path = args[2].clone();

        let ignore_case = env::var("IGNORE_CASE").is_ok();

        Ok(Config {
            query,
            file_path,
            ignore_case,
        })
    }
}

fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.file_path)?;

    let results = if config.ignore_case {
        search_case_insensitive(&config.query, &contents)
    } else {
        search(&config.query, &contents)
    };

    for line in results {
        println!("{line}");
    }

    Ok(())
}
Listing 12-24: Escrevendo mensagens de erro na saída de erro padrão em vez da saída padrão com eprintln!

Agora vamos executar o programa novamente da mesma forma, sem argumentos e redirecionando stdout com >:

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

Agora vemos o erro na tela, e output.txt não contém nada, que é exatamente o comportamento esperado de programas de linha de comando.

Vamos executar o programa outra vez com argumentos que não causam erro, mas ainda redirecionando stdout para um arquivo:

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

Não veremos nenhuma saída no terminal, e output.txt conterá nossos resultados:

Nome do arquivo: output.txt

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

Isso demonstra que agora estamos usando stdout para a saída bem-sucedida e stderr para a saída de erro, como deve ser.

Resumo

Este capítulo recapitula alguns dos principais conceitos que você aprendeu até agora e mostra como realizar operações comuns de E/S em Rust. Com argumentos de linha de comando, arquivos, variáveis de ambiente e a macro eprintln! para imprimir erros, você agora está preparado para escrever aplicações de linha de comando. Combinado aos conceitos dos capítulos anteriores, seu código ficará bem organizado, armazenará dados de forma eficiente nas estruturas apropriadas, lidará bem com erros e será bem testado.

A seguir, exploraremos alguns recursos de Rust influenciados por linguagens funcionais: closures e iteradores.