DSL em Rust: escrevendo sua própria linguagem para problemas reais

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que um desenvolvedor Rust precisaria de sua própria linguagem? Mergulhando no mundo das DSLs



Imagine que você está escrevendo um parser para um arquivo de configuração complexo, implementando lógica de jogo ou descrevendo regras de negócio. Toda vez você esbarra no mesmo problema: o código se torna volumoso e a legibilidade cai. E se você pudesse criar uma mini-linguagem, especialmente ajustada para sua tarefa? Isso é uma linguagem específica de domínio (DSL em Rust).



Rust é um ambiente ideal para criar DSLs. Seu sistema de macros, poderosa inferência de tipos e custo zero de abstrações permitem construir linguagens que não perdem em desempenho para código escrito à mão. Neste artigo, vamos explorar como transformar Rust em um construtor de suas próprias linguagens e mostrar por que isso pode ser útil para você.



O que é uma DSL e por que ela é necessária em Rust?



DSL (Domain-Specific Language) é uma linguagem criada para resolver problemas em uma área de domínio específica. Diferente de uma linguagem de programação de propósito geral, uma DSL sacrifica a universalidade em prol da expressividade em um nicho específico. No ecossistema Rust, existem duas abordagens principais:



  • DSLs internas (embutidas) — construídas sobre a sintaxe do Rust usando macros e encadeamento de métodos. Exemplo: a biblioteca clap para análise de argumentos de linha de comando.
  • DSLs externas — uma sintaxe separada, que é analisada em tempo de execução. Exemplo: regex ou sqlx.


O desenvolvimento de DSLs em Rust é especialmente relevante para:

  • Arquivos de configuração (YAML/TOML como DSLs externas)
  • Motores de jogos (descrição do comportamento de NPCs)
  • Frameworks de teste (sintaxe própria para cenários)
  • Equilíbrio entre segurança e flexibilidade


Criando uma DSL interna em Rust: macros e encadeamentos



Vamos começar com algo simples — uma DSL interna para descrever requisições HTTP. Em vez de escrever:



let mut req = Request::new(Method::GET);req.set_url("https://api.example.com/data");req.add_header("Authorization", "Bearer token");req.add_header("Content-Type", "application/json");


Queremos obter uma sintaxe concisa:



let req = request! {    GET "https://api.example.com/data"    headers {        "Authorization": "Bearer token",        "Content-Type": "application/json"    }};


Vamos implementar isso usando macros Rust — a ferramenta mais poderosa para criar DSLs internas:



macro_rules! request {    ($method:ident $url:expr) => {        Request::new(stringify!($method).parse().unwrap(), $url)    };    ($method:ident $url:expr, headers { $($key:expr => $value:expr),* $(,)? }) => {        {            let mut req = Request::new(stringify!($method).parse().unwrap(), $url);            $(                req.add_header($key, $value);            )*            req        }    };}


Essa abordagem é um exemplo clássico de linguagem específica de domínio em Rust: não criamos uma nova sintaxe, mas redefinimos a existente, tornando o código mais legível. Observe: a macro gera código Rust comum em tempo de compilação, portanto, erros de tipo são detectados antes da execução.



DSL interna avançada: padrão Builder



Macros não são a única maneira. Frequentemente, para criar DSLs em Rust, usa-se o padrão Builder com encadeamento de métodos. Vejamos um exemplo de DSL para consultas SQL:



let query = Query::select()    .from("users")    .where_clause("age > ?")    .bind(18)    .order_by("name")    .build();


A implementação de tal DSL é simples e elegante:



struct QueryBuilder {    select: Vec<String>,    from: String,    condition: Option<String>,    order: Option<String>,}\                

Blogs

Book Recommendations