---
title: "stdin, stdout e stderr em Rust"
url: "https://rustlang.com.br/stdlib/stdin-stdout/"
markdown_url: "https://rustlang.com.br/stdlib/stdin-stdout.MD"
description: "Referência completa de stdin, stdout e stderr em Rust: io::stdin, io::stdout, lock, read_line, print!, println!, eprint!, eprintln! e piping."
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# stdin, stdout e stderr em Rust

Referência completa de stdin, stdout e stderr em Rust: io::stdin, io::stdout, lock, read_line, print!, println!, eprint!, eprintln! e piping.


# stdin, stdout e stderr em Rust

Todo processo possui três fluxos de I/O padrão: **stdin** (entrada padrão), **stdout** (saída padrão) e **stderr** (saída de erro). No Rust, eles são acessados via `io::stdin()`, `io::stdout()` e `io::stderr()`, e usados indiretamente pelas macros `print!`, `println!`, `eprint!` e `eprintln!`. Entender como funcionam — especialmente o buffering e o locking — é essencial para programas de linha de comando eficientes.

## Visão geral e tipos-chave

### Os três fluxos

| Fluxo | Função | Tipo retornado | Buffering | Uso |
|---|---|---|---|---|
| stdin | `io::stdin()` | `Stdin` | Line-buffered | Ler entrada do usuário ou pipe |
| stdout | `io::stdout()` | `Stdout` | Line-buffered (terminal) / Full (pipe) | Saída principal do programa |
| stderr | `io::stderr()` | `Stderr` | Sem buffer (imediato) | Mensagens de erro e diagnóstico |

### Locking

Os handles `Stdin`, `Stdout` e `Stderr` usam um mutex interno para segurança entre threads. Cada chamada a `read_line()`, `write()` etc. adquire e libera esse lock. Para operações intensivas, use `lock()` para adquirir o lock uma única vez:

```rust
// Lento: lock adquirido e liberado a cada println!
for i in 0..1000 {
    println!("{}", i);
}

// Rápido: lock adquirido uma vez
let stdout = std::io::stdout();
let mut handle = stdout.lock();
for i in 0..1000 {
    writeln!(handle, "{}", i).unwrap();
}
```

## Padrões comuns com código

### Ler entrada do usuário

```rust
use std::io::{self, Write};

fn main() -> io::Result<()> {
    // Padrão básico: prompt + read_line
    print!("Digite seu nome: ");
    io::stdout().flush()?; // flush necessário após print! (sem newline)

    let mut nome = String::new();
    io::stdin().read_line(&mut nome)?;
    let nome = nome.trim(); // remover \n final

    println!("Olá, {}!", nome);

    // Ler número
    print!("Digite sua idade: ");
    io::stdout().flush()?;

    let mut entrada = String::new();
    io::stdin().read_line(&mut entrada)?;
    let idade: u32 = entrada.trim().parse().map_err(|e| {
        io::Error::new(io::ErrorKind::InvalidData, format!("Idade inválida: {}", e))
    })?;

    println!("{} tem {} anos", nome, idade);
    Ok(())
}
```

### Loop de leitura interativa

```rust
use std::io::{self, BufRead, Write};

fn main() -> io::Result<()> {
    let stdin = io::stdin();
    let mut stdout = io::stdout();

    println!("Calculadora simples (digite 'sair' para encerrar)");
    println!("Formato: <numero> <operador> <numero>");

    let mut linha = String::new();

    loop {
        print!("> ");
        stdout.flush()?;

        linha.clear();
        let bytes = stdin.read_line(&mut linha)?;

        if bytes == 0 {
            println!("\nEOF — encerrando");
            break;
        }

        let entrada = linha.trim();
        if entrada == "sair" || entrada == "quit" {
            println!("Até logo!");
            break;
        }

        let partes: Vec<&str> = entrada.split_whitespace().collect();
        if partes.len() != 3 {
            eprintln!("Erro: formato esperado: <num> <op> <num>");
            continue;
        }

        let a: f64 = match partes[0].parse() {
            Ok(v) => v,
            Err(_) => { eprintln!("Erro: '{}' não é um número", partes[0]); continue; }
        };
        let b: f64 = match partes[2].parse() {
            Ok(v) => v,
            Err(_) => { eprintln!("Erro: '{}' não é um número", partes[2]); continue; }
        };

        let resultado = match partes[1] {
            "+" => Some(a + b),
            "-" => Some(a - b),
            "*" => Some(a * b),
            "/" if b != 0.0 => Some(a / b),
            "/" => { eprintln!("Erro: divisão por zero"); None }
            op => { eprintln!("Erro: operador '{}' não reconhecido", op); None }
        };

        if let Some(r) = resultado {
            println!("= {}", r);
        }
    }

    Ok(())
}
```

