Unsafe Rust: quando e como usar

1. Introdução ao Unsafe Rust

1.1 O que é unsafe e por que existe no Rust

Rust é conhecido por suas garantias de segurança de memória em tempo de compilação. No entanto, existem cenários onde o compilador não pode verificar automaticamente se o código é seguro. Para esses casos, Rust oferece o bloco unsafe, que permite ao programador assumir a responsabilidade por garantir a segurança.

O unsafe não desliga o borrow checker ou outras verificações de segurança — ele simplesmente concede acesso a operações que o compilador não pode verificar.

1.2 Diferença fundamental entre código seguro e inseguro

No código seguro do Rust, o compilador garante:
- Sem ponteiros nulos
- Sem data races
- Sem uso após liberação (use-after-free)
- Sem violações de aliasing

No código unsafe, o programador deve garantir manualmente essas invariantes.

1.3 A promessa do unsafe: responsabilidade do programador

Usar unsafe significa: "Eu, programador, prometo ao compilador que este código é seguro, mesmo que você não consiga provar isso."

2. As Superpotências do Unsafe: O que ele Permite Fazer

2.1 Desreferenciar ponteiros brutos (*const T e *mut T)

let x = 42;
let raw_ptr = &x as *const i32;

unsafe {
    println!("Valor através do ponteiro bruto: {}", *raw_ptr);
}

Ponteiros brutos podem ser nulos, dangling ou apontar para memória inválida — o compilador não verifica nada disso.

2.2 Chamar funções externas via FFI

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Valor absoluto de -3: {}", abs(-3));
    }
}

2.3 Acessar e modificar variáveis estáticas mutáveis

static mut CONTADOR: u32 = 0;

fn incrementar() {
    unsafe {
        CONTADOR += 1;
    }
}

2.4 Implementar traits inseguros

struct MeuTipo(*const u8);

unsafe impl Send for MeuTipo {}
unsafe impl Sync for MeuTipo {}

3. Quando Usar Unsafe: Cenários Legítimos

3.1 Otimizações de desempenho críticas

fn soma_segura(v: &[i32]) -> i32 {
    v.iter().sum()
}

fn soma_otimizada(v: &[i32]) -> i32 {
    let mut soma = 0;
    let ptr = v.as_ptr();
    let len = v.len();

    unsafe {
        for i in 0..len {
            soma += *ptr.add(i); // Evita bounds checking
        }
    }
    soma
}

3.2 Interoperabilidade com código C/C++

use std::ffi::CString;
use std::os::raw::c_char;

extern "C" {
    fn strlen(s: *const c_char) -> usize;
}

fn rust_strlen(s: &str) -> usize {
    let c_str = CString::new(s).expect("CString falhou");
    unsafe { strlen(c_str.as_ptr()) }
}

3.3 Implementação de estruturas de dados de baixo nível

struct MeuVec<T> {
    ptr: *mut T,
    len: usize,
    cap: usize,
}

impl<T> MeuVec<T> {
    fn new() -> Self {
        MeuVec {
            ptr: std::ptr::null_mut(),
            len: 0,
            cap: 0,
        }
    }

    fn push(&mut self, valor: T) {
        if self.len == self.cap {
            self.crescer();
        }
        unsafe {
            std::ptr::write(self.ptr.add(self.len), valor);
            self.len += 1;
        }
    }
}

3.4 Manipulação direta de hardware

// Exemplo conceitual para acesso a registradores de hardware
const GPIO_BASE: *mut u32 = 0x3F200000 as *mut u32;

fn escrever_gpio(valor: u32) {
    unsafe {
        std::ptr::write_volatile(GPIO_BASE, valor);
    }
}

4. Regras de Ouro para Usar Unsafe com Segurança

4.1 Nunca violar o borrow checker

// ERRADO: viola regras de aliasing
let mut data = vec![1, 2, 3];
let ptr = &data[0] as *const i32;
data.push(4); // Invalida o ponteiro
unsafe { println!("{}", *ptr); } // UB!

4.2 Garantir que ponteiros sejam válidos e alinhados

fn validar_alinhamento<T>(ptr: *const T) -> bool {
    let align = std::mem::align_of::<T>();
    (ptr as usize) % align == 0
}

4.3 Evitar data races

use std::sync::atomic::{AtomicU32, Ordering};

static CONTADOR_SEGURO: AtomicU32 = AtomicU32::new(0);

fn incrementar_seguro() {
    CONTADOR_SEGURO.fetch_add(1, Ordering::SeqCst);
}

4.4 Documentar pré-condições de segurança

