Ponteiros em Rust: Módulo std::ptr Guia | Rust Brasil

Guia completo do módulo std::ptr em Rust: raw pointers, null, read, write, copy e segurança de ponteiros. Referência em português.

Visao Geral do Modulo std::ptr

O modulo std::ptr fornece funcoes e tipos para trabalhar com ponteiros raw (*const T e *mut T) em Rust. Ponteiros raw sao o nivel mais baixo de acesso a memoria — eles nao carregam informacoes de lifetime, nao garantem validade e podem ser nulos.

Diferente de referencias (&T e &mut T), ponteiros raw:

  • Podem ser nulos
  • Podem apontar para memoria invalida
  • Nao seguem regras de aliasing
  • Podem ser criados em codigo safe, mas so podem ser desreferenciados em blocos unsafe

O uso principal de std::ptr e em:

  • FFI (Foreign Function Interface) — interoperabilidade com C
  • Implementacao de estruturas de dados — listas ligadas, alocadores customizados
  • Otimizacoes de baixo nivel — quando as abstracoes safe tem custo inaceitavel

Aviso: Todo desreferenciamento de ponteiro raw requer unsafe. Use ponteiros raw apenas quando nao houver alternativa safe. Quando usar, documente as invariantes de seguranca com comentarios // SAFETY:.


Tipos e Funcoes Principais

Tipos de Ponteiro

TipoDescricao
*const TPonteiro raw imutavel
*mut TPonteiro raw mutavel
NonNull<T>Ponteiro nao-nulo (otimizado para Option)

Funcoes do Modulo

FuncaoDescricao
null::<T>()Cria um *const T nulo
null_mut::<T>()Cria um *mut T nulo
read(src)Le o valor apontado (unsafe)
write(dst, val)Escreve um valor no destino (unsafe)
read_unaligned(src)Le de endereco nao-alinhado (unsafe)
write_unaligned(dst, val)Escreve em endereco nao-alinhado (unsafe)
copy(src, dst, count)Copia count elementos (regioes podem se sobrepor, unsafe)
copy_nonoverlapping(src, dst, count)Copia sem sobreposicao (unsafe)
swap(a, b)Troca valores via ponteiros (unsafe)
drop_in_place(ptr)Chama o destrutor do valor apontado (unsafe)
addr_of!(expr)Cria *const T sem criar referencia
addr_of_mut!(expr)Cria *mut T sem criar referencia

Metodos de Ponteiro

MetodoDescricao
.is_null()Verifica se e nulo
.offset(count)Desloca por count elementos (unsafe)
.add(count)Avanca count elementos (unsafe)
.sub(count)Retrocede count elementos (unsafe)
.as_ref()Converte para Option<&T> (unsafe)
.as_mut()Converte para Option<&mut T> (unsafe)
.cast::<U>()Converte para *const U ou *mut U

Exemplos Praticos

1. Criando e Usando Ponteiros Raw

fn main() {
    let valor = 42i32;
    let referencia = &valor;

    // Criando ponteiros raw a partir de referencias (safe)
    let ptr_const: *const i32 = &valor;
    let ptr_const2: *const i32 = referencia as *const i32;

    // Verificando nulidade (safe)
    println!("E nulo? {}", ptr_const.is_null()); // false

    // Desreferenciar requer unsafe
    // SAFETY: ptr_const foi criado a partir de uma referencia valida
    // e o valor original ainda esta vivo
    unsafe {
        println!("Valor: {}", *ptr_const); // 42
    }

    // Ponteiro mutavel
    let mut numero = 10;
    let ptr_mut: *mut i32 = &mut numero;

    // SAFETY: ptr_mut aponta para 'numero' que esta vivo e acessivel
    unsafe {
        *ptr_mut = 20;
    }
    println!("Modificado: {numero}"); // 20

    // Ponteiro nulo
    let nulo: *const i32 = std::ptr::null();
    println!("E nulo? {}", nulo.is_null()); // true
}

2. NonNull — Ponteiro Garantidamente Nao-Nulo

use std::ptr::NonNull;

struct Node<T> {
    valor: T,
    proximo: Option<NonNull<Node<T>>>,
}

impl<T: std::fmt::Display> Node<T> {
    fn new(valor: T) -> Self {
        Node { valor, proximo: None }
    }

    fn imprimir_lista(&self) {
        print!("{}", self.valor);
        let mut atual = self.proximo;

        while let Some(ptr) = atual {
            // SAFETY: NonNull garante que o ponteiro nao e nulo,
            // e mantemos a invariante de que todos os nos sao validos
            unsafe {
                let node = ptr.as_ref();
                print!(" -> {}", node.valor);
                atual = node.proximo;
            }
        }
        println!();
    }
}

fn main() {
    let mut n1 = Node::new(1);
    let mut n2 = Node::new(2);
    let n3 = Node::new(3);

    // Conectando os nos
    n2.proximo = NonNull::new(&n3 as *const Node<i32> as *mut Node<i32>);
    n1.proximo = NonNull::new(&n2 as *const Node<i32> as *mut Node<i32>);

    n1.imprimir_lista(); // 1 -> 2 -> 3

    // NonNull tem o mesmo tamanho que *mut T
    println!(
        "Tamanho de Option<NonNull<i32>>: {} bytes",
        std::mem::size_of::<Option<NonNull<i32>>>()
    ); // 8 (mesma otimizacao que Option<&T>)
}

