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
| Funcao | Descricao |
|---|---|
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)
| Funcao | Descricao |
|---|---|
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/Tipo | Descricao |
|---|---|
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
forgetoutransmute
Nao use std::mem quando:
- Uma simples atribuicao ou
clone()resolver o problema - Estiver tentando contornar o borrow checker (repense o design)
mem::transmuteparecer a solucao — quase sempre ha uma alternativa safemem::zeroed()for usada com tipos que nao suportam todos-zeros (comobool,&T,Box<T>)
Dica de performance: mem::swap e mem::replace sao operacoes de custo O(size_of::Box ou ponteiros.
Veja Tambem
- Otimizacao de Performance em Rust — tecnicas avancadas usando
std::mem - Modulo std::ptr — ponteiros raw para manipulacao de memoria de baixo nivel
- Modulo std::marker — traits como
CopyeSizedque afetam o layout - Pin<P> em Rust — fixando valores na memoria
- Unsafe Rust — contexto completo sobre blocos unsafe
- Documentacao oficial — std::mem