Comparando Ada e Rust na resolução de problemas do AoC
(github.com/johnperry-math)- Compara as diferenças e características de Ada e Rust ao resolver problemas do Advent of Code usando as duas linguagens
- Analisa as diferenças no design das linguagens e na forma prática de programar, com foco em segurança e confiabilidade
- As diferenças aparecem sob vários ângulos, como biblioteca padrão, recursos embutidos, desempenho e estilo de tratamento de erros
- Explica casos concretos encontrados na escrita e operação reais, com exemplos de código envolvendo modularidade, genéricos, loops e tratamento de erros
- A experiência de desenvolvimento se diferencia claramente por causa das diferenças em tipagem estática, manipulação de arrays e interfaces de tratamento de erros
Introdução e objetivo
- Ao resolver problemas do Advent of Code (doravante AoC), o autor usava apenas Ada, mas a partir de 2023 passou a escrever soluções também em Rust e Modula-2, o que permitiu uma comparação direta
- Ao portar soluções antes centradas em Ada para Rust, foi possível sentir na prática as diferenças estruturais e as abordagens próprias de cada linguagem
- O objetivo é deixar claras as diferenças de uso real sob as perspectivas de segurança do código, confiabilidade e design da linguagem
Versões das linguagens usadas na comparação
- Ada 2022 (com referência parcial a algumas regras do Spark 2014 quando necessário)
- Rust 2021 (comparações principais baseadas na versão Rust 1.81.0)
Recursos excluídos e critérios de comparação
- Recursos mais representativos de cada linguagem (= killer features) são mencionados brevemente em comentários ao longo do texto
- Alguns recursos não foram abordados, conforme a experiência pessoal do autor e as necessidades reais de cada solução
- O foco está nos principais aspectos, buscando evitar ao máximo opiniões pessoais
Contexto e perspectiva do autor
- Como usuário não nativo tanto de Ada quanto de Rust, a base do autor vem da experiência com linguagens dos anos 1980, como C/C++, Pascal e Modula-2
- Como resultado, o estilo de código pode diferir do moderno ou do idiomático
- As implementações podem não ser as ideais, e em alguns casos foram escolhidas soluções intuitivas ou não convencionais conforme o problema
Posicionamento de Ada e Rust
- Ada continua sendo uma linguagem para sistemas e embarcados extremamente segura e confiável, com ênfase na legibilidade do código
- Rust traz pontos fortes em segurança de memória e programação de sistemas, tendo sido apontada por vários anos como a “linguagem mais admirada” na pesquisa de desenvolvedores do Stack Overflow
- Ada, como linguagem de alto nível de propósito geral, oferece um espectro voltado à leitura e manutenção
- Rust é orientada ao desenvolvimento de programas de sistema de baixo nível, estabelecendo uma cultura de programação segura baseada em gerenciamento explícito de memória e tipos de erro/opção
Comparação de segurança e características estruturais
-
Ada
- Padrão ISO (especificação rigorosa)
- É fácil definir tipos adequados às características do problema (intervalos, número de dígitos etc.)
- Índices de arrays não precisam necessariamente ser numéricos
- Existe a especificação ainda mais rigorosa do Spark
-
Rust
- A especificação é centrada na documentação oficial (Reference) e no compilador
- A declaração de tipos depende de tipos de máquina (ex.: f64, u32)
- Indexação de arrays é natural apenas com tipos numéricos
Resumo principal da tabela de recursos/itens embutidos
- Há diferenças em suporte a verificação de limites de arrays, contêineres genéricos, concorrência, loops com rótulo e pattern matching
- Ada usa tratamento de erros baseado em Exception (exceções), enquanto Rust usa retorno baseado nos tipos Result/Option
- Rust se destaca em suporte a macros, pattern matching e pureza funcional
- Ada oferece design por contrato e, no Spark, verificação em tempo de compilação de DBC (Design By Contract)
- Em termos de segurança de memória, Rust e Spark impõem essa segurança, enquanto Ada permite o uso de ponteiros nulos
Comparação de desempenho e tempo de execução
- Rust geralmente tem execução mais rápida, mas compilação mais lenta, enquanto Ada tem a reputação oposta: compilação mais rápida e execução um pouco mais lenta, dependendo das verificações em tempo de execução
- Nos benchmarks, Rust apresentou overflow no problema day24 por causa do limite do tipo f64, enquanto Ada, com tipos de alto nível como digits 18, pôde escolher automaticamente um tipo de máquina adequado e evitar overflow, mostrando excelente desempenho
- Em Rust seria necessário usar f128 instável ou bibliotecas externas, enquanto Ada consegue vantagem apenas com a especificação de tipo adequada à especificação do compilador
Processamento de arquivos e tratamento de erros (Estudo de caso 1)
Processamento de arquivos em Ada
- Uso básico de Ada.Text_IO
- É relativamente intuitivo abrir arquivos explicitamente, ler linha por linha e tratar linhas por intervalo ou posição desejada
- Quando ocorre erro, o tratamento é feito por exceções em vez de mensagens de erro claras, e a possibilidade de erro não aparece na assinatura da função
Processamento de arquivos em Rust
- Uso de std::fs::File e BufReader
- Ao abrir um arquivo, o retorno é do tipo Result, deixando explícita a possibilidade de erro
- Não há suporte a acesso direto por índice de caractere; é obrigatório processar com Iterator
- Ferramentas funcionais e iterativas como map, filter, collect, sum são centrais, e há várias macros disponíveis (ex.:
include_str!) - Ao declarar erros explicitamente no tipo de retorno, a propagação de erro fica clara no nível da função
Modularidade e genéricos (Estudo de caso 2)
Modularidade em Ada
- Separação clara entre especificação (interface) e implementação com base em package
- Para reforçar a modularização, usa subpackages e combina a sintaxe use/rename para ajustar a legibilidade
- Suporte a generic em packages: generalização de tipos/constantes/subpackages inteiros
Modularidade em Rust
- Organização modular por meio do sistema de mod/crate, com a distinção entre especificação e implementação automatizada pelo gerador de documentação
- Controle de acesso declarativo com pub/private
- Combinação de importação/renomeação com use/as
- Suporte embutido a testes, permitindo declarar módulos de teste diretamente no código, compilar e executar automaticamente
Genéricos
- Ada suporta genéricos apenas no nível de package/procedure (não apenas no tipo isolado)
- Rust permite aplicar genéricos ao próprio tipo (baseado em templates)
- Ada permite expressar claramente características adicionais, como faixa de tipos, com range type e subtype, enquanto Rust usa constantes de instância
Comparação de tipos enumerados (Estudo de caso 3)
- Em Ada, uma declaração concisa já fornece automaticamente suporte a tipo discreto, ordenação, uso em loops e indexação
- Em Rust, a declaração de
enumé parecida, mas o acesso com pattern matching e iteração exige uma abordagem mais explícita
Conclusão
- Ada oferece controle mais rígido em tipos de especificação de alto nível, verificabilidade e checagens em tempo de execução
- Rust leva vantagem tanto em conveniência de desenvolvimento quanto em segurança em aspectos como estilo de programação funcional, programação com macros e tratamento de erros auxiliado pelo compilador
- Na resolução prática de problemas, Ada mostra força em compatibilidade com código antigo e manutenção, enquanto Rust se destaca pelo ecossistema moderno de ferramentas e pelo suporte a segurança/paralelismo
1 comentários
Comentários do Hacker News
Link explicando subranges em Nim
Documentação relacionada
Especificação do Rust
char), então é fácil indexar como um array de bytes. Rust, por outro lado, foi projetada com consciência de Unicode desde a base, então as strings de Rust são texto de verdade codificado em UTF-8. Por isso, Ada permite indexação aleatória como em arrays, enquanto em Rust o conceito de string é diferente, embora sempre exista a opção de convertê-la para um array de bytespanic(caso em que se quebra o limite de um valor codificado em Unicode). Em algo como AoC, onde se usa apenas ASCII, o certo é usar[u8]ou o métodostr::as_bytespara trabalhar com fatias de bytesVecMap), que são rápidos com poucos elementos, mas acima de certo ponto outras estruturas de dados passam a ser mais eficientes. Queria entender em que ponto o work stealing realmente traz vantagemSite do Prunt
Prunt no GitHub
Comentário relacionado no HN
SIDE_LENGTH, adicione uma função que o retorne", mas em vez disso a forma mais direta seria declarar uma constante comopub const SIDE_LENGTH: usize = ROW_LENGTH;BirdSpecies, entãoeggs[Robin]eeggs[Seagull]fazem sentido, maseggs[5]não é permitido. Em Rust, também dá para criar a estrutura de dados desejada (por exemplo implementandoIndex<BirdSpecies>), e entãoeggs[Robin]funciona enquantoeggs[5]dá erro. A diferença é que Rust não oferece isso diretamente como "array" no nível da linguagem. Quando, como em Ada, "tipos definidos pelo usuário podem ser declarados como inteiros que representam subconjuntos", esse tipo de indexação mostra seu real valor. Rust ainda não permite criar inteiros com faixa restrita como tipos definidos puramente pelo usuário (internamente há coisas comoNonZeroI16). Seria ótimo se Rust chegasse a suportar esse nível