Modulo std::mem em Rust: Manipulacao de Memoria

Guia completo do modulo std::mem em Rust: size_of, align_of, swap, replace, take, drop, forget, MaybeUninit e manipulacao segura de memoria.

Visao Geral do Modulo std::mem

O modulo std::mem fornece funcoes para inspecionar e manipular memoria em Rust. Ele permite consultar o tamanho e alinhamento de tipos, trocar valores entre variaveis, substituir valores in-place e trabalhar com memoria nao-inicializada de forma controlada.

A maioria das funcoes em std::mem e safe (segura) — como swap, replace, take e size_of. Porem, algumas funcoes como zeroed, transmute e MaybeUninit envolvem unsafe e devem ser usadas com extremo cuidado, pois violar suas invariantes causa comportamento indefinido.

Este modulo e essencial para:

  • Otimizacoes de performance (evitar clonagens desnecessarias)
  • Interoperabilidade com C/FFI
  • Programacao de baixo nivel e sistemas embarcados
  • Entender o layout de memoria dos tipos Rust

Funcoes e Tipos Principais

Funcoes de Inspecao

FuncaoDescricao
size_of::<T>()Tamanho de T em bytes
size_of_val(&val)Tamanho do valor em bytes
align_of::<T>()Alinhamento de T em bytes
align_of_val(&val)Alinhamento do valor em bytes
discriminant(&val)Discriminante de um enum
needs_drop::<T>()Se T precisa de destrutor

Funcoes de Manipulacao (Safe)

FuncaoDescricao
swap(&mut a, &mut b)Troca os valores de duas variaveis
replace(&mut dest, novo)Substitui o valor e retorna o antigo
take(&mut val)Substitui por Default e retorna o antigo
drop(val)Descarta o valor imediatamente
forget(val)“Esquece” o valor sem chamar Drop

Funcoes e Tipos Unsafe

Funcao/TipoDescricao
transmute::<A, B>(val)Reinterpreta os bits de um tipo como outro
zeroed::<T>()Cria valor com todos os bytes zero
MaybeUninit<T>Wrapper para valores possivelmente nao-inicializados

Exemplos Praticos

1. Inspecionando Tamanho e Alinhamento

use std::mem;

struct Ponto {
    x: f64,
    y: f64,
}

enum Forma {
    Circulo(f64),
    Retangulo(f64, f64),
    Triangulo(f64, f64, f64),
}

fn main() {
    // Tipos primitivos
    println!("bool:  {} bytes", mem::size_of::<bool>());   // 1
    println!("char:  {} bytes", mem::size_of::<char>());   // 4
    println!("i32:   {} bytes", mem::size_of::<i32>());    // 4
    println!("f64:   {} bytes", mem::size_of::<f64>());    // 8
    println!("usize: {} bytes", mem::size_of::<usize>());  // 8 (em 64-bit)

    // Tipos compostos
    println!("Ponto: {} bytes (alinhamento: {})",
        mem::size_of::<Ponto>(),
        mem::align_of::<Ponto>());
    // 16 bytes, alinhamento 8

    println!("Forma: {} bytes", mem::size_of::<Forma>()); // 32

    // Otimizacao de niche: Option<&T> tem o mesmo tamanho que &T
    println!("&str:          {} bytes", mem::size_of::<&str>());         // 16
    println!("Option<&str>:  {} bytes", mem::size_of::<Option<&str>>()); // 16!

    // Box e Option<Box> tambem tem o mesmo tamanho
    println!("Box<i32>:         {} bytes", mem::size_of::<Box<i32>>());         // 8
    println!("Option<Box<i32>>: {} bytes", mem::size_of::<Option<Box<i32>>>()); // 8!

    // Verificar se precisa de Drop
    println!("i32 precisa de drop? {}", mem::needs_drop::<i32>());      // false
    println!("String precisa de drop? {}", mem::needs_drop::<String>()); // true
}

2. swap, replace e take

Essas tres funcoes sao extremamente uteis para manipular valores sem clonagem:

use std::mem;

fn main() {
    // swap: troca dois valores sem copia temporaria explicita
    let mut a = String::from("primeiro");
    let mut b = String::from("segundo");
    mem::swap(&mut a, &mut b);
    println!("a={a}, b={b}"); // a=segundo, b=primeiro

    // replace: substitui e retorna o antigo
    let mut nome = String::from("Alice");
    let antigo = mem::replace(&mut nome, String::from("Bob"));
    println!("novo={nome}, antigo={antigo}"); // novo=Bob, antigo=Alice

    // take: substitui por Default::default() e retorna o antigo
    let mut buffer = vec![1, 2, 3, 4, 5];
    let dados = mem::take(&mut buffer);
    println!("dados={dados:?}");   // [1, 2, 3, 4, 5]
    println!("buffer={buffer:?}"); // [] (Vec vazio, que e o Default)
}

3. Uso Pratico de replace em Maquina de Estados

use std::mem;

enum Estado {
    Ocioso,
    Processando(Vec<u8>),
    Concluido(String),
}

impl Default for Estado {
    fn default() -> Self {
        Estado::Ocioso
    }
}

struct Maquina {
    estado: Estado,
}

impl Maquina {
    fn new() -> Self {
        Maquina { estado: Estado::Ocioso }
    }

