O Tipo Unit () em Rust: Compreendendo Retornos e Expressões.
- #Rust
Fala galera, bora falar sobre o tipo Unit?
No Rust, quase tudo é uma expressão que precisa retornar algum valor. Mas o que acontece quando uma função ou bloco simplesmente não tem nada útil para devolver? É aí que entra o Unit Type, representado por "()".
Diferente do "void" do C++ ou Java (que indica a ausência total de um tipo), o "()" no Rust é um tipo real, que possui um único valor possível: "()". O compilador o trata como um “Zero-Sized Type“ (ZST). Na prática, isso significa que ele tem tamanho zero em memória (std::mem::size_of::<()>() == 0) e é totalmente otimizado no binário final, sem gastar um único byte em tempo de execução.
Abaixo estão os pontos principais de como ele funciona no dia a dia e onde o compilador costuma pegar quem está começando.
Retornos Implícitos vs explícitos:
Se você não define um tipo de retorno (usando "->") na assinatura de uma função, o compilador assume automaticamente que ela retorna "()".
Rust
//rust
// O padrão que usamos no dia a dia:
fn rotina_de_limpeza() {
let temp_files = 10;
println!("Limpando {} arquivos temporários...", temp_files);
}
O que o compilador enxerga por baixo dos panos:
//rust
fn rotina_de_limpeza_explicita() -> () {
let temp_files = 10;
println!("Limpando {} arquivos temporários...", temp_files);
return ();
}
A Armadilha do Ponto e Vírgula
Esse é um dos erros mais comuns para quem está entrando no ecossistema do Rust. Colocar ou omitir um ponto e vírgula (;) muda completamente o tipo de retorno de um bloco de código, porque o ; transforma uma expressão em um statement (instrução), descartando o valor e gerando ().
Rust
// rust
fn avaliar_blocos() {
// Sem ponto e vírgula: o bloco retorna o i32 (10)
let valor_real: i32 = {
let x = 5;
x + 5
};
Com ponto e vírgula: o valor 10 é descartado e o bloco retorna ()
let valor_descartado: () = {
let x = 5;
x + 5;
};
assert_eq!(valor_descartado, ());
}
Generics e Tratamento de Erros
O grande trunfo do () ser um tipo real (e não uma palavra-chave como void) aparece quando lidamos com tipos genéricos, especialmente no Result<T, E>. Quando uma função pode dar erro, mas o sucesso dela significa apenas que a tarefa foi cumprida (sem precisar devolver nenhum dado de volta), usamos () no lugar do T.
//rust
use std::fs::File;
use std::io::Error;
// "Retorna nada em caso de sucesso, ou um Error se falhar"
fn inicializar_sistema(config_path: &str) -> Result<(), Error> {
let _file = File::open(config_path)?;
// Sucesso absoluto: encapsula o unit value no Ok
Ok(())
}
fn main() {
match inicializar_sistema("/etc/config.toml") {
Ok(()) => println!("Sistema pronto."),
Err(e) => eprintln!("Erro na inicialização: {}", e),
}
}
() vs ! (Never Type)
Uma dúvida comum é a diferença entre () e o tipo !. A lógica é simples:
- () (Unit): A função executa até o fim e termina normalmente, mas não produz nenhum dado relevante.
- ! (Never): A função nunca retorna para quem a chamou. Ela quebra o fluxo de execução, seja por causa de um panic!(), um loop {} infinito ou um encerramento do processo (std::process::exit()).
Resumindo: () significa "terminei, mas não tenho nada para te entregar"; ! significa "daqui eu não volto".




