Rust vs Elixir: Concorrência e BEAM vs Tokio | Rust Brasil

Rust vs Elixir: BEAM vs OS threads, Phoenix vs Axum, tolerância a falhas e quando combinar as duas linguagens em 2026.

Introdução

Rust e Elixir são duas linguagens que atraem desenvolvedores apaixonados, mas por razões completamente opostas. Elixir, criada pelo brasileiro José Valim em 2011, roda sobre a BEAM (a máquina virtual do Erlang) e é projetada para sistemas concorrentes, distribuídos e tolerantes a falhas. Rust compila para código nativo e foca em performance máxima com segurança de memória.

Enquanto Rust pergunta “como extrair o máximo de performance com segurança?”, Elixir pergunta “como construir sistemas que nunca param de funcionar?”. Essa diferença de filosofia torna a comparação especialmente interessante.

Este artigo é para desenvolvedores web que estão decidindo entre Phoenix e Axum, engenheiros de sistemas que precisam de concorrência pesada e qualquer pessoa que queira entender quando cada linguagem brilha.

Se você veio do Google procurando actix vs elixir, Rust vs Elixir, Phoenix vs Axum ou BEAM vs Tokio, o ponto central é este: não compare apenas benchmark. Compare o modelo operacional. Rust entrega binários previsíveis, custo baixo por requisição e controle fino de CPU/memória. Elixir entrega uma plataforma de runtime para manter sistemas vivos, reiniciar partes quebradas e sustentar milhões de interações concorrentes com menos cola manual.

Resumo rápido: Rust vs Elixir em 2026

PerguntaMelhor resposta curta
Quero API HTTP muito rápida e barata de hospedarRust com Axum ou Actix Web
Quero chat, notificações, presença, LiveView e tempo realElixir com Phoenix
Quero processar imagem, criptografia, parsing ou compressãoRust puro, ou Rust dentro de Elixir via Rustler
Quero sistema que se recupere automaticamente de falhas parciaisElixir/BEAM leva vantagem
Quero binário pequeno para edge, CLI, sidecar ou serviço internoRust leva vantagem
Quero produtividade full-stack com uma equipe pequenaPhoenix/Elixir pode ser mais direto

A escolha madura raramente é “qual linguagem é melhor?”. A pergunta de engenharia é: onde o gargalo real do produto mora? Se mora em CPU, latência, memória, distribuição de binário ou segurança de tipos, Rust costuma pagar. Se mora em fluxo de produto, presença em tempo real, supervisão, filas internas e resiliência de runtime, Elixir costuma pagar.

Tabela Comparativa

AspectoRustElixir
RuntimeCódigo nativo (sem VM)BEAM (máquina virtual do Erlang)
ParadigmaMulti-paradigma (imperativo + funcional)Funcional (imutabilidade por padrão)
Concorrênciaasync/await + OS threadsProcessos leves BEAM (~2KB cada)
Tolerância a falhasNão nativa (Result + panic)Supervisors + “let it crash”
Hot code reloadNão suportadoNativo na BEAM
Performance CPUMáxima (código nativo)5-20x mais lenta (BEAM interpretada)
LatênciaUltra-baixa, previsívelBaixa e consistente (preemptive scheduling)
TipagemEstática, forteDinâmica, forte (dialyzer para análise estática)
DistribuiçãoManual (gRPC, HTTP)Nativa (distributed Erlang)
Ecossistema webAxum, Actix WebPhoenix (com LiveView)
ImutabilidadeOpt-in (let mut)Por padrão (tudo é imutável)

Modelos de Concorrência: Filosofias Opostas

Elixir: Processos BEAM

Elixir usa processos leves da BEAM — não confunda com processos do sistema operacional. Cada processo BEAM consome apenas ~2KB de memória, e você pode criar milhões deles em uma única máquina:

