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

Rust vs C: segurança de memória, FFI, performance, build systems e quando migrar de C para Rust. Comparação técnica.

Introdução

C é, sem exagero, a linguagem mais influente da história da computação. Criada em 1972 por Dennis Ritchie, ela é a base de sistemas operacionais (Linux, Windows, macOS), bancos de dados (PostgreSQL, SQLite), linguagens de programação (CPython, Ruby MRI, PHP) e praticamente toda infraestrutura que sustenta a internet. Rust surgiu em 2015 com a missão explícita de ser uma alternativa segura para os mesmos domínios onde C reina.

Este artigo é para programadores C que avaliam Rust, engenheiros de sistemas que precisam decidir entre as duas linguagens e qualquer pessoa interessada na transformação que está acontecendo na programação de sistemas com a adoção de Rust no Linux kernel, Android e Windows.

Tabela Comparativa

AspectoRustC
Ano de criação20151972
Segurança de memóriaGarantida pelo compiladorResponsabilidade total do programador
Undefined BehaviorImpossível em safe RustCentenas de formas possíveis
Gerenciamento de memóriaOwnership (automático, sem GC)Manual (malloc/free)
Sistema de tiposRico (enums, generics, traits)Básico (structs, unions, typedef)
Build systemCargo (unificado)Make, CMake, Meson (fragmentado)
Gerenciamento de dependênciascrates.io (integrado)Nenhum padrão (pkg-config, conan)
PerformanceEquivalente a CReferência de performance
ABINão estável (usa C ABI para FFI)ABI estável de fato
PortabilidadeMuito boa (LLVM)Máxima (compilador para tudo)
ConcorrênciaThread safety no compiladorSem garantias (pthreads manual)

Segurança de Memória: O Problema Fundamental

Segundo a NSA, Microsoft e Google, 60-70% de todas as vulnerabilidades de segurança em software de sistemas são causadas por erros de memória em C e C++. Vamos ver os erros mais comuns:

Buffer Overflow em C

#include <stdio.h>
#include <string.h>

void processar_entrada(const char *entrada) {
    char buffer[64];
    // PERIGO: strcpy não verifica limites!
    strcpy(buffer, entrada);
    printf("Processado: %s\n", buffer);
}

int main(void) {
    // Se entrada tiver mais de 63 chars, overflow!
    char entrada_longa[256];
    memset(entrada_longa, 'A', 255);
    entrada_longa[255] = '\0';

    processar_entrada(entrada_longa); // Buffer overflow!
    return 0;
}

O Equivalente Seguro em Rust

fn processar_entrada(entrada: &str) {
    // String em Rust sempre conhece seu tamanho — overflow é impossível
    let buffer: String = entrada.chars().take(64).collect();
    println!("Processado: {buffer}");
}

fn main() {
    let entrada_longa = "A".repeat(255);
    processar_entrada(&entrada_longa); // Seguro, trunca automaticamente
}

Use-After-Free em C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    char *nome = malloc(32);
    if (!nome) return 1;
    strcpy(nome, "Rust Brasil");

    free(nome);

    // Use-after-free: comportamento indefinido!
    // Pode funcionar, crashar ou ser explorado
    printf("Nome: %s\n", nome);

    return 0;
}

Rust Impede Use-After-Free

fn main() {
    let nome = String::from("Rust Brasil");
    drop(nome); // Libera a memória explicitamente

    // ERRO DE COMPILAÇÃO: value used after move
    // println!("Nome: {nome}");
}

Double Free em C

#include <stdlib.h>

int main(void) {
    int *dados = malloc(100 * sizeof(int));
    if (!dados) return 1;

    free(dados);
    free(dados); // Double free: undefined behavior!

    return 0;
}

Em Rust, double free é estruturalmente impossível: cada valor tem exatamente um owner, e o drop é chamado automaticamente quando o owner sai de escopo. Não existe como liberar a mesma memória duas vezes.