### Saída formatada com macros

```rust
fn main() {
    let nome = "Rust";
    let versao = 1.76;

    // print! e println! — para stdout
    println!("Linguagem: {}", nome);
    println!("Versão: {:.1}", versao);
    println!("{:<20} {:>10}", "Produto", "Preço");
    println!("{:<20} {:>10.2}", "Notebook", 4599.90);
    println!("{:<20} {:>10.2}", "Mouse", 89.50);

    // eprint! e eprintln! — para stderr
    eprintln!("[AVISO] Esta mensagem vai para stderr");
    eprintln!("[ERRO] Detalhe: {}", "conexão recusada");

    // format! — para String (não imprime nada)
    let msg = format!("{} v{:.1}", nome, versao);
    println!("Formatado: {}", msg);

    // Formatação avançada
    println!("{:=^40}", " RELATÓRIO ");     // centralizado com =
    println!("{:#010X}", 255);               // 0x000000FF
    println!("{:b}", 42);                     // binário: 101010
    println!("{:e}", 1_500_000.0);            // notação científica
    println!("{value:.prec$}", value = 3.14159, prec = 3); // 3.142
}
```

## Tabela de funções e macros

### Funções de I/O padrão

| Função | Retorno | Descrição |
|---|---|---|
| `io::stdin()` | `Stdin` | Handle da entrada padrão |
| `io::stdout()` | `Stdout` | Handle da saída padrão |
| `io::stderr()` | `Stderr` | Handle da saída de erro |
| `stdin.lock()` | `StdinLock` | Lock exclusivo para stdin |
| `stdout.lock()` | `StdoutLock` | Lock exclusivo para stdout |
| `stderr.lock()` | `StderrLock` | Lock exclusivo para stderr |
| `stdin.read_line(&mut s)` | `io::Result<usize>` | Lê uma linha em String |
| `stdin.lines()` | `Lines<StdinLock>` | Iterador sobre linhas |

### Macros de saída

| Macro | Destino | Descrição |
|---|---|---|
| `print!(fmt, ...)` | stdout | Imprime sem newline |
| `println!(fmt, ...)` | stdout | Imprime com newline |
| `eprint!(fmt, ...)` | stderr | Imprime sem newline em stderr |
| `eprintln!(fmt, ...)` | stderr | Imprime com newline em stderr |
| `write!(w, fmt, ...)` | qualquer Write | Escrita formatada em writer |
| `writeln!(w, fmt, ...)` | qualquer Write | Escrita formatada com newline |
| `format!(fmt, ...)` | String | Formata e retorna String |

## Exemplos práticos

### Exemplo 1: Processar input via pipe (stdin como fonte de dados)

```rust
use std::io::{self, BufRead};

/// Programa que funciona como filtro Unix:
/// cat dados.txt | meu_programa --filtro "ERROR"
fn main() -> io::Result<()> {
    let args: Vec<String> = std::env::args().collect();
    let filtro = if args.len() > 2 && args[1] == "--filtro" {
        Some(args[2].as_str())
    } else {
        None
    };

    let stdin = io::stdin();
    let mut total = 0u64;
    let mut correspondencias = 0u64;

    for linha in stdin.lock().lines() {
        let linha = linha?;
        total += 1;

        match filtro {
            Some(f) if linha.contains(f) => {
                println!("{}", linha);
                correspondencias += 1;
            }
            None => {
                println!("{}", linha);
                correspondencias += 1;
            }
            _ => {}
        }
    }

    // Estatísticas vão para stderr (não interferem no pipe)
    eprintln!(
        "--- Processadas {} linhas, {} correspondências ---",
        total, correspondencias
    );

    Ok(())
}
```

