Capítulo 19: Unsafe Rust, Ponteiros Brutos e FFI (C Interop)

Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance


🗺️ Mapa Conceitual do Encapsulamento Seguro em Unsafe Rust e FFI

flowchart TD
    CodigoCliente["Código Seguro do Usuário (Safe Rust)"] --> ApiSegura["API Pública Segura (Contratos Fortes e Invariantes)"]

    subgraph ISOLAMENTO_UNSAFE["Fronteira Estrita de Isolamento (Unsafe Boundary)"]
        BlocoUnsafe["Bloco unsafe { ... }"]
        RawPtrs["Ponteiros Brutos: *const T e *mut T"]
        FFI_C["Chamadas Externas: extern 'C' (libc, Windows API, Drivers)"]
    end

    ApiSegura --> ISOLAMENTO_UNSAFE
    ISOLAMENTO_UNSAFE --> HardwareNativo["Acesso Direto à Memória / Registradores / SO"]

    ApiSegura -.->|"Blindagem Total: Erros Não Vazam para o Usuário"| CodigoCliente

    style CodigoCliente fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style ApiSegura fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style ISOLAMENTO_UNSAFE fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style BlocoUnsafe fill:#ffebee,stroke:#f44336,stroke-width:2px
    style HardwareNativo fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

🏛️ 1. O que é o Unsafe Rust e Por que Ele Existe?

O compilador do Rust é deliberadamente conservador: se ele não conseguir provar matematicamente que um trecho de código é 100% seguro contra violações de memória, ele rejeita a compilação, mesmo que o programador saiba que a operação é válida.

No entanto, sistemas operacionais, drivers de vídeo, motores de bancos de dados e rotinas de comunicação direta com registradores de hardware precisam realizar operações de baixo nível que o compilador não consegue auditar sozinho. Para esses cenários, o Rust fornece uma válvula de escape controlada: o Unsafe Rust.

O que o unsafe NÃO Faz:

  • O unsafe não desativa o Borrow Checker.
  • O unsafe não desativa a checagem de tipos.
  • O unsafe apenas concede acesso a 5 superpoderes restritos.

⚡ 2. Os 5 Superpoderes do unsafe

Dentro de um bloco unsafe { ... }, você tem permissão exclusiva para:

  1. Desreferenciar Ponteiros Brutos (Raw Pointers): Ler ou escrever em ponteiros *const T e *mut T.
  2. Invocar Funções ou Métodos unsafe: Incluindo chamadas de bibliotecas escritas em C/C++ (FFI).
  3. Implementar Traits unsafe: Como garantir manualmente que um tipo customizado é Send ou Sync.
  4. Mutar Variáveis Estáticas Mutáveis: Manipular static mut.
  5. Acessar Campos de uma union: Estruturas de estilo C com sobreposição de memória.

📍 3. Ponteiros Brutos: *const T e *mut T

Diferente das referências normais (&T e &mut T), os ponteiros brutos:

  • Podem ser nulos (null pointers).
  • Podem ter alinhamento arbitrário de memória.
  • Não possuem lifetimes e não são rastreados pelo Borrow Checker.
  • Não possuem garantia de limpeza automática (não executam Drop).
let mut numero: i32 = 42;

// Criar ponteiros brutos é 100% seguro e NÃO exige 'unsafe':
let ptr_imutavel: *const i32 = &numero as *const i32;
let ptr_mutavel: *mut i32 = &mut numero as *mut i32;

// APENAS desreferenciar (ler ou escrever no endereço) exige o bloco 'unsafe':
unsafe {
    println!("Lendo valor via ponteiro bruto: {}", *ptr_imutavel);
    *ptr_mutavel += 58;
    println!("Novo valor após escrita direta: {}", *ptr_mutavel);
}

🌉 4. FFI: Interoperabilidade Direta com a Linguagem C

Para se comunicar com bibliotecas legadas do sistema operacional (Windows API, POSIX, OpenSSL ou libc), o Rust utiliza o bloco extern "C":

use std::ffi::{CStr, CString};

// Declaração de assinaturas de funções C externas
extern "C" {
    fn puts(s: *const i8) -> i32;
    fn abs(x: i32) -> i32;
}

fn main() {
    // 1. Invocando função simples de C
    let valor_abs = unsafe { abs(-99) };
    println!("Resultado de abs(-99) em C: {}", valor_abs);

    // 2. Passando strings compatíveis com C (terminadas em null byte '\0')
    let mensagem = CString::new("Rust comunicando diretamente com o kernel C!").unwrap();
    unsafe {
        puts(mensagem.as_ptr());
    }
}

4.1 O Atributo #[repr(C)]

Ao compartilhar structs com código em C, é mandatório anotar a struct com #[repr(C)] para instruir o compilador a organizar os campos com o mesmo alinhamento de memória e padding adotados pelos compiladores C (GCC/Clang/MSVC):

#[repr(C)]
pub struct CabecalhoPacoteC {
    pub versao: u8,
    pub flags: u8,
    pub checksum: u16,
    pub tamanho_payload: u32,
}

🛡️ 5. O Princípio da Abstração Segura (Safe Abstraction)

Na engenharia de software profissional em Rust, a regra suprema é:

O código unsafe deve ser restrito ao menor escopo possível e encapsulado dentro de uma API pública 100% segura.

O usuário que consome sua biblioteca jamais deve ser obrigado a escrever unsafe. A sua struct pública deve garantir todas as invariantes de tamanho, limites de array e validade de ponteiros internamente.


⚖️ 6. Matriz Comparativa: Referências vs Raw Pointers

Característica Referências Seguras (&T, &mut T) Ponteiros Brutos (*const T, *mut T)
Pode Ser Nulo? NUNCA (Garantia matemática) SIM (std::ptr::null())
Rastreado pelo Borrow Checker? SIM (Aliasing XOR Mutability) NÃO (Você assume a responsabilidade)
Validação de Lifetimes? SIM NÃO
Exige Bloco unsafe para Criar? Não Não
Exige Bloco unsafe para Desreferenciar? Não SIM

⚠️ 7. Armadilhas Críticas e o Perigo do Undefined Behavior (UB)

Escrever código unsafe incorreto pode resultar em Undefined Behavior (Comportamento Indefinido - UB), onde o compilador LLVM assume otimizações falsas que corrompem a execução.

  • Exemplos de UB: Criar referências sobrepostas não-alinhadas, violar a regra de unicidade de &mut mesmo dentro de um bloco unsafe, ou ler memória não inicializada.
  • Ferramenta de Auditoria: Utilize o Miri (cargo miri test), o interpretador oficial do Rust que executa sua suíte de testes detectando qualquer violação de regras de ponteiros e UB em tempo de execução!

📋 8. Cheat-Sheet: Resumo Executivo

// Ponteiro bruto
let mut x = 10;
let p: *mut i32 = &mut x;
unsafe { *p = 20; }

// FFI com C
extern "C" { fn getpid() -> i32; }
let pid = unsafe { getpid() };

// Struct compatível com C
#[repr(C)]
struct PontoC { x: f32, y: f32 }