FFI: Interoperabilidade Rust-C

Uma das maiores vantagens do Rust é sua interoperabilidade nativa com C via FFI (Foreign Function Interface). Isso permite adotar Rust gradualmente em projetos C existentes.

Chamando C a partir de Rust

// Declaração da função C
extern "C" {
    fn abs(n: i32) -> i32;
    fn strlen(s: *const std::ffi::c_char) -> usize;
}

fn main() {
    unsafe {
        let valor = abs(-42);
        println!("Valor absoluto: {valor}"); // 42

        let texto = std::ffi::CString::new("Olá Rust").unwrap();
        let tamanho = strlen(texto.as_ptr());
        println!("Tamanho: {tamanho}"); // 8
    }
}

Expondo Rust para C

// lib.rs — biblioteca Rust utilizável por código C
use std::ffi::{c_char, CStr, CString};
use std::ptr;

#[no_mangle]
pub extern "C" fn somar_vetor(dados: *const f64, tamanho: usize) -> f64 {
    if dados.is_null() || tamanho == 0 {
        return 0.0;
    }
    let slice = unsafe { std::slice::from_raw_parts(dados, tamanho) };
    slice.iter().sum()
}

#[no_mangle]
pub extern "C" fn criar_saudacao(nome: *const c_char) -> *mut c_char {
    let nome_str = unsafe {
        if nome.is_null() {
            return ptr::null_mut();
        }
        CStr::from_ptr(nome).to_str().unwrap_or("mundo")
    };

    let saudacao = format!("Olá, {nome_str}!");
    CString::new(saudacao)
        .map(|s| s.into_raw())
        .unwrap_or(ptr::null_mut())
}

#[no_mangle]
pub extern "C" fn liberar_string(s: *mut c_char) {
    if !s.is_null() {
        unsafe {
            drop(CString::from_raw(s));
        }
    }
}
/* main.c — código C chamando a biblioteca Rust */
#include <stdio.h>

extern double somar_vetor(const double *dados, size_t tamanho);
extern char *criar_saudacao(const char *nome);
extern void liberar_string(char *s);

int main(void) {
    double valores[] = {1.5, 2.5, 3.0, 4.0};
    double soma = somar_vetor(valores, 4);
    printf("Soma: %.1f\n", soma); /* Soma: 11.0 */

    char *msg = criar_saudacao("Comunidade");
    if (msg) {
        printf("%s\n", msg); /* Olá, Comunidade! */
        liberar_string(msg);
    }

    return 0;
}

Para compilar e linkar esses exemplos em diferentes plataformas, veja nosso guia de cross-compilation.

Exemplo Prático: Linked List

Uma linked list é um exercício clássico que demonstra as diferenças de gestão de memória.

C com malloc/free

#include <stdio.h>
#include <stdlib.h>

typedef struct No {
    int valor;
    struct No *proximo;
} No;

No *criar_no(int valor) {
    No *no = malloc(sizeof(No));
    if (!no) return NULL;
    no->valor = valor;
    no->proximo = NULL;
    return no;
}

void inserir(No **cabeca, int valor) {
    No *novo = criar_no(valor);
    if (!novo) return;
    novo->proximo = *cabeca;
    *cabeca = novo;
}

void imprimir(const No *cabeca) {
    const No *atual = cabeca;
    while (atual) {
        printf("%d -> ", atual->valor);
        atual = atual->proximo;
    }
    printf("NULL\n");
}

void liberar(No *cabeca) {
    while (cabeca) {
        No *temp = cabeca;
        cabeca = cabeca->proximo;
        free(temp);
    }
}

int main(void) {
    No *lista = NULL;
    inserir(&lista, 3);
    inserir(&lista, 2);
    inserir(&lista, 1);
    imprimir(lista);   /* 1 -> 2 -> 3 -> NULL */
    liberar(lista);    /* Esquecer essa linha = memory leak */
    return 0;
}

Rust com Box

type Link = Option<Box<No>>;