defmodule ProcessadorPedidos do
  def processar(pedido_id) do
    # Cada pedido é processado em seu próprio processo BEAM
    IO.puts("Processando pedido #{pedido_id}...")
    :timer.sleep(100)  # simula trabalho
    {:ok, pedido_id * 10}
  end
end

defmodule App do
  def executar do
    # Cria 10.000 processos concorrentes trivialmente
    tarefas =
      1..10_000
      |> Enum.map(fn id ->
        Task.async(fn -> ProcessadorPedidos.processar(id) end)
      end)

    resultados =
      tarefas
      |> Enum.map(&Task.await(&1, 5000))

    total =
      resultados
      |> Enum.map(fn {:ok, valor} -> valor end)
      |> Enum.sum()

    IO.puts("Total processado: #{total}")
  end
end

App.executar()

Rust: Async/Await com Tokio

use tokio::task;

async fn processar(pedido_id: u64) -> u64 {
    println!("Processando pedido {pedido_id}...");
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    pedido_id * 10
}

#[tokio::main]
async fn main() {
    let mut handles = Vec::new();
    for id in 1..=10_000 {
        handles.push(task::spawn(processar(id)));
    }

    let mut total: u64 = 0;
    for handle in handles {
        total += handle.await.unwrap();
    }

    println!("Total processado: {total}");
}

Ambas as abordagens lidam bem com 10.000 tarefas concorrentes. A diferença:

  • Elixir: processos são preemptivos — a BEAM garante que nenhum processo monopoliza a CPU. Ideal para latência consistente.
  • Rust: tasks são cooperativas — cada task precisa fazer await para devolver controle ao scheduler. Ideal para throughput máximo.

Para se aprofundar em concorrência no Rust, veja nosso tutorial de concorrência.

Tolerância a Falhas: “Let It Crash”

O recurso mais distintivo do Elixir (herdado do Erlang) é a tolerância a falhas via supervisors.

Elixir: Supervisor Trees

defmodule Conexao do
  use GenServer

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: opts[:nome])
  end

  @impl true
  def init(opts) do
    IO.puts("Conexão #{opts[:nome]} iniciada")
    {:ok, %{tentativas: 0}}
  end

  @impl true
  def handle_call(:consultar, _from, estado) do
    # Simula falha intermitente
    if :rand.uniform(10) == 1 do
      raise "Erro de conexão!"
    end
    {:reply, {:ok, "dados"}, estado}
  end
end

defmodule App.Supervisor do
  use Supervisor

  def start_link(_) do
    Supervisor.start_link(__MODULE__, [], name: __MODULE__)
  end

  @impl true
  def init(_) do
    filhos = [
      {Conexao, nome: :db_principal},
      {Conexao, nome: :db_replica},
      {Conexao, nome: :cache},
    ]

    # Se um processo crashar, é reiniciado automaticamente!
    Supervisor.init(filhos, strategy: :one_for_one)
  end
end

Se a conexão do banco crashar, o supervisor reinicia automaticamente apenas aquele processo — o resto do sistema continua funcionando. Esse modelo de “let it crash” torna sistemas Elixir extraordinariamente resilientes.

Rust: Tratamento Explícito de Erros

use std::fmt;

#[derive(Debug)]
struct ErroConexao(String);

impl fmt::Display for ErroConexao {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Erro de conexão: {}", self.0)
    }
}

impl std::error::Error for ErroConexao {}

async fn consultar_com_retry(
    nome: &str,
    tentativas: u32,
) -> Result<String, ErroConexao> {
    for tentativa in 1..=tentativas {
        match consultar_banco(nome).await {
            Ok(dados) => return Ok(dados),
            Err(e) => {
                eprintln!(
                    "[{nome}] Tentativa {tentativa}/{tentativas} falhou: {e}"
                );
                if tentativa < tentativas {
                    tokio::time::sleep(
                        tokio::time::Duration::from_millis(100 * tentativa as u64)
                    ).await;
                }
            }
        }
    }
    Err(ErroConexao(format!("{nome}: todas as tentativas falharam")))
}

