Eco: minha implementação em Rust do programa echo
Hey, folks! Hoje quero falar sobre o meu mini-projeto, chamado "eco".
Minha implementação em Rust do programa echo.
O programa echo
basicamente pega argumentos (entrada) e imprime eles na tela do seu console (stdout). O eco, atualmente, faz isso também. Escolhi esse nome porque eu sempre li o programa echo
como /ɛko/. Como eu disse, o echo
pega a entrada e devolve a mesma coisa, similar a uma reverberação, um eco.
A iniciativa do projeto surgiu quando pesquisei por projetos para fazer em Rust, quero praticar!
A implementação inicial (v0.1.0) foi bem simples, como esperado. Veja o código do commit 516992e:
use std::env;
fn main() {
let mut args: Vec<String> = env::args().collect();
/*
* Read the `remove` docstring.
* This `remove(0)` is the worst case,
* all the arguments are going to be
* shifted to the left, everytime.
*/
args.remove(0);
let mut output = String::new();
for arg in args {
output.push_str(&format!("{arg} "));
}
println!("{}", output.trim());
}
- Pega os argumentos passados;
- Remove o primeiro argumento, pois ele é o caminho do executável;
- Inicializa uma string dinâmica, chamada output;
- Itera sobre os argumentos adicionando cada argumento com o formato "argumento-tal " (argumento + espaço) ao final da string output;
- Imprime a string output.
Agora na versão 0.1.2, o código está assim:
use std::collections::VecDeque;
use std::env;
use std::process::exit;
fn main() {
let mut args: VecDeque<String> = env::args().collect();
args.pop_front();
let is_help_needed = (args.len() == 1 && args[0] == "-h")
|| (args.len() == 1 && args[0] == "--help");
if is_help_needed {
println!("Example: eco-rs Bom dia!");
exit(0);
}
let mut output = String::new();
for arg in args {
output.push_str(&format!("{arg} "));
}
println!("{}", output.trim());
}
Alterações:
- Na linha
args.remove(0)
havia possibilidade de otimização e mesmo sem conhecer VecDeque, que deve ser um vetor bidirecional (entrada e saída por ambos os lados), a fiz. Essa estrutura de dados possibilita a saída de um elemento do início sem precisar mover os elementos posteriores para trás, o que é vantajoso aqui. - Adicionei um menu de ajuda, que tá bem vazio por sinal.
A distribuição está sendo feito pelo crates.io em eco-rs. Infelizmente a crate "eco" já existia. O legal é que ao executar o comando cargo install eco-rs
o Cargo pega a crate e compila na arquitetura do seu computador. Com isso, eu não preciso compilar para todas as arquiteturas disponíveis e manter as releases no GitHub também (deveria, quem não gosta de só baixar o executável e pronto?).
Conclusão
tl;dr: o projeto eco é, atualmente, apenas uma implementação em Rust do programa echo
que pega os argumentos de entrada, junta eles em uma string e imprime a string final na saída do terminal.
Pretendo fazer mais umas funcionalidades como colorir palavras específicas e criar uma sintaxe de marcação para estilizar também.
Se quiser acompanhar o projeto, veja o repositório eco.
Você pode me achar no:
- GitHub: kauefraga
- TabNews: kauefraga
- DevTo: kauefraga
- Twitter/X: rkauefraga
Obrigado por ler! ❤
Gostei demais da iniciativa!
Porém meu instinto otimizador chamou muito a atenção nesse método: .collect();
.
O Problema
Lendo a documentação do Args pode se ler o seguinte:
An iterator over the arguments of a process, yielding a String value for each argument.
E na documentação do método:
Transforms an iterator into a collection.
Um Iterador permite você percorrer do início ao final, sem poder acessar os elementos intermediários aleatóriamente. Que é exatamente a operação que você está fazendo.
A única vantagem de ter uma coleção (não todas) é justamente poder acessar qualquer elemento em qualquer ordem, o que você não está fazendo em nenhum momento.
O maior problema explicado:
O que acontece se você usar o seu comando com um arquivo de log de 1GB? Para cada palavra o método collect vai inserir em uma nova coleção, aumentando significativamente o uso de memória, sem falar na performance, para criar uma coleção pesadíssima sem toda essa necessidade.
Recomendo usar o iterator diretamente pelo método next()
Segunda evolução:
O que acontece se você usar o seu comando com um arquivo de log de 1GB?
rodando no terminal echo teste.txt
todo o conteúdo desse arquivo é jogado na tela. Numa segunda versão seria interessante implementar essa funcionalidade
Muito bacana como o rust "incentiva" esse tipo de implementação. Hoje em dia é comum vermos aplicativos de terminal reimplementados em rust e fazer isso para aprender é bem massa.
Uma sugestão que quero dar é utilizar o crate clap, que é o mais popular para se criar programas de terminal. Ele facilita a interpretação dos arqumentos e te dá de graça um menu de ajuda pro seu programa igual outros conhecidos, além de facilitar a colorização. Já utilizei em alguns projetos e deixa o produto final com uma cara bem profissional.