3. Aritmetica de Ponteiros e Acesso a Arrays

fn main() {
    let dados = [10, 20, 30, 40, 50];
    let ptr = dados.as_ptr();

    // Acessando elementos via aritmetica de ponteiros
    for i in 0..dados.len() {
        // SAFETY: i esta dentro dos limites do array,
        // entao ptr.add(i) aponta para um elemento valido
        unsafe {
            let valor = *ptr.add(i);
            println!("dados[{i}] = {valor}");
        }
    }

    // Copiando dados entre arrays
    let origem = [1u32, 2, 3, 4];
    let mut destino = [0u32; 4];

    // SAFETY: origem e destino nao se sobrepoe em memoria,
    // ambos tem pelo menos 4 elementos de u32
    unsafe {
        std::ptr::copy_nonoverlapping(
            origem.as_ptr(),
            destino.as_mut_ptr(),
            4,
        );
    }

    println!("Destino: {destino:?}"); // [1, 2, 3, 4]
}

4. Padrao FFI — Interoperabilidade com C

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

// Simulando uma funcao C que retorna uma string
fn funcao_c_simulada() -> *const c_char {
    b"Resultado da funcao C\0".as_ptr() as *const c_char
}

// Simulando uma funcao C que recebe um ponteiro para preencher
fn c_preencher_buffer(buf: *mut i32, tamanho: usize) {
    for i in 0..tamanho {
        // SAFETY: assumimos que buf aponta para memoria valida
        // com pelo menos 'tamanho' elementos
        unsafe {
            buf.add(i).write(i as i32 * 100);
        }
    }
}

fn main() {
    // Recebendo string de C
    let ptr_c = funcao_c_simulada();
    if !ptr_c.is_null() {
        // SAFETY: verificamos que nao e nulo e a string
        // termina com null byte
        let texto = unsafe { CStr::from_ptr(ptr_c) };
        println!("{}", texto.to_string_lossy());
    }

    // Passando buffer para funcao C preencher
    let mut buffer = vec![0i32; 5];

    // SAFETY: buffer tem exatamente 5 elementos alocados
    c_preencher_buffer(buffer.as_mut_ptr(), buffer.len());

    println!("Buffer: {buffer:?}"); // [0, 100, 200, 300, 400]
}

5. addr_of! e addr_of_mut! para Campos Nao-Alinhados

use std::ptr;

#[repr(packed)]
struct Pacote {
    flag: u8,
    valor: u32,  // Nao alinhado em 4 bytes por causa de #[repr(packed)]
    dados: u64,
}

fn main() {
    let pacote = Pacote {
        flag: 1,
        valor: 42,
        dados: 0xDEADBEEF,
    };

    // ERRADO: &pacote.valor criaria uma referencia nao-alinhada (UB!)
    // let ref_valor = &pacote.valor; // Isso e UB em repr(packed)!

    // CORRETO: addr_of! cria um ponteiro raw sem criar referencia
    let ptr_valor = ptr::addr_of!(pacote.valor);

    // SAFETY: usamos read_unaligned porque o campo pode nao estar alinhado
    let valor = unsafe { ptr::read_unaligned(ptr_valor) };
    println!("Valor: {valor}"); // 42

    let ptr_dados = ptr::addr_of!(pacote.dados);
    let dados = unsafe { ptr::read_unaligned(ptr_dados) };
    println!("Dados: {dados:#X}"); // 0xDEADBEEF
}

Padroes Comuns

Verificacao de Nulidade Antes de Usar

Sempre verifique ponteiros recebidos de FFI:

fn processar_ponteiro(ptr: *const i32) -> Option<i32> {
    if ptr.is_null() {
        return None;
    }
    // SAFETY: acabamos de verificar que nao e nulo.
    // Assumimos que o chamador garante que o ponteiro
    // aponta para memoria valida.
    Some(unsafe { *ptr })
}

Convertendo Entre Ponteiros e Referencias

fn ponteiro_para_referencia<'a>(ptr: *const i32) -> Option<&'a i32> {
    // SAFETY: as_ref() retorna None se o ponteiro for nulo.
    // O chamador deve garantir que o ponteiro aponta para
    // memoria valida pelo lifetime 'a.
    unsafe { ptr.as_ref() }
}

Usando NonNull como Campo de Struct

use std::ptr::NonNull;

struct Buffer {
    ptr: NonNull<u8>,
    len: usize,
}

// SAFETY: NonNull nao implementa Send/Sync automaticamente
// Implementamos apenas se tivermos ownership exclusivo
unsafe impl Send for Buffer {}
unsafe impl Sync for Buffer {}

Quando Usar (e Quando Nao Usar)

Use std::ptr quando:

  • Estiver fazendo FFI com C/C++
  • Implementando estruturas de dados com ponteiros (listas, arvores)
  • Precisar de addr_of! para structs #[repr(packed)]
  • Implementando alocadores de memoria customizados
  • Otimizacoes de performance comprovadamente necessarias

Nao use std::ptr quando:

  • Referencias (&T, &mut T) resolverem o problema
  • Box, Rc, Arc atenderem a necessidade de ownership
  • Estiver tentando contornar o borrow checker (repense o design!)
  • Vec, HashMap ou outra colecao da stdlib for suficiente

Regra de ouro: Se voce precisa de ponteiros raw e nao esta fazendo FFI ou implementando uma estrutura de dados fundamental, provavelmente existe uma alternativa safe melhor.


Veja Tambem