### Exemplo 2: Saída com lock para alta performance

```rust
use std::io::{self, Write};
use std::time::Instant;

fn gerar_csv_performatico(num_linhas: usize) -> io::Result<()> {
    let stdout = io::stdout();
    let mut handle = stdout.lock(); // lock uma vez

    writeln!(handle, "id,nome,valor,categoria")?;

    for i in 0..num_linhas {
        writeln!(
            handle,
            "{},item_{:06},{:.2},cat_{}",
            i,
            i,
            i as f64 * 1.5,
            i % 10
        )?;
    }

    handle.flush()?;
    Ok(())
}

fn main() -> io::Result<()> {
    let inicio = Instant::now();
    gerar_csv_performatico(100_000)?;
    eprintln!("Gerado em {:?}", inicio.elapsed());
    Ok(())
}
```

### Exemplo 3: Separar stdout e stderr corretamente

```rust
use std::fs;
use std::io::{self, Write};

fn processar_arquivos(caminhos: &[&str]) -> io::Result<()> {
    let mut stdout = io::stdout().lock();
    let mut stderr = io::stderr().lock();

    let mut sucesso = 0;
    let mut falhas = 0;

    for caminho in caminhos {
        match fs::read_to_string(caminho) {
            Ok(conteudo) => {
                // Dados vão para stdout (podem ser redirecionados)
                writeln!(stdout, "=== {} ({} bytes) ===", caminho, conteudo.len())?;
                writeln!(stdout, "{}", conteudo)?;
                sucesso += 1;
            }
            Err(e) => {
                // Erros vão para stderr (sempre visíveis no terminal)
                writeln!(stderr, "[ERRO] {}: {}", caminho, e)?;
                falhas += 1;
            }
        }
    }

    // Resumo em stderr
    writeln!(stderr, "--- {} ok, {} falhas ---", sucesso, falhas)?;

    Ok(())
}

fn main() -> io::Result<()> {
    // Uso: ./programa > saida.txt 2> erros.txt
    processar_arquivos(&["config.toml", "dados.csv", "inexistente.txt"])?;
    Ok(())
}
```

### Exemplo 4: Menu interativo com limpeza de tela

```rust
use std::io::{self, Write};

fn limpar_tela() {
    // Sequências ANSI para limpar tela
    print!("\x1B[2J\x1B[1;1H");
    io::stdout().flush().unwrap();
}

fn menu_principal() -> io::Result<String> {
    println!("=== Gerenciador de Tarefas ===");
    println!();
    println!("1. Listar tarefas");
    println!("2. Adicionar tarefa");
    println!("3. Remover tarefa");
    println!("4. Sair");
    println!();
    print!("Escolha uma opção: ");
    io::stdout().flush()?;

    let mut escolha = String::new();
    io::stdin().read_line(&mut escolha)?;
    Ok(escolha.trim().to_string())
}

fn ler_texto(prompt: &str) -> io::Result<String> {
    print!("{}", prompt);
    io::stdout().flush()?;
    let mut input = String::new();
    io::stdin().read_line(&mut input)?;
    Ok(input.trim().to_string())
}

fn main() -> io::Result<()> {
    let mut tarefas: Vec<String> = Vec::new();

    loop {
        match menu_principal()?.as_str() {
            "1" => {
                if tarefas.is_empty() {
                    println!("\nNenhuma tarefa cadastrada.");
                } else {
                    println!("\nTarefas:");
                    for (i, t) in tarefas.iter().enumerate() {
                        println!("  {}. {}", i + 1, t);
                    }
                }
            }
            "2" => {
                let tarefa = ler_texto("Nova tarefa: ")?;
                if !tarefa.is_empty() {
                    tarefas.push(tarefa);
                    println!("Tarefa adicionada!");
                }
            }
            "3" => {
                let indice = ler_texto("Número da tarefa para remover: ")?;
                if let Ok(i) = indice.parse::<usize>() {
                    if i > 0 && i <= tarefas.len() {
                        let removida = tarefas.remove(i - 1);
                        println!("Removida: {}", removida);
                    } else {
                        eprintln!("Índice inválido");
                    }
                }
            }
            "4" | "sair" => {
                println!("Até logo!");
                break;
            }
            _ => eprintln!("Opção inválida"),
        }
        println!();
    }

    Ok(())
}
```

