Rust vs C++: Segurança e Performance | Rust Brasil

Rust vs C++: segurança de memória, RAII vs ownership, build systems e quando migrar. Comparação técnica detalhada.

Introdução

Rust e C++ são as duas linguagens de programação de sistemas mais relevantes da atualidade. C++, com mais de 40 anos de história, é a base de sistemas operacionais, game engines, navegadores, bancos de dados e praticamente toda infraestrutura crítica de software. Rust, lançada em 2015, foi criada explicitamente para resolver os problemas de segurança de memória que assolam projetos C++ há décadas.

A questão não é se Rust é “melhor” que C++ — ambas produzem código com performance comparável. A questão é se as garantias de segurança do Rust justificam a transição para projetos novos ou a reescrita gradual de projetos existentes.

Este artigo é para desenvolvedores C++ considerando Rust, e para desenvolvedores Rust que precisam entender como as duas linguagens se comparam em profundidade técnica.

Tabela Comparativa

AspectoRustC++
Segurança de memóriaGarantida em tempo de compilaçãoResponsabilidade do programador
Modelo de memóriaOwnership + BorrowingRAII + smart pointers (opt-in)
Undefined BehaviorImpossível em safe RustComum e difícil de detectar
Build systemCargo (unificado)CMake, Meson, Bazel, Make (fragmentado)
PerformanceEquivalente a C++Referência de performance
GenericsMonomorphization + TraitsTemplates (Turing-complete)
HerançaSem herança (composição via Traits)Herança múltipla, classes virtuais
ABI estávelNão (exceto via C ABI)Não (mas mais estabelecido)
Ecossistemacrates.io, crescendo rápidoImenso, mas fragmentado
Padrão da linguagemEditions (2015, 2018, 2021, 2024)ISO C++11/14/17/20/23/26

Segurança de Memória: O Diferencial Fundamental

A Microsoft estima que 70% das vulnerabilidades de segurança em seus produtos são causadas por erros de memória em código C/C++. O Google reporta números similares para o Chromium. Rust foi projetada para eliminar essas classes inteiras de bugs.

Use-After-Free em C++

#include <iostream>
#include <vector>
#include <string>

int main() {
    std::vector<std::string> nomes = {"Alice", "Bob", "Carol"};
    std::string& referencia = nomes[0];

    nomes.push_back("Dave"); // Pode realocar o vetor!

    // Undefined behavior: referencia pode apontar para memória inválida
    std::cout << referencia << std::endl;
    return 0;
}

Este código compila sem warnings na maioria dos compiladores, mas contém undefined behavior. O push_back pode realocar o vetor, invalidando referencia.

O Equivalente em Rust Não Compila

fn main() {
    let mut nomes = vec!["Alice".to_string(), "Bob".to_string(), "Carol".to_string()];
    let referencia = &nomes[0]; // empréstimo imutável

    nomes.push("Dave".to_string()); // ERRO: não pode mutar enquanto emprestado

    println!("{}", referencia);
}
error[E0502]: cannot borrow `nomes` as mutable because it is also
              borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let referencia = &nomes[0];
  |                       ----- immutable borrow occurs here
4 |     nomes.push("Dave".to_string());
  |     ^^^^^ mutable borrow occurs here
5 |     println!("{}", referencia);
  |                    ---------- immutable borrow later used here

O borrow checker do Rust detecta o problema em tempo de compilação. Para entender melhor este erro, veja nossa página sobre o erro E0382: uso após move.

RAII vs Ownership: Filosofias Diferentes

Ambas as linguagens usam RAII (Resource Acquisition Is Initialization), mas de formas diferentes.

C++ com Smart Pointers

#include <memory>
#include <iostream>

class Recurso {
    std::string nome_;
public:
    explicit Recurso(std::string nome) : nome_(std::move(nome)) {
        std::cout << "Criando: " << nome_ << "\n";
    }
    ~Recurso() {
        std::cout << "Destruindo: " << nome_ << "\n";
    }
    void usar() const { std::cout << "Usando: " << nome_ << "\n"; }
};

void processar() {
    auto r1 = std::make_unique<Recurso>("Principal");
    r1->usar();

    // Transferência de ownership (move semantics)
    auto r2 = std::move(r1);
    // r1 agora é nullptr — mas COMPILA se você usar r1!
    // r1->usar(); // Undefined behavior em runtime

    r2->usar();
} // r2 destruído automaticamente aqui

int main() {
    processar();
    return 0;
}

Rust com Ownership

struct Recurso {
    nome: String,
}

impl Recurso {
    fn new(nome: &str) -> Self {
        println!("Criando: {nome}");
        Recurso { nome: nome.to_string() }
    }

    fn usar(&self) {
        println!("Usando: {}", self.nome);
    }
}

impl Drop for Recurso {
    fn drop(&mut self) {
        println!("Destruindo: {}", self.nome);
    }
}

fn processar() {
    let r1 = Recurso::new("Principal");
    r1.usar();

    // Transferência de ownership (move)
    let r2 = r1;
    // r1.usar(); // ERRO DE COMPILAÇÃO: value used after move

    r2.usar();
} // r2 destruído automaticamente aqui

fn main() {
    processar();
}