/// # Safety
///
/// - `ptr` deve ser não nulo e alinhado
/// - `ptr` deve apontar para memória inicializada do tipo `T`
/// - O chamador deve garantir que ninguém mais está escrevendo em `ptr`
unsafe fn ler_valor<T>(ptr: *const T) -> T {
    std::ptr::read(ptr)
}

5. Padrões e Boas Práticas com Unsafe

5.1 Isolar o código unsafe em funções pequenas

// Bom: função pequena e verificável
unsafe fn inverter_bytes(ptr: *mut u8, len: usize) {
    for i in 0..len / 2 {
        let a = ptr.add(i);
        let b = ptr.add(len - 1 - i);
        std::ptr::swap(a, b);
    }
}

5.2 Criar APIs seguras por cima de implementações inseguras

struct Buffer {
    data: Vec<u8>,
}

impl Buffer {
    fn new(tamanho: usize) -> Self {
        Buffer { data: vec![0; tamanho] }
    }

    fn escrever(&mut self, indice: usize, byte: u8) {
        // API segura que internamente pode usar unsafe
        self.data[indice] = byte;
    }
}

5.3 Uso de unsafe dentro de blocos delimitados

// Prefira blocos a funções inteiras unsafe
fn processar_dados(dados: &mut [u8]) {
    // Código seguro aqui
    let ptr = dados.as_mut_ptr();
    let len = dados.len();

    unsafe {
        // Apenas a parte realmente insegura
        for i in 0..len {
            *ptr.add(i) = dados[i].wrapping_add(1);
        }
    }
    // Mais código seguro aqui
}

5.4 Testar exaustivamente com ferramentas

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_inverter_bytes() {
        let mut dados = vec![1, 2, 3, 4];
        unsafe { inverter_bytes(dados.as_mut_ptr(), dados.len()) };
        assert_eq!(dados, vec![4, 3, 2, 1]);
    }
}

6. Armadilhas Comuns e Erros Fatais

6.1 Undefined behavior

// UB: criar referência a partir de ponteiro nulo
unsafe {
    let ptr: *const i32 = std::ptr::null();
    let _ref = &*ptr; // UB!
}

6.2 Uso após liberação

let ptr: *const i32;
{
    let x = 42;
    ptr = &x as *const i32;
}
// ptr agora é dangling!
unsafe { println!("{}", *ptr); } // UB!

6.3 Invariantes de Send/Sync mal implementadas

struct NaoSync(*const u8);
unsafe impl Sync for NaoSync {} // MENTIRA! Isso pode causar data races

6.4 Exemplos de bugs reais

Um bug comum é esquecer de considerar o alinhamento ao fazer casts de ponteiros:

// ERRADO: alinhamento pode estar incorreto
let bytes: [u8; 4] = [1, 0, 0, 0];
let ptr = &bytes as *const u8 as *const u32;
unsafe {
    println!("{}", *ptr); // Pode crashar em arquiteturas que exigem alinhamento
}

7. Alternativas ao Unsafe: Quando Não Usar

7.1 Soluções seguras disponíveis

// Em vez de unsafe, use:
use std::cell::RefCell;
use std::sync::Mutex;

// RefCell para mutabilidade interior em single-thread
let celula = RefCell::new(42);
*celula.borrow_mut() += 1;

// Mutex para acesso concorrente seguro
let mutex = Mutex::new(42);
*mutex.lock().unwrap() += 1;

7.2 Uso de Pin, Cell e RefCell

use std::pin::Pin;

// Pin garante que um valor não será movido na memória
struct Imovel {
    dados: Vec<u8>,
}

let imovel = Imovel { dados: vec![1, 2, 3] };
let pinado = Pin::new(&imovel);

7.3 Quando o custo supera os benefícios

Antes de usar unsafe, pergunte-se:
- O ganho de performance é mensurável?
- Existe uma crate segura que faz o mesmo?
- Você entende todas as implicações de segurança?

7.4 Ferramentas de profiling

// Use perf, flamegraph, ou o profiler integrado do Rust
// para confirmar que unsafe é realmente necessário
fn main() {
    let inicio = std::time::Instant::now();
    // Código a ser testado
    println!("Tempo: {:?}", inicio.elapsed());
}

8. Conclusão

unsafe é uma ferramenta poderosa, mas perigosa. Use-a como último recurso, isole o código inseguro em pequenas funções bem documentadas, e sempre teste exaustivamente com ferramentas como Miri e sanitizers.

Lembre-se: unsafe não significa "código ruim" — significa "código que requer mais cuidado". A comunidade Rust valoriza a segurança, mas reconhece que para certos cenários (FFI, otimizações críticas, hardware), o unsafe é necessário.

Referências