Metaprogramação e minha primeira experiência com Rust.
Você já se perguntou por que Rust é uma linguagem tão bem falada? Não apenas por sua performance, mas sempre está como "a linguagem mais amada" em pesquisas como as do Stack Overflow.
No início do ano fui convidado por um amigo a ajudá-lo na construção de alguns microsserviços, como estava querendo estudar uma nova linguagem, decidi arriscar e experimentar criar o meu em Rust. Foi meu primeiro contato com a linguagem, mas, já me trouxe ótimas impressões. Quero compartilhar essa experiência aqui.
Era um serviço muito simples: acessar um servidor FTP, processar arquivos de textos de notas fiscais com layout PROCEDA, mapear isso para um json e enviar para uma fila (RabbitMQ) onde outros serviços agiriam conforme.
Não havia regras de negócio muito complicadas, e parecia um ótimo caso para explorar bastante da nova linguagem.
Em alguns dias já deu pra entender o motivo da linguagem ser tão bem falada.
Todo mundo já está de saco cheio de saber da performance e segurança do Rust, minha abordagem aqui será outra: o "prazer do desenvolvedor" que a linguagem oferece.
Apesar de estranha no início, Rust é uma linguagem pouquissimo verbosa. Ela é realmente poderosa para realizar grandes coisas com poucas linhas de código. Tem uma semântica e uma sintaxe simples. Essa simplicidade, em certo ponto, me lembrou um pouco de Go. Poucos e simples arquivos de configuração, programação funcional, e catapimba. Rust é simples, mas está longe de ser fácil (abordaremos isso depois).
O que me encheu mesmo os olhos foi a liberdade que o Rust proporciona ao programador através de sua metaprogramação com macros e traits.
Sempre fui fã de criar ferramentas, e por isso sempre gostei de metaprogramação. Para quem não sabe, metaprogramação é basicamente programar ferramentas que ajudam a escrever programas. Como um bom Javeiro, sempre foi algo que me encantou: pensar em como ferramentas como o Spring traz abstrações que ajudam nossa vida, gerando código em tempo de compilação, por exemplo.
O Rust tem muito disso. E é incrível.
Existem os "macros" que são simplesmente snippets que geram códigos para você. Os macros são uma mão na roda para você pular boilerplates e focar no que realmente importa. Isso simplifica ainda mais.
No meu caso, eu criei um macro (vou mandar o repositório da biblioteca mais tarde), que me ajudava a transformar os arquivos de texto em "structs" (objetos no rust).
Basicamente, cada linha era um objeto. E cada propriedade do objeto estavam posicionados entre um range predefinido de caracteres.
Por exemplo, digamos que a linha 10 é para o objeto "pessoa".
- O nome da pessoa estaria dentro dos primeiros vinte caracteres dessa linha.
- O e-mail dela, estaria do caracter 21 até o 50 dessa linha.
- O documento dela estaria do caracter 51 até o 70 dessa mesma linha.
- O endereço dela estaria dos caracteres 71 até o 150 dessa linha.
Por ai vai.
Eu poderia escrever tudo isso na mão, com código puro, sim. Mas considerando o número de dados que uma nota fiscal possui, daria um trabalhão, certo?
Por sorte, eu sou um programador, e crio programas para evitar trabalho braçal repetitido... e... como minhas ferramentas são programas, posso fazer isso com o meu próprio trabalho! Incrível, não é?
A forma de solucionar isso com o Rust foi simples. Eu criei um macro para gerar código para mim a partir de um "decorator". Já explicarei o que é um decorator.
Vamos exemplificar melhor com código, o código abaixo é uma struct no Rust, uma "classe":
struct Pessoa {
pub nome: String,
pub email: String,
pub documento: String,
pub endereco: String,
}
É fácil de entender, é um objeto do tipo pessoa, que tem nome, email, documento e endereço que são strings.
Decorators são anotações que colocamos no nosso código e é a grande ferramenta da metaprogramação. Em algumas linguagens são chamados de Annotations. Lembra? No java ou no typescript usamos isso com @, como o @Entity, @Controller, no C# usamos entre [ e ].
No Rust é um pouco parecido com o C# (que é uma linguagem que também me fascina muito no quesito de liberdade que dá ao programador).
Eu criei um decorator que é o seguinte: #[from_line(X..Y)], X é o primeiro caracter que vai pegar da linha, e o Y é o último (na verdade, o Y é excluido, então vai pegar do X até o caracter antes do Y).
Então, no final, bastava eu apenas marcar minha struct assim:
#[derive(FromLine)]
struct Pessoa {
#[from_line(0..20)]
pub nome: String,
#[from_line(20..49)]
pub email: String,
#[from_Line(49..69)]
pub documento: String,
#[from_line(69..149)]
pub endereco: String,
}
E com isso, o Rust me gera uma função Pessoa::from_line(line), que eu posso passar a linha ali dentro e ele já me retorna a struct formada.
Só que existe uma outra questão, algumas Structs tinham outros tipos de dados que eu gostaria de preservar: inteiros, decimais, booleanos. Alguns dados eram opcionais, outros não.
Então, para fazer isso, eu implementei uma trait. Trait para Rust é como se fosse uma interface. Só que existe algo muito incrível que eu não vi em nenhuma outra linguagem:
Traits podem ser implementadas mesmo após a declaração de uma "classe" ou "objeto".
Ou seja, você pode criar uma trait e implementar para tipos nativos do Rust. Você cria um método e implementa ao tipo "inteiro" do Rust, e esse "inteiro" agora vai ter um novo método que você criou. Isso para mim abriu muitas possibilidades, porque eu poderia não só pegar tipos da biblioteca nativa do Rust, mas também de outras bibliotecas, e implementar minha trait neles.
E assim, eles se tornaram adaptados ao meu macro.
Eu não posso compartilhar o código original, mas fiz uma biblioteca para exemplificar como seria isso, no repositório abaixo:
https://github.com/gmessiasc/from_line/tree/main
Bom, mas nem tudo é flores. Rust é realmente difícil, e me deixou triste em alguns momentos: você precisa a todo momento saber o que está fazendo, o que o seu codigo vai fazer a nível de memória, é difícil compilar porque qualquer coisinha errada você toma bronca do Borrow Checker, e parece que não tem nenhuma inteligência artificial inteligente o suficiente para te ajudar nessa linguagem (o copilot e o bing.ai se sairam melhores, mas foram raras as vezes que eles me salvaram). Passei por estresses e perrengues devido minha falta de experiência, às vezes para fazer coisas simples.
Mas sinceramente? Os pontos positivos para mim foram muito mais impactantes, e me encantei com a linguagem. A visão que eu tenho é que com a experiência, sabendo exatamente o que vocÊ está fazendo, você consegue ser tão produtivo quanto uma linguagem mais "fácil" como python, javascript ou java, e, produzir aplicações performáticas e realmente potentes.