A diferença crucial: em C++, usar um unique_ptr após std::move compila mas causa undefined behavior. Em Rust, o compilador impede o uso após move com um erro claro.

Para se aprofundar em ownership e borrowing, veja nosso tutorial de ownership e borrowing.

Exemplo Prático: Processamento Paralelo

C++ com <thread> e <mutex>

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <numeric>

int main() {
    std::vector<int> dados(1'000'000);
    std::iota(dados.begin(), dados.end(), 1);

    long long soma_total = 0;
    std::mutex mtx;
    const int num_threads = 4;
    const int chunk = dados.size() / num_threads;

    std::vector<std::thread> threads;
    for (int t = 0; t < num_threads; ++t) {
        int inicio = t * chunk;
        int fim = (t == num_threads - 1) ? dados.size() : (t + 1) * chunk;

        threads.emplace_back([&dados, &soma_total, &mtx, inicio, fim]() {
            long long soma_local = 0;
            for (int i = inicio; i < fim; ++i) {
                soma_local += dados[i];
            }
            std::lock_guard<std::mutex> lock(mtx);
            soma_total += soma_local;
        });
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "Soma: " << soma_total << std::endl;
    return 0;
}

Rust com Rayon

use rayon::prelude::*;

fn main() {
    let dados: Vec<i64> = (1..=1_000_000).collect();

    let soma_total: i64 = dados.par_iter().sum();

    println!("Soma: {soma_total}");
}

Rust com Rayon transforma código sequencial em paralelo com uma única mudança (.par_iter() em vez de .iter()). O compilador garante que não há data races — se o código compila, é thread-safe.

Em C++, o programador precisa gerenciar threads, mutexes e garantir manualmente que não há condições de corrida.

Comparação de Performance

Benchmarks (Compilados com Otimização Máxima)

BenchmarkRustC++Diferença
Fibonacci(45)3,2s3,1s~3% C++
Sort 100M inteiros4,8s4,6s~4% C++
Parse JSON 1GB0,8s (serde)0,7s (simdjson)~14% C++
Regex 500MB0,28s (regex)0,30s (RE2)~7% Rust
HTTP req/s850k (axum)900k (drogon)~6% C++
Compilação (projeto médio)45s120s (CMake+ninja)~63% Rust

A performance é essencialmente equivalente. Diferenças menores dependem do benchmark específico, otimizações do compilador e bibliotecas usadas. A vantagem do Rust é conseguir essa performance com garantias de segurança.

Tempo de Compilação

Curiosamente, Rust frequentemente compila mais rápido que C++ em projetos grandes, graças ao Cargo e ao sistema de módulos mais eficiente. C++ sofre com o modelo de includes baseado em texto e tempos de link lentos.

Build Systems: Cargo vs o Caos

Uma das maiores frustrações em C++ é o ecossistema de build fragmentado:

# C++: varia por projeto
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
# Ou: meson setup build && ninja -C build
# Ou: bazel build //...
# Ou: make -j$(nproc)
# Rust: sempre o mesmo
cargo build --release
cargo test
cargo doc

O Cargo é unanimemente considerado uma das maiores vantagens do Rust. Gerenciamento de dependências, compilação, testes, documentação e publicação — tudo em uma ferramenta.

Quando Usar C++

Escolha C++ quando:

  • O projeto já existe em C++: reescrever bilhões de linhas não é prático
  • Game engines: Unreal Engine, Unity (runtime), Godot — o ecossistema é C++
  • ABI estável é necessário: interoperabilidade com bibliotecas existentes
  • A equipe conhece C++ profundamente: expertise conta muito
  • Plataformas com toolchains limitados: alguns embarcados só têm compilador C/C++

Quando Usar Rust

Escolha Rust quando:

  • Projeto novo de sistemas: sem legado para manter compatibilidade
  • Segurança é prioridade: criptografia, kernels, navegadores, infraestrutura
  • Concorrência pesada: o compilador elimina data races
  • Build system importa: Cargo vs CMake não tem comparação
  • Atração de talentos: Rust é consistentemente a linguagem “mais amada”

Tendências: Rust no Mundo C++

O movimento de adoção de Rust em projetos tradicionalmente C++ é real:

  • Linux Kernel: aceita módulos em Rust desde 2022
  • Android: mais de 1 milhão de linhas de Rust no AOSP
  • Windows: Microsoft usa Rust em componentes do kernel
  • Chromium: Google começou a adotar Rust no navegador
  • NSA e Casa Branca: recomendam oficialmente linguagens memory-safe

Conclusão e Recomendação

Para projetos novos de sistemas em 2026, recomendamos Rust. A performance é equivalente a C++, e as garantias de segurança de memória eliminam classes inteiras de bugs que custam bilhões de dólares à indústria. O ecossistema (Cargo, crates.io, documentação) é mais moderno e produtivo.

Para projetos C++ existentes, a abordagem pragmática é adotar Rust gradualmente: novos componentes em Rust, interoperabilidade via FFI (C ABI), e reescrita incremental dos módulos mais críticos. Não tente reescrever tudo de uma vez.

Se você é desenvolvedor C++, aprender Rust vai torná-lo um programador melhor mesmo em C++ — os conceitos de ownership e borrowing vão mudar como você pensa sobre gerenciamento de recursos.


Veja Também