async fn consultar_banco(nome: &str) -> Result<String, ErroConexao> {
    // Simula falha intermitente
    if rand::random::<u32>() % 10 == 0 {
        return Err(ErroConexao(format!("timeout em {nome}")));
    }
    Ok("dados".to_string())
}

#[tokio::main]
async fn main() {
    match consultar_com_retry("db_principal", 3).await {
        Ok(dados) => println!("Sucesso: {dados}"),
        Err(e) => eprintln!("Falha final: {e}"),
    }
}

Em Rust, a resiliência é construída manualmente com Result, retries e circuit breakers. Funciona bem, mas requer mais código e disciplina do que o modelo de supervisors do Elixir.

Exemplo Prático: Aplicação Web

Elixir com Phoenix

defmodule AppWeb.TarefaController do
  use AppWeb, :controller

  alias App.Tarefas

  def index(conn, _params) do
    tarefas = Tarefas.listar()
    json(conn, %{tarefas: tarefas})
  end

  def create(conn, %{"titulo" => titulo, "prioridade" => prioridade}) do
    case Tarefas.criar(%{titulo: titulo, prioridade: prioridade}) do
      {:ok, tarefa} ->
        conn
        |> put_status(:created)
        |> json(%{tarefa: tarefa})

      {:error, changeset} ->
        conn
        |> put_status(:unprocessable_entity)
        |> json(%{erros: traduzir_erros(changeset)})
    end
  end

  defp traduzir_erros(changeset) do
    Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
  end
end

Rust com Axum

use axum::{
    extract::State,
    http::StatusCode,
    routing::{get, post},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};

#[derive(Clone, Serialize)]
struct Tarefa {
    id: u64,
    titulo: String,
    prioridade: String,
}

#[derive(Deserialize)]
struct CriarTarefa {
    titulo: String,
    prioridade: String,
}

type Db = Arc<Mutex<Vec<Tarefa>>>;

async fn listar(State(db): State<Db>) -> Json<Vec<Tarefa>> {
    let tarefas = db.lock().unwrap();
    Json(tarefas.clone())
}

async fn criar(
    State(db): State<Db>,
    Json(dto): Json<CriarTarefa>,
) -> (StatusCode, Json<Tarefa>) {
    let mut tarefas = db.lock().unwrap();
    let id = tarefas.len() as u64 + 1;
    let tarefa = Tarefa {
        id,
        titulo: dto.titulo,
        prioridade: dto.prioridade,
    };
    tarefas.push(tarefa.clone());
    (StatusCode::CREATED, Json(tarefa))
}

