A chateação da minha existência: dar suporte tanto a código async quanto sync em Rust

Dar suporte a código síncrono e assíncrono em Rust expõe uma tensão mais profunda na programação moderna: uma vez que uma biblioteca adota I/O assíncrono, sua “cor” tende a se espalhar pela API, complicando o uso para chamadores que querem interfaces simples e bloqueantes. Os comentaristas contrastam o ecossistema de Rust — em que múltiplos runtimes, a falta de traits async padronizadas e restrições de feature flags tornam o suporte duplo a sync/async doloroso — com linguagens como Go, JavaScript, Python e Zig, cada uma embutindo diferentes trade-offs entre ergonomia, desempenho e complexidade de runtime. As alternativas propostas vão desde crates separados para sync/async e wrappers `block_on` até designs sans-I/O e systems de efeitos, mas nenhuma elimina totalmente o custo de manutenção e de design para autores de bibliotecas.

Dor em bibliotecas Rust: dar suporte a sync & async

  • Problema central: bibliotecas querem oferecer APIs tanto síncronas quanto assíncronas sem duplicação de código, explosão de feature flags ou forçar um runtime async em usuários de sync.
  • As regras de features do Cargo (“features devem ser aditivas”) tornam configurações mutuamente exclusivas sync/async desajeitadas.
  • Manter dois crates (por exemplo, -sync e -async) ou dois caminhos de código paralelos foi tentado e considerado trabalhoso.
  • Usuários também não gostam de ser forçados ao async porque uma dependência assim escolheu.

Abordagens propostas

  • Usar block_on em uma thread dedicada para expor uma fachada sync sobre um núcleo async; muitos consideram isso um compromisso pragmático.
  • Manter apenas uma API async e sugerir que usuários sync bloqueiem sobre ela, embora isso ainda “colora” o chamador.
  • Crates ou módulos separados para sync e async, com alguma discussão sobre nomenclatura.
  • Estilo Sans-IO: expor tipos puros de request/response e deixar o chamador escolher clientes HTTP sync ou async.
  • Macros / codegen (como o unasync do Python ou ideias do tipo “multisync” do Nim) para gerar automaticamente variantes sync e async.

Async vs sync: debate conceitual

  • Alguns veem async como “bonito” e a forma mais natural de expressar máquinas de estado complexas e I/O de alta concorrência.
  • Outros acham futures e máquinas de estado menos naturais do que threads com escopo para muitas cargas de trabalho.
  • Vários argumentam que os benefícios do async aparecem apenas quando muitas tarefas rodam em paralelo; para código puramente sequencial, ele adiciona sobrecarga mental.
  • Há discordância sobre se async evita bugs de concorrência; outros apontam que condições de corrida ainda podem surgir quando await é inserido em código antes linear.

Comparações entre linguagens

  • JS: async se integra suavemente porque I/O sempre foi baseado em callbacks e em thread única; esconder async sob sync ainda é difícil.
  • Python: async abre novos padrões, mas asyncio é criticado por ser pesado e às vezes mais lento do que threads simples.
  • Go: elogiado por um único modelo de concorrência “verde”; há debate sobre se isso realmente evita function coloring ou apenas padroniza uma única “cor”.
  • Haskell: green threads e async são vistos como ergonomicamente mais agradáveis; a imutabilidade ajuda a raciocinar sobre concorrência.

Function coloring e runtimes

  • “Function coloring” é amplamente citado como a dor de fundo: anotações async se propagam viralmente para cima na pilha de chamadas e entre módulos.
  • A falta de um runtime embutido em Rust e a fragmentação entre Tokio e outros runtimes exacerbam a fricção (async traits, acoplamento ao runtime, semântica de cancelamento).
  • Alguns esperam futuras soluções (keyword generics, effect systems, designs async-generic) para reduzir duplicação e tornar o código agnóstico ao runtime.

Imutabilidade, concorrência e bugs

  • Um subthread argumenta que imutabilidade combina bem com async/threads ao eliminar data races; outros respondem que a imutabilidade pode ser supervalorizada e desloca a complexidade para mecanismos de atualização.
  • Não há consenso; participantes trazem relatos tanto a favor quanto contra a imutabilidade como uma “bala de prata” para correção concorrente.