    fn avancar(&mut self) {
        // replace permite "tirar" o estado atual e colocar um novo
        // sem precisar de Clone e sem borrow checker reclamando
        let estado_atual = mem::replace(&mut self.estado, Estado::Ocioso);

        self.estado = match estado_atual {
            Estado::Ocioso => {
                println!("Iniciando processamento...");
                Estado::Processando(vec![1, 2, 3])
            }
            Estado::Processando(dados) => {
                let resultado = format!("Processados {} bytes", dados.len());
                println!("{resultado}");
                Estado::Concluido(resultado)
            }
            Estado::Concluido(msg) => {
                println!("Reiniciando apos: {msg}");
                Estado::Ocioso
            }
        };
    }
}

fn main() {
    let mut maquina = Maquina::new();
    maquina.avancar(); // Ocioso -> Processando
    maquina.avancar(); // Processando -> Concluido
    maquina.avancar(); // Concluido -> Ocioso
}

4. drop e forget

use std::mem;

struct Recurso {
    nome: String,
}

impl Drop for Recurso {
    fn drop(&mut self) {
        println!("Liberando recurso: {}", self.nome);
    }
}

fn main() {
    let r1 = Recurso { nome: "banco_de_dados".into() };
    let r2 = Recurso { nome: "arquivo".into() };
    let r3 = Recurso { nome: "conexao".into() };

    // drop() libera imediatamente (chama Drop::drop)
    println!("Antes do drop explicito");
    drop(r1);
    println!("Apos drop explicito");

    // forget() "esquece" o valor SEM chamar Drop
    // CUIDADO: isso pode causar vazamento de memoria!
    mem::forget(r2);
    println!("r2 foi esquecido — nenhuma mensagem de liberacao");

    // r3 sera descartado automaticamente ao sair do escopo
    println!("Fim do main");
    // Saida: "Liberando recurso: conexao"
}

Atencao: mem::forget e safe em Rust, mas pode causar vazamento de recursos (memoria, file handles, etc.). Use apenas quando intencionalmente quiser evitar a chamada do destrutor, como ao transferir ownership para codigo C via FFI.

5. MaybeUninit para Inicializacao Segura (Unsafe)

use std::mem::MaybeUninit;

fn criar_array_inicializado() -> [u32; 5] {
    // MaybeUninit evita UB de valores nao-inicializados
    let mut array: [MaybeUninit<u32>; 5] = [const { MaybeUninit::uninit() }; 5];

    for (i, elem) in array.iter_mut().enumerate() {
        elem.write((i as u32 + 1) * 10);
    }

    // SAFETY: todos os elementos foram inicializados no loop acima
    unsafe {
        // transmute de [MaybeUninit<u32>; 5] para [u32; 5]
        // Isso e seguro porque MaybeUninit<T> tem o mesmo layout que T
        std::mem::transmute::<_, [u32; 5]>(array)
    }
}

fn main() {
    let arr = criar_array_inicializado();
    println!("{arr:?}"); // [10, 20, 30, 40, 50]
}

Importante sobre unsafe: O bloco unsafe acima e necessario porque estamos garantindo ao compilador que todos os elementos foram inicializados. Se esquecessemos de inicializar algum elemento, teriamos comportamento indefinido. MaybeUninit e a forma correta de lidar com memoria nao-inicializada — nunca use mem::zeroed() ou mem::uninitialized() (este ultimo foi descontinuado).


Padroes Comuns

Usando discriminant para Comparar Variantes de Enum

use std::mem;

enum Comando {
    Iniciar(String),
    Parar,
    Reiniciar { delay: u32 },
}

fn mesma_variante(a: &Comando, b: &Comando) -> bool {
    mem::discriminant(a) == mem::discriminant(b)
}

fn main() {
    let c1 = Comando::Iniciar("app".into());
    let c2 = Comando::Iniciar("outro".into());
    let c3 = Comando::Parar;

    println!("{}", mesma_variante(&c1, &c2)); // true (ambos Iniciar)
    println!("{}", mesma_variante(&c1, &c3)); // false
}

Reordenando Campos para Reduzir Tamanho

O compilador Rust reordena campos de structs por padrao para otimizar o layout. Voce pode verificar com size_of:

use std::mem;

// O compilador pode reordenar campos
struct SemRepr {
    a: u8,
    b: u64,
    c: u8,
}

// #[repr(C)] forca a ordem declarada (util para FFI)
#[repr(C)]
struct ComReprC {
    a: u8,
    b: u64,
    c: u8,
}

fn main() {
    println!("SemRepr: {} bytes", mem::size_of::<SemRepr>());   // 16 (otimizado)
    println!("ComReprC: {} bytes", mem::size_of::<ComReprC>()); // 24 (com padding)
}

Quando Usar (e Quando Nao Usar)

Use std::mem quando:

  • Precisar inspecionar o layout de memoria de tipos
  • Precisar trocar ou substituir valores eficientemente sem Clone
  • Estiver implementando maquinas de estado com replace/take
  • Precisar de inicializacao gradual com MaybeUninit
  • Estiver fazendo FFI e precisar de forget ou transmute

Nao use std::mem quando:

  • Uma simples atribuicao ou clone() resolver o problema
  • Estiver tentando contornar o borrow checker (repense o design)
  • mem::transmute parecer a solucao — quase sempre ha uma alternativa safe
  • mem::zeroed() for usada com tipos que nao suportam todos-zeros (como bool, &T, Box<T>)

Dica de performance: mem::swap e mem::replace sao operacoes de custo O(size_of::()) — elas fazem uma copia byte a byte. Para tipos pequenos, sao extremamente rapidas. Para tipos grandes, considere usar Box ou ponteiros.


Veja Tambem