struct No {
    valor: i32,
    proximo: Link,
}

struct Lista {
    cabeca: Link,
}

impl Lista {
    fn new() -> Self {
        Lista { cabeca: None }
    }

    fn inserir(&mut self, valor: i32) {
        let novo = Box::new(No {
            valor,
            proximo: self.cabeca.take(),
        });
        self.cabeca = Some(novo);
    }

    fn imprimir(&self) {
        let mut atual = &self.cabeca;
        while let Some(no) = atual {
            print!("{} -> ", no.valor);
            atual = &no.proximo;
        }
        println!("None");
    }
}
// Drop é chamado automaticamente — sem memory leak possível

fn main() {
    let mut lista = Lista::new();
    lista.inserir(3);
    lista.inserir(2);
    lista.inserir(1);
    lista.imprimir(); // 1 -> 2 -> 3 -> None
} // lista é automaticamente liberada aqui, recursivamente

Em C, esquecer de chamar liberar() causa memory leak. Em Rust, a memória é liberada automaticamente quando lista sai de escopo.

Comparação de Performance

Benchmarks

BenchmarkRustCDiferença
Fibonacci(45)3,2s3,1s~3% C
Sort 100M inteiros4,7s4,8s~2% Rust
Malloc/free 10M0,18s0,15s~20% C
Regex 500MB0,28s0,31s (PCRE2)~10% Rust
HTTP req/s850k (axum)900k (libuv)~6% C

A performance é efetivamente equivalente. Rust pode ser marginalmente mais lento em alocações individuais (devido a verificações de bounds checking), mas frequentemente é mais rápido em código real graças a otimizações do iterador e melhor inlining.

Rust no Linux Kernel

Desde 2022, o Linux kernel aceita contribuições em Rust. Isso representa uma mudança histórica:

  • Drivers de dispositivo estão sendo escritos em Rust
  • Subsistemas novos podem escolher Rust como linguagem
  • O projeto Rust for Linux mantém bindings seguros para as APIs do kernel
  • Linus Torvalds aprovou pessoalmente a integração

Isso não significa que o kernel será reescrito em Rust — o código C existente (30+ milhões de linhas) continuará sendo mantido. Mas novos módulos têm a opção de usar Rust para maior segurança.

Quando Usar C

Escolha C quando:

  • O projeto já é em C: manter consistência em bases de código existentes
  • ABI estável é obrigatório: bibliotecas que precisam ser linkadas por qualquer linguagem
  • Plataforma sem suporte Rust: microcontroladores muito pequenos, plataformas obscuras
  • Tamanho mínimo absoluto: C pode produzir binários menores (sem runtime Rust)
  • Padrão da indústria: MISRA C, AUTOSAR, POSIX strict

Quando Usar Rust

Escolha Rust quando:

  • Projeto novo de sistemas: sem legado para manter
  • Segurança de memória importa: infraestrutura, networking, criptografia
  • Concorrência é necessária: Rust garante thread safety em compilação
  • Produtividade do desenvolvedor: Cargo, enums, pattern matching, iteradores
  • Manutenção a longo prazo: refatoração segura, menos bugs em produção

Conclusão e Recomendação

Para novos projetos de sistemas em 2026, Rust é a escolha recomendada. Você obtém performance equivalente a C com segurança de memória garantida, um sistema de build moderno (Cargo), gerenciamento de dependências integrado e abstrações de alto nível que não custam nada em runtime.

Para projetos C existentes, a estratégia mais inteligente é adotar Rust nos módulos novos e manter o código C existente. A FFI entre Rust e C é excelente e permite migração gradual. Não reescreva tudo de uma vez — o kernel Linux está mostrando o caminho com adoção incremental.

Se você programa em C, aprender Rust vai torná-lo mais produtivo e consciente sobre segurança de memória. Muitos conceitos que em C exigem disciplina e atenção manual são automatizados pelo compilador do Rust.


Veja Também