#[tokio::main]
async fn main() {
    let db: Db = Arc::new(Mutex::new(Vec::new()));
    let app = Router::new()
        .route("/tarefas", get(listar).post(criar))
        .with_state(db);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:4000")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

Phoenix oferece uma experiência mais “batteries included” com Ecto (ORM), Phoenix LiveView (interfaces reativas sem JavaScript) e PubSub integrado. Axum é mais minimalista, mas significativamente mais rápido.

Comparação de Performance

Benchmarks de Servidor Web

Benchmarks públicos variam conforme hardware, payload, banco, TLS, middleware e versão do framework. Use a tabela como ordem de grandeza, não como promessa absoluta de produção.

MétricaRust (Axum/Actix)Elixir (Phoenix)Tendência prática
Requisições/s (JSON simples)Centenas de milhares a milhõesDezenas a centenas de milharesRust costuma vencer em throughput bruto
Latência p50Muito baixaBaixaRust tende a ter vantagem em serviços pequenos
Latência p99Excelente se não houver blocking no runtimeMuito consistente por causa do scheduler preemptivoDepende do tipo de carga
Memória idlePoucos MBDezenas de MBRust vence em footprint
Memória sob cargaBaixa e previsívelMaior, mas com ótimo isolamento de processosRust vence em custo por instância
StartupMilissegundosSegundosRust vence para serverless, CLI e edge

Actix vs Elixir: por que essa busca aparece

A consulta actix vs elixir normalmente mistura duas comparações diferentes: Actix Web contra Phoenix, e Rust contra a BEAM. Actix Web é um framework HTTP extremamente rápido dentro do ecossistema Rust. Elixir é uma linguagem e um runtime completo; Phoenix é o framework web mais famoso em cima dele.

Se a decisão é apenas “quero uma API REST que responda JSON com baixa latência”, compare Actix Web, Axum e Phoenix. Rust provavelmente entrega menor custo por requisição. Mas se a decisão envolve workers supervisionados, canais em tempo real, PubSub, presença, LiveView e recuperação automática de falhas, comparar só Actix com Phoenix fica injusto: Phoenix vem com mais plataforma embutida.

Para times que já usam Rust, a pergunta prática é se Actix ainda vale frente a Axum. Para projetos novos, Axum ganhou força por se alinhar melhor com Tokio/Tower e composição explícita; Actix segue muito maduro e rápido. Se você está decidindo dentro de Rust, veja Axum vs Actix Web em 2026 e depois volte para esta comparação contra Elixir.

Onde Elixir Compensa

Apesar de ser 7x mais lenta em throughput bruto, Elixir tem vantagens práticas:

  • Latência consistente: o scheduler preemptivo da BEAM garante que nenhuma requisição espere muito. Rust pode ter picos de latência se uma task bloquear o scheduler.
  • Hot code reload: atualizar código em produção sem derrubar conexões. Impossível em Rust.
  • Distribuição nativa: conectar nós em cluster é trivial. Em Rust, você precisa implementar manualmente com gRPC ou similar.
  • Concorrência massiva: gerenciar 1 milhão de WebSockets é mais natural com processos BEAM.

Quando Combinar Rust e Elixir

Uma abordagem cada vez mais popular é usar Elixir como orquestrador e Rust nos pontos quentes, via NIFs (Native Implemented Functions) com a biblioteca Rustler:

// native/meu_nif/src/lib.rs
use rustler::{Encoder, Env, NifResult, Term};

#[rustler::nif]
fn processar_imagem(dados: Vec<u8>, largura: u32) -> NifResult<Vec<u8>> {
    // Processamento pesado de imagem em Rust
    let resultado: Vec<u8> = dados
        .chunks(3)
        .flat_map(|pixel| {
            let cinza = (pixel[0] as u32 + pixel[1] as u32 + pixel[2] as u32) / 3;
            vec![cinza as u8; 3]
        })
        .collect();
    Ok(resultado)
}

rustler::init!("Elixir.MeuNif");
defmodule MeuNif do
  use Rustler, otp_app: :minha_app, crate: "meu_nif"

  # Funções implementadas em Rust para performance
  def processar_imagem(_dados, _largura), do: :erlang.nif_error(:not_loaded)
end

# Uso: o melhor dos dois mundos
defmodule ImagemController do
  def processar(conn, %{"imagem" => upload}) do
    dados = File.read!(upload.path)

    # Processamento pesado em Rust, orquestração em Elixir
    resultado = MeuNif.processar_imagem(dados, 1920)

    send_download(conn, {:binary, resultado}, filename: "processada.rgb")
  end
end

Essa combinação é usada em produção por empresas como Discord (que migrou componentes de Elixir para Rust) e pela própria comunidade Elixir com projetos como Explorer (dataframes com Polars/Rust por baixo).

Quando Usar Elixir

Escolha Elixir quando:

  • Sistemas de tempo real: chat, notificações, WebSockets em massa (Phoenix LiveView)
  • Tolerância a falhas é prioridade: sistemas financeiros, telecoms, IoT
  • Distribuição é necessária: clusters de servidores com comunicação nativa
  • Hot code reload importa: sistemas que não podem ter downtime
  • Equipe de tamanho médio: Elixir é produtiva e a comunidade é excelente

Quando Usar Rust

Escolha Rust quando:

  • Throughput bruto é prioridade: processamento de dados em massa, proxies, CDNs
  • Recursos são limitados: serverless, edge computing, containers pequenos
  • Latência ultra-baixa: trading, gaming, processamento de áudio/vídeo
  • CPU-bound: compiladores, compressão, criptografia, processamento de imagem
  • Binários autossuficientes: CLIs e ferramentas sem dependências de runtime

Impacto na carreira no Brasil

Para carreira, Rust e Elixir ocupam nichos diferentes. Elixir aparece muito em produtos web, fintechs, educação, SaaS, healthtechs e empresas que valorizam Phoenix/LiveView para construir rápido com alta disponibilidade. Rust aparece mais em infraestrutura, segurança, blockchain, performance, sistemas embarcados, ferramentas internas, edge, observabilidade e backend de baixa latência.

No Brasil, Rust ainda é menor, mas a combinação com backend moderno aumenta o valor do perfil. Um desenvolvedor que sabe Tokio, Axum, SQLx, Tracing, deploy e observabilidade consegue se posicionar para vagas de plataforma e sistemas. Veja também as vagas Rust, a página de empresas usando Rust e o guia de salário Rust no Brasil.

Elixir costuma ser mais imediatamente produtivo para web product engineering. Rust costuma ser mais diferenciado para infraestrutura e performance. Se você já trabalha com Elixir, aprender Rust para NIFs, serviços críticos e ferramentas de plataforma é uma expansão natural. Se você já trabalha com Rust, estudar Elixir ajuda a enxergar supervisão, mensagens e tolerância a falhas como propriedades de arquitetura, não apenas bibliotecas.

FAQ rápido

Rust substitui Elixir?

Não em todos os casos. Rust substitui bem componentes onde performance, footprint e segurança de tipos importam mais. Elixir continua excelente para sistemas vivos, distribuídos, com comunicação em tempo real e alta tolerância a falhas.

Phoenix é mais lento que Axum?

Em throughput bruto, geralmente sim. Mas Phoenix entrega LiveView, PubSub, supervisão e produtividade full-stack. Em produto real, uma stack mais lenta no benchmark pode vencer se entregar mais rápido e operar com menos incidentes.

Rustler é seguro para NIFs?

Rustler reduz bastante o risco de NIFs escritos em C, porque Rust evita classes inteiras de erros de memória. Ainda assim, NIF mal desenhada pode bloquear schedulers da BEAM. Use para trabalho CPU-bound bem isolado e meça.

Qual estudar primeiro?

Se você quer backend web produtivo e tempo real, Elixir/Phoenix é um ótimo primeiro foco. Se você quer sistemas, performance, infraestrutura, WebAssembly, segurança ou edge, Rust é o melhor foco. Para mercado premium, saber os dois abre conversas raras.

Conclusão e Recomendação

Para aplicações web com requisitos de tempo real e alta disponibilidade, como chat, plataformas de streaming, IoT e sistemas financeiros, Elixir com Phoenix é a escolha ideal. O modelo de concorrência da BEAM, supervisors e hot code reload oferecem uma fundação incomparável para sistemas que não podem parar.

Para serviços de alta performance, processamento de dados e infraestrutura, Rust é claramente superior. A diferença de 7x em throughput e 10x em consumo de memória se traduz em custos de infraestrutura significativamente menores.

A melhor estratégia para muitos projetos é combinar as duas: Elixir para a lógica de aplicação, orquestração e comunicação em tempo real, com Rust via Rustler/NIFs para os componentes CPU-bound. Essa abordagem oferece produtividade, resiliência e performance onde cada uma importa mais.


Veja Também


Se você está comparando modelos de concorrência entre linguagens, veja também: