1 pontos por GN⁺ 2025-10-05 | 1 comentários | Compartilhar no WhatsApp
  • 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

 
GN⁺ 2025-10-05
Comentários do Hacker News
  • É uma pena que, apesar de Ada ter tido tantas ideias realmente boas, ela tenha acabado sendo usada quase só em áreas em que a segurança é extremamente importante. Em especial, o recurso de limitar a faixa de valores de tipos numéricos é muito útil para prevenir certos bugs. O Spark Ada foi fácil de aprender e de aplicar no desenvolvimento de software em conformidade com SIL 4 (o padrão mais rigoroso de segurança de software). Nas últimas décadas, a indústria de software correu para o "crescimento primeiro, estabilidade depois", mas agora dá para sentir um movimento de retorno ao desenvolvimento de software seguro. Tomara que as lições acumuladas sobre segurança ao longo desse tempo levem a linguagens melhores. Na prática, boas ideias muitas vezes acabaram escondidas e se perderam dentro de linguagens minoritárias
    • Quando se passa muito tempo desenvolvendo software, dá para ver que há mesmo muita "reinvenção da roda". Ada e Rust se parecem no objetivo de buscar segurança, mas a definição e o alcance disso são diferentes. Rust busca de forma muito intensa um tipo importante e bem focado de segurança, enquanto Ada tem uma definição de segurança mais ampla e mais concreta. Quando aprendi Ada no começo dos anos 90, a crítica mais comum era que a linguagem era grande e complexa demais, o que diminuía a velocidade de desenvolvimento (na época, um compilador Ada 83 validado custava por pessoa algo em torno de 20 mil dólares em valores de hoje). Mas os tempos mudaram, e agora todo mundo reconhece que uma linguagem grande e complexa como Rust é necessária para programação concorrente segura de verdade
    • Nim também suporta subranges, inspirados em Ada e Modula, para limitar a faixa de valores de um tipo
      type
        Age = range[0..200]
      
      let ageWorks = 200.Age
      let ageFails = 201.Age
      
      Na compilação, aparece um erro dizendo que 201 não pode ser convertido para o tipo Age
      Link explicando subranges em Nim
    • Ada (no caso do GNAT) oferece análise de unidades físicas/dimensionais em tempo de compilação, ou seja, verificação de unidades. Isso é muito prático em engenharia, então fico me perguntando por que outras linguagens só oferecem um recurso tão importante por meio de bibliotecas de terceiros
      Documentação relacionada
    • Em C++ também dá para criar facilmente, com código, tipos numéricos com faixa de valores restrita (não existe na biblioteca padrão, mas é bem simples implementar por conta própria). Algumas verificações de segurança podem ser feitas em tempo de compilação, e não em tempo de execução. Seria ótimo se todas as linguagens suportassem isso como padrão
    • O que mais me agrada em Ada é a abordagem clara em relação a orientação a objetos (OOP). A maioria das linguagens enfia os conceitos de OOP dentro de um único bloco chamado "classe", mas Ada permite aplicar separadamente passagem de mensagens, despacho dinâmico, subtipagem, genéricos etc. Gostei muito de como cada um desses recursos se combina de forma elegante
  • O autor cita como diferenças coisas como Ada ter uma especificação oficial e Rust não, mas do ponto de vista do usuário, adoção/ecossistema da linguagem (ferramentas, bibliotecas, comunidade) é mais importante do que isso. Ada foi bem-sucedida em áreas como aeroespacial/segurança e é adequada para AOC ou trabalhos embarcados de baixo nível, mas em projetos reais (sistemas distribuídos, componentes de SO etc.) entram em jogo fatores como formatos de dados, protocolos, suporte de IDE e colaboração com colegas. No fim, ao escolher uma linguagem pela primeira vez, esses aspectos do ambiente acabam sendo decisivos
    • Recentemente, Rust também recebeu da Ferrocene uma documentação de especificação inspirada no estilo das specs de Ada. Ela é pública, então vale consultar
      Especificação do Rust
    • Tanto Rust quanto Ada são fracos como "especificações formais" no sentido estrito (documentos que podem ser provados mecanicamente). Mesmo Spark Ada se apoia em pressupostos semânticos da linguagem, e ainda assim isso não é totalmente formal nem legível por máquina
    • Os desenvolvedores de software de controle de aeronaves provavelmente responderiam: "se isso não for importante no ambiente real, então sim, nosso processo é exagerado". De fato, em áreas críticas para segurança, linguagens e processos rigorosos como os de Ada são justamente o padrão
  • Achei interessante que, mesmo que Ada tenha menos recursos ligados a tipos do que Rust, em termos de legibilidade do código Ada muitas vezes pode ser superior. O texto comparativo não menciona velocidade de compilação, e essa ideia de que Ada é uma linguagem complexa talvez seja mais coisa do passado — hoje, em comparação com Rust, isso pode não ser necessariamente verdade. Depois de ler isso, fiquei com vontade de tentar um projeto real em Ada
    • Fico curioso sobre o que exatamente significa a expressão "desvantagens relacionadas a tipos". Pela minha experiência, Ada tem um sistema de tipos extremamente expressivo. Há tipos definidos pelo usuário com faixas de valores, arrays indexáveis por enumerações arbitrárias, definição de operadores por tipo, adição de verificações em compilação/execução, pré-condições/pós-condições e vários outros recursos auxiliares ligados aos tipos. Também há registros variantes, cláusulas de representação de estruturas etc. Isso não parece desvantagem; parece um recurso poderosíssimo
  • Quero falar sobre a diferença entre strings em Ada e Rust. Quando Ada foi projetada no começo dos anos 80, "string" era basicamente um array de caracteres (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 bytes
    • As strings Unicode embutidas em Ada geralmente são arrays UTF-32. Diferentemente de Rust, Ada não oferece literais UTF-8 diretamente; é preciso converter a partir de arrays de 8/16/32 bits
    • Também é possível indexar strings em Rust. Mas Rust não trata strings como arrays comuns; usa principalmente fatias de substring. Se você fizer uma indexação cortando no meio de um caractere, ocorre panic (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étodo str::as_bytes para trabalhar com fatias de bytes
  • A afirmação do autor de que Rust "não suporta programação concorrente por padrão" me parece estranha. Rust tem suporte a threads embutido na linguagem e, na verdade, isso é até mais fácil de usar do que async. O problema só aparece quando se precisa de um número muito grande de threads e se bate no limite de recursos; para a maior parte do software, threads built-in são suficientes
    • (Pergunta sincera de alguém que não usa Rust) Queria entender como o tratamento de cancelamento em threads e em async funciona em Rust, e em que isso difere do async de outras linguagens. Em C++, Python e C#, gerenciar interrupção/cancelamento em async sempre me pareceu muito melhor do que com threads. Ouvi dizer que em Rust esse gerenciamento de cancelamento/interrupção é até mais difícil porque não é tratado via exceções, então queria saber como isso é na prática profissional. Também gostaria de ouvir como Ada lida com esse tipo de interrupção
    • Tenho curiosidade sobre onde fica a linha de corte em que um scheduler com work stealing, como o Tokio, realmente fica mais rápido do que simplesmente rodar várias threads. Penso nisso como arrays simples (por exemplo, VecMap), 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 vantagem
    • Na prática, o principal motivo para usar async é que o crate de terceiros de que você depende já é async. (Ex.: Reqwest exige Tokrio) Se, no desenvolvimento de aplicações de mais alto nível, você insistir apenas em soluções não async, acaba batendo em limites
    • Em plataformas com suporte ruim a threads (WASM, embarcados etc.), async pode até ser mais adequado. A situação de centenas de milhares de pessoas acessando um blog ao mesmo tempo é meio irrealista; parece exagero usar isso como justificativa para defender a necessidade de async
  • Achei interessante que Ada também tenha compiladores open source. Antigamente eu pensava que só existiam compiladores proprietários, então nunca cheguei a me interessar por Ada; preciso dar outra olhada
    • O compilador GNAT existe há mais de 30 anos e, embora em certo momento tenha havido o mal-entendido de que, por não haver exceção de runtime GPL, os binários compilados também precisariam ser GPL, hoje essa questão já foi resolvida
    • O GNAT foi construído com base no GCC desde os anos 90, e em algumas universidades ele chegou a ser usado diretamente em disciplinas mais práticas, como programação em tempo real. Também tive a experiência de tentar usar Ada como linguagem introdutória de programação, mas logo migrar para Pascal e C++
  • Entre os projetos que me chamaram atenção na área de impressão 3D, um dos mais recentes foi o Prunt, uma placa de controle e firmware para impressoras. O firmware é desenvolvido em Ada, o que é uma escolha bem diferente, mas conceitualmente combina bastante
    Site do Prunt
    Prunt no GitHub
  • No fim do Case Study 2, foi dito que "se o cliente precisar conhecer SIDE_LENGTH, adicione uma função que o retorne", mas em vez disso a forma mais direta seria declarar uma constante como pub const SIDE_LENGTH: usize = ROW_LENGTH;
  • Não concordo com a afirmação de que ambas as linguagens incentivam programação centrada em pilha. Ada, na verdade, incentiva bastante a alocação estática
  • Achei curioso que a possibilidade de arrays em Ada terem índices de tipos arbitrários tenha sido apresentada como uma grande vantagem. Quase toda linguagem tem dicionários (hash maps) na biblioteca padrão, e Rust oferece dois tipos
    • Aqui estamos falando de arrays embutidos na linguagem. Por exemplo, em Ada, se você definir o índice de um array "eggs" como o tipo BirdSpecies, então eggs[Robin] e eggs[Seagull] fazem sentido, mas eggs[5] não é permitido. Em Rust, também dá para criar a estrutura de dados desejada (por exemplo implementando Index<BirdSpecies>), e então eggs[Robin] funciona enquanto eggs[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 como NonZeroI16). Seria ótimo se Rust chegasse a suportar esse nível
    • Ada também oferece suporte padrão a hash maps e conjuntos. Padrão relacionado a contêineres em Ada (veja a seção A.18). O fato de o tipo de índice de arrays poder usar uma faixa típica de "valores contíguos" (por exemplo, 0 a N-1) é uma vantagem enorme em cenários em que mapas densos ou acesso contínuo à memória importam, porque isso é muito mais rápido do que um dicionário e ainda tem melhor eficiência de cache
    • A restrição de subtipo aplicada ao tipo de índice de arrays em Ada é um conceito estruturalmente bem diferente de um dicionário. No nível da linguagem, dá até para limitar quais valores são válidos como índice do array