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
| Aspecto | Rust | C |
|---|---|---|
| Ano de criação | 2015 | 1972 |
| Segurança de memória | Garantida pelo compilador | Responsabilidade total do programador |
| Undefined Behavior | Impossível em safe Rust | Centenas de formas possíveis |
| Gerenciamento de memória | Ownership (automático, sem GC) | Manual (malloc/free) |
| Sistema de tipos | Rico (enums, generics, traits) | Básico (structs, unions, typedef) |
| Build system | Cargo (unificado) | Make, CMake, Meson (fragmentado) |
| Gerenciamento de dependências | crates.io (integrado) | Nenhum padrão (pkg-config, conan) |
| Performance | Equivalente a C | Referência de performance |
| ABI | Não estável (usa C ABI para FFI) | ABI estável de fato |
| Portabilidade | Muito boa (LLVM) | Máxima (compilador para tudo) |
| Concorrência | Thread safety no compilador | Sem 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
| Benchmark | Rust | C | Diferença |
|---|---|---|---|
| Fibonacci(45) | 3,2s | 3,1s | ~3% C |
| Sort 100M inteiros | 4,7s | 4,8s | ~2% Rust |
| Malloc/free 10M | 0,18s | 0,15s | ~20% C |
| Regex 500MB | 0,28s | 0,31s (PCRE2) | ~10% Rust |
| HTTP req/s | 850k (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
- Cross-Compilation em Rust — Compile para diferentes arquiteturas e plataformas
- Rust vs C++: Segurança sem Sacrificar Performance — Compare com o sucessor do C
- Rust vs Zig: Novas Linguagens de Sistemas — Outra alternativa moderna ao C
- Entendendo o Erro E0382: Uso Após Move — Conceito fundamental de ownership
- Tutorial: Ownership e Borrowing — O modelo de memória do Rust explicado
- Glossário Rust — Termos como FFI, unsafe, ownership e borrowing