Pular para o conteúdo

Argumentos de linha de comando em Rust

A maioria dos projetinhos que valem a pena para praticar, como uma lista de tarefas ou um contador de palavras, precisa receber dados pela linha de comando em vez de deixar valores fixos no código. Esta página mostra o básico.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}

Se você compilasse isso em um programa chamado greet e rodasse:

Terminal window
./greet Alice

Você veria:

["greet", "Alice"]

O primeiro elemento é sempre o nome do próprio programa. Os argumentos que você digitou de fato começam no índice 1.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Uso: greet <nome>");
return;
}
let name = &args[1];
println!("Olá, {name}!");
}

Rodando com cargo run -- Alice, você teria:

Olá, Alice!

Se rodar sem nenhum argumento, você vê a mensagem de uso em vez de o programa quebrar. Verificar args.len() antes de acessar um índice evita um panic quando alguém esquece de passar o argumento.

Como todo argumento chega como String, muitas vezes você vai precisar do .parse() (da página de Conversão de tipos) para transformá-lo em número:

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Uso: square <número>");
return;
}
match args[1].parse::<i32>() {
Ok(n) => println!("{}", n * n),
Err(_) => println!("Isso não é um número válido"),
}
}

Uma flag é um argumento que liga alguma coisa, como --shout (gritar). Antes de partir para um crate, vale a pena ver como você mesmo trataria uma. O truque que deixa isso fácil de testar é colocar a lógica em uma função que recebe os argumentos como uma slice, &[String]. Assim o main pode passar os argumentos de verdade, e você pode passar uma lista feita à mão enquanto experimenta:

fn run(args: &[String]) {
let mut shout = false;
let mut names = Vec::new();
// pula o índice 0, o nome do programa
for arg in &args[1..] {
if arg == "--shout" {
shout = true;
} else {
names.push(arg);
}
}
if names.is_empty() {
println!("Uso: greet [--shout] <nome>...");
return;
}
for name in names {
let line = format!("Olá, {name}!");
if shout {
println!("{}", line.to_uppercase());
} else {
println!("{line}");
}
}
}
fn main() {
// Em um programa de verdade, você escreveria:
// let args: Vec<String> = std::env::args().collect();
// Aqui montamos a lista na mão, para funcionar também no Playground.
let args = vec![
String::from("greet"),
String::from("--shout"),
String::from("Ana"),
String::from("Ben"),
];
run(&args);
}

Saída:

OLÁ, ANA!
OLÁ, BEN!
▶ Experimente você mesmo

Algumas coisas para reparar:

  • &args[1..] são todos os argumentos menos o nome do programa. A flag pode aparecer em qualquer posição: greet Ana --shout Ben também funciona.
  • Tire o --shout da lista e rode de novo: você recebe as saudações normais. Tire os nomes também, e aparece a mensagem de uso.
  • Como run não se importa com a origem da lista, trocar para std::env::args().collect() é uma mudança de uma linha no main.

Isso funciona bem para uma ou duas flags. Quando você também quiser versões curtas (-s), valores depois de uma flag (--count 3) e uma mensagem de --help, o laço escrito à mão cresce rápido. Essa é a hora de mudar para o clap.

Acessar args por índice na mão fica complicado rapidinho quando você precisa de flags (--verbose), valores opcionais ou uma saída de --help que ajude de verdade. A solução padrão é o crate clap, que você adiciona com:

Terminal window
cargo add clap --features derive
use clap::Parser;
#[derive(Parser)]
struct Args {
name: String,
#[arg(short, long, default_value_t = 1)]
count: u32,
}
fn main() {
let args = Args::parse();
for _ in 0..args.count {
println!("Olá, {}!", args.name);
}
}

Com isso você ganha a leitura dos argumentos, a validação e uma mensagem --help funcionando, tudo gerado a partir da definição da struct. Para scripts rápidos você não precisa do clap, mas para qualquer ferramenta que você vá usar mais de uma vez, ele poupa muito trabalho manual.

O Playground não consegue passar argumentos de verdade, então os dois exercícios começam com um Vec<String> feito à mão, igualzinho ao que env::args().collect() devolveria. No seu computador, troque pela versão real e experimente cargo run -- ....

Dado vec!["notes", "add", "comprar leite"] (como Strings), imprima quantos argumentos o usuário digitou (sem contar o nome do programa) e depois imprima cada um com a sua posição, começando em 1.

Dica: a contagem é args.len() - 1. Um laço for i in 1..args.len() dá cada índice depois do nome do programa.

Ver solução
fn main() {
// finja que isto veio de std::env::args().collect()
let args = vec![
String::from("notes"),
String::from("add"),
String::from("comprar leite"),
];
println!("Você passou {} argumentos", args.len() - 1);
for i in 1..args.len() {
println!("{i}: {}", args[i]);
}
}

Saída:

Você passou 2 argumentos
1: add
2: comprar leite
▶ Experimente você mesmo

"comprar leite" conta como um argumento: em uma linha de comando de verdade, você digitaria entre aspas (cargo run -- add "comprar leite"), e o shell passa tudo como uma única string. O índice 0 continua sendo o nome do programa, e é por isso que o laço começa em 1.

Dado vec!["sum", "4", "10", "x", "6"], some todos os argumentos que são números válidos e imprima um aviso para os que não são. No fim, imprima o total.

Dica: percorra &args[1..] e use match arg.parse::<i32>(), como em Convertendo um argumento em número. Deixe um let mut total = 0; fora do laço.

Ver solução
fn main() {
let args = vec![
String::from("sum"),
String::from("4"),
String::from("10"),
String::from("x"),
String::from("6"),
];
let mut total = 0;
for arg in &args[1..] {
match arg.parse::<i32>() {
Ok(n) => total += n,
Err(_) => println!("Ignorando '{arg}': não é um número"),
}
}
println!("Total: {total}");
}

Saída:

Ignorando 'x': não é um número
Total: 20
▶ Experimente você mesmo

Todo argumento chega como texto, até o "4", então cada um precisa ser convertido. Pular uma entrada ruim com uma mensagem é mais amigável do que fazer o programa quebrar. Você também poderia parar no primeiro argumento ruim com return, se isso combinar melhor com o seu programa.

  • std::env::args().collect() devolve um Vec<String> com os argumentos da linha de comando. O índice 0 é sempre o nome do programa.
  • Verifique args.len() antes de acessar um índice, para que a falta de um argumento não cause um panic.
  • O .parse() transforma um argumento String em número, igual em qualquer outro lugar.
  • Coloque o tratamento dos argumentos em uma função que recebe &[String], para poder testá-la com uma lista feita à mão.
  • Para qualquer coisa além de alguns argumentos simples, o crate clap é a escolha padrão e bem testada.

Teste o que aprendeu

1. Quando você chama env::args().collect(), qual é o primeiro elemento do Vec resultante?

2. Por que verificar args.len() antes de acessar args por índice?

3. O que o crate clap oferece que acessar args na mão não oferece?

Pontuação: 0 / 3