### Exemplo 5: Barra de progresso em stderr

```rust
use std::io::{self, Write};
use std::thread;
use std::time::Duration;

fn barra_progresso(atual: usize, total: usize, largura: usize) {
    let porcentagem = (atual as f64 / total as f64 * 100.0) as usize;
    let preenchido = (atual * largura) / total;
    let vazio = largura - preenchido;

    // \r volta ao início da linha (sem newline)
    eprint!(
        "\r[{}{}] {:3}% ({}/{})",
        "#".repeat(preenchido),
        "-".repeat(vazio),
        porcentagem,
        atual,
        total
    );
    io::stderr().flush().unwrap();
}

fn processar_com_progresso(itens: &[String]) -> io::Result<Vec<String>> {
    let total = itens.len();
    let mut resultados = Vec::with_capacity(total);

    for (i, item) in itens.iter().enumerate() {
        // Simular processamento
        thread::sleep(Duration::from_millis(50));

        resultados.push(item.to_uppercase());
        barra_progresso(i + 1, total, 30);
    }

    eprintln!(); // newline final após a barra
    Ok(resultados)
}

fn main() -> io::Result<()> {
    let itens: Vec<String> = (0..50).map(|i| format!("item_{}", i)).collect();

    eprintln!("Processando {} itens...", itens.len());
    let resultados = processar_com_progresso(&itens)?;

    // Resultados vão para stdout (podem ser pipados)
    for r in &resultados {
        println!("{}", r);
    }

    Ok(())
}
```

## Padrões de tratamento de erro para I/O

### Flush obrigatório após print!

A macro `print!` (sem newline) pode ficar no buffer. Sempre faça flush quando precisar que o texto apareça imediatamente:

```rust
use std::io::{self, Write};

fn prompt(mensagem: &str) -> io::Result<String> {
    print!("{}", mensagem);
    io::stdout().flush()?; // ESSENCIAL após print! sem newline
    let mut resposta = String::new();
    io::stdin().read_line(&mut resposta)?;
    Ok(resposta.trim().to_string())
}
```

### stdin retornando 0 bytes (EOF)

```rust
use std::io;

fn ler_obrigatorio(prompt: &str) -> io::Result<String> {
    loop {
        let mut entrada = String::new();
        print!("{}", prompt);
        io::stdout().flush()?;

        let bytes = io::stdin().read_line(&mut entrada)?;
        if bytes == 0 {
            return Err(io::Error::new(
                io::ErrorKind::UnexpectedEof,
                "Entrada encerrada (EOF) antes de receber resposta",
            ));
        }

        let texto = entrada.trim().to_string();
        if !texto.is_empty() {
            return Ok(texto);
        }

        eprintln!("Entrada não pode ser vazia. Tente novamente.");
    }
}
```

## Dicas de desempenho

- **Use `lock()` em loops** — evita adquirir/liberar o mutex a cada operação. A diferença pode ser de 10x ou mais.
- **stderr não é bufferizado** — cada `eprintln!` é uma syscall. Para logging intensivo, considere escrever em arquivo com `BufWriter`.
- **stdout é line-buffered no terminal** mas **full-buffered em pipe** — `flush()` é necessário se quiser saída imediata em pipe.
- **Reutilize o buffer** de `read_line()` chamando `clear()` em vez de criar uma nova `String` a cada iteração.

## Veja também

- [Módulo std::io](/stdlib/io-module/) — visão geral do sistema de I/O
- [Read e Write Traits](/stdlib/read-write/) — traits implementadas por Stdin/Stdout
- [BufReader e BufWriter](/stdlib/bufreader-bufwriter/) — buffering para performance
- [Módulo std::process](/stdlib/process-module/) — piping entre processos
- [Ler Input do Usuário](/receitas/ler-input-usuario/) — receita prática de leitura interativa
- Documentação oficial: [`std::io::stdin`](https://doc.rust-lang.org/std/io/fn.stdin.html), [`std::io::stdout`](https://doc.rust-lang.org/std/io/fn.stdout.html)
