Introdução
Um programa de linha de comando recebe valores depois do nome do executável, grava os resultados normais na saída padrão e informa falhas pela saída de erro padrão e por um status de saída diferente de zero. Esses limites do processo permitem que pessoas e scripts distingam sucesso de falha.
Você vai concluir um pequeno comando de busca de texto. A função de busca já está preparada no código-fonte da biblioteca do pacote, portanto cada etapa pode se concentrar na análise de argumentos, no carregamento do arquivo, no comportamento quando não há correspondências e na comunicação final do processo.
Analisar os argumentos da linha de comando
Nesta etapa, você vai validar e extrair uma consulta de busca e um caminho de arquivo dos argumentos do processo.
O projeto está em /home/labex/project/mini-search. src/main.rs é o binário da linha de comando, enquanto src/lib.rs contém a lógica de busca reutilizável preparada para uma etapa posterior. Entre no projeto e abra o código-fonte do binário:
cd /home/labex/project/mini-search
nano src/main.rs
env::args() produz cada argumento como uma String, incluindo o caminho do executável no índice zero. A função main preparada coleta esses argumentos em um vetor. Em seguida, ela passa &args para o parâmetro args: &[String].
O tipo &[String] é uma fatia de valores String: uma visão emprestada e somente leitura dos elementos do vetor. Ele segue a mesma ideia de empréstimo de &str, mas representa uma sequência de elementos String, e não bytes de texto. O vetor continua pertencendo a main enquanto run lê seus elementos.
O fluxo dos dados é:
shell words → env::args() → Vec<String> in main → borrowed &[String] in run
Substitua o Err(...) de placeholder dentro de run por:
if args.len() != 3 {
return Err(String::from("usage: mini-search <query> <file>"));
}
let query = args[1].clone();
let path = args[2].clone();
Ok(vec![format!("Query: {query}"), format!("File: {path}")])
Exatamente três entradas significam o nome do executável mais dois argumentos fornecidos pelo usuário. O return Err(...) explícito interrompe run imediatamente quando esse formato está incorreto. Esse é um retorno antecipado, diferente dos retornos pela expressão final que você usou na Lab de funções: o código abaixo dele só é executado quando a quantidade de argumentos é válida.
O acesso por índice à fatia emprestada produz strings emprestadas. As duas chamadas a clone criam intencionalmente cópias pertencentes ao próprio código apenas da consulta e do caminho, para que o restante de run possa gerenciá-las como valores locais pertencentes ao escopo. Essa é uma escolha deliberada de propriedade, e não uma solução geral para erros de movimentação. A macro vec![first, second] então cria um vetor com dois elementos, e Ok retorna temporariamente essas duas linhas de inspeção.
Salve com Ctrl+O, pressione Enter e saia com Ctrl+X. Execute o comando com dois argumentos:
cargo run --quiet -- rust data/notes.txt
O primeiro --quiet reduz as mensagens do próprio Cargo. O -- separado informa ao Cargo que ele deve parar de interpretar opções; tudo o que vier depois será passado ao seu programa.
Query: rust
File: data/notes.txt
Isso comprova que os argumentos chegaram às posições esperadas.
Ler o arquivo e chamar a lógica da biblioteca
Nesta etapa, você vai substituir o resultado temporário de inspeção pelo carregamento real do arquivo e pelos resultados da busca.
Abra o código-fonte do binário:
nano src/main.rs
Adicione estas importações abaixo de use std::env;:
use std::fs;
use mini_search::find_lines;
O nome do pacote mini-search se torna o nome do crate Rust mini_search. O pub já preparado em find_lines torna essa função acessível fora do crate da biblioteca. Importá-la permite que o binário chame a função pública de src/lib.rs; manter a lógica de busca nesse arquivo separa o processamento de dados reutilizável do tratamento de argumentos específico do processo. Este é um pequeno exemplo inicial do modelo de biblioteca, binário e visibilidade ensinado integralmente na Lab posterior sobre módulos.
Substitua a linha temporária Ok(vec![...]) por:
let contents = fs::read_to_string(&path)
.map_err(|error| format!("could not read {path}: {error}"))?;
Ok(find_lines(&query, &contents))
O erro de leitura mantém o caminho solicitado e é propagado com ?. Em caso de sucesso, o binário toma emprestados a consulta e o conteúdo do arquivo, enquanto a biblioteca retorna as linhas correspondentes pertencentes ao resultado.
Salve e saia. Em seguida, verifique e execute:
cargo check
cargo run --quiet -- rust data/notes.txt
Rust makes ownership explicit.
Cargo builds Rust packages.
Rust tools help beginners.
Os resultados normais da busca são impressos na saída padrão pelo braço Ok preparado em main.
Transformar uma busca vazia em um erro útil
Nesta etapa, você vai distinguir uma busca bem-sucedida que encontrou resultados de uma busca válida que não encontrou nada.
Abra o código-fonte:
nano src/main.rs
Substitua Ok(find_lines(&query, &contents)) por:
let matches = find_lines(&query, &contents);
if matches.is_empty() {
return Err(format!("no lines matched '{query}'"));
}
Ok(matches)
Um vetor vazio pode ser detectado, mas o comando é mais útil quando explica esse resultado. Salve e saia. Em seguida, tente buscar um termo que não existe:
cargo run --quiet -- python data/notes.txt
no lines matched 'python'
Nesta etapa intermediária, o braço Err preparado ainda imprime a mensagem na saída padrão e encerra o programa com sucesso. Na próxima etapa, você dará aos erros o comportamento correto no processo.
Enviar erros para stderr e sair com status diferente de zero
Nesta etapa, você vai concluir o limite da linha de comando separando a saída normal das falhas.
A saída padrão, ou stdout, transporta os resultados solicitados. A saída de erro padrão, ou stderr, transporta diagnósticos de forma independente. Um status de saída zero significa sucesso; um status diferente de zero significa falha. Abra o código-fonte do binário:
nano src/main.rs
Adicione esta importação abaixo das importações existentes da biblioteca padrão:
use std::process;
Substitua o braço de uma linha Err(error) por:
Err(error) => {
eprintln!("{error}");
process::exit(1);
}
eprintln! escreve uma linha em stderr. process::exit(1) encerra imediatamente o processo com o status um. Salve e saia. Em seguida, compile uma vez para poder executar o binário diretamente, sem que o Cargo adicione sua própria mensagem de falha:
cargo build --quiet
./target/debug/mini-search python data/notes.txt
O diagnóstico continua sendo:
no lines matched 'python'
Imprima imediatamente o status do comando anterior:
echo $?
echo imprime seus argumentos, e o shell substitui $? pelo status de saída do comando executado mais recentemente:
1
O mesmo limite agora também trata uso inválido e arquivos ausentes. Executar ./target/debug/mini-search exibe a mensagem de uso; um caminho como data/missing.txt exibe o erro de leitura. Ambos escrevem somente em stderr e saem com status um, enquanto uma busca com correspondência escreve os resultados em stdout e sai com status zero.
Resumo
Você coletou e validou argumentos da linha de comando, usou o separador -- do Cargo, manteve a lógica de busca em um limite de biblioteca preparado, carregou o arquivo solicitado e concluiu o comportamento convencional de stdout, stderr e status de saída para um caso de sucesso e três casos de falha.


