O que é o Default Trait?
O trait Default fornece uma maneira padronizada de criar um valor padrão para um tipo. Ele responde à pergunta: “Qual é o valor mais razoável e neutro para este tipo?”
Em muitas linguagens, tipos têm valores padrão implícitos (como 0 para inteiros ou null para referências). Em Rust, o Default trait torna isso explícito e seguro — sem nulls, sem surpresas.
Default é amplamente usado em:
- Inicialização de structs com campos parciais (struct update syntax)
- Builder pattern
- Coleções e containers
- APIs genéricas que precisam de um valor inicial
Definição do Trait
// Definido em std::default
pub trait Default: Sized {
fn default() -> Self;
}
O trait tem um único método, default(), que retorna uma nova instância do tipo com seus valores padrão.
Valores padrão dos tipos primitivos
| Tipo | Valor padrão |
|---|---|
bool | false |
i8, i16, i32, i64, i128, isize | 0 |
u8, u16, u32, u64, u128, usize | 0 |
f32, f64 | 0.0 |
char | '\0' |
String | "" (string vazia) |
Vec<T> | vec![] (vetor vazio) |
Option<T> | None |
HashMap<K, V> | mapa vazio |
Como Implementar: derive vs impl manual
Default com #[derive]
O compilador gera automaticamente uma implementação que chama Default::default() para cada campo:
#[derive(Debug, Default)]
struct ConfigServidor {
host: String, // ""
porta: u16, // 0
max_conexoes: usize, // 0
tls_habilitado: bool, // false
timeout_ms: Option<u64>, // None
}
fn main() {
let config = ConfigServidor::default();
println!("{:#?}", config);
// ConfigServidor {
// host: "",
// porta: 0,
// max_conexoes: 0,
// tls_habilitado: false,
// timeout_ms: None,
// }
}
Para usar #[derive(Default)], todos os campos devem implementar Default.
Default com implementação manual
Quando os valores padrão derivados não fazem sentido para o seu domínio:
#[derive(Debug)]
struct ConfigServidor {
host: String,
porta: u16,
max_conexoes: usize,
tls_habilitado: bool,
timeout_ms: Option<u64>,
}
impl Default for ConfigServidor {
fn default() -> Self {
ConfigServidor {
host: String::from("127.0.0.1"),
porta: 8080,
max_conexoes: 100,
tls_habilitado: false,
timeout_ms: Some(30_000),
}
}
}
fn main() {
let config = ConfigServidor::default();
println!("{:#?}", config);
// ConfigServidor {
// host: "127.0.0.1",
// porta: 8080,
// max_conexoes: 100,
// tls_habilitado: false,
// timeout_ms: Some(30000),
// }
}
Default para enums
#[derive(Debug)]
enum NivelLog {
Erro,
Aviso,
Info,
Debug,
Trace,
}
impl Default for NivelLog {
fn default() -> Self {
NivelLog::Info // Info é o padrão mais razoável
}
}
// Com derive, marca a variante padrão com #[default]
#[derive(Debug, Default)]
enum Tema {
Claro,
Escuro,
#[default]
Sistema, // Segue a preferência do sistema operacional
}
fn main() {
let nivel = NivelLog::default();
let tema = Tema::default();
println!("Nível: {:?}", nivel); // Info
println!("Tema: {:?}", tema); // Sistema
}
Exemplos Práticos
Exemplo 1: Struct update syntax com ..Default::default()
A sintaxe ..Default::default() permite criar uma instância especificando apenas os campos que diferem do padrão:
#[derive(Debug)]
struct Requisicao {
metodo: String,
url: String,
headers: Vec<(String, String)>,
corpo: Option<String>,
timeout_ms: u64,
seguir_redirect: bool,
max_redirects: u32,
}
impl Default for Requisicao {
fn default() -> Self {
Requisicao {
metodo: String::from("GET"),
url: String::new(),
headers: Vec::new(),
corpo: None,
timeout_ms: 30_000,
seguir_redirect: true,
max_redirects: 10,
}
}
}
fn main() {
// Especifica apenas url e metodo, o resto é padrão
let req = Requisicao {
url: String::from("https://api.exemplo.com/dados"),
metodo: String::from("POST"),
corpo: Some(String::from(r#"{"chave": "valor"}"#)),
..Default::default()
};
println!("{:#?}", req);
// timeout_ms será 30000
// seguir_redirect será true
// max_redirects será 10
}
Exemplo 2: Builder pattern com Default
#[derive(Debug)]
struct Conexao {
host: String,
porta: u16,
usuario: Option<String>,
senha: Option<String>,
pool_tamanho: usize,
ssl: bool,
}
impl Default for Conexao {
fn default() -> Self {
Conexao {
host: String::from("localhost"),
porta: 5432,
usuario: None,
senha: None,
pool_tamanho: 10,
ssl: false,
}
}
}
impl Conexao {
fn builder() -> ConexaoBuilder {
ConexaoBuilder::default()
}
}
#[derive(Default)]
struct ConexaoBuilder {
host: Option<String>,
porta: Option<u16>,
usuario: Option<String>,
senha: Option<String>,
pool_tamanho: Option<usize>,
ssl: Option<bool>,
}
impl ConexaoBuilder {
fn host(mut self, host: impl Into<String>) -> Self {
self.host = Some(host.into());
self
}
fn porta(mut self, porta: u16) -> Self {
self.porta = Some(porta);
self
}
fn usuario(mut self, usuario: impl Into<String>) -> Self {
self.usuario = Some(usuario.into());
self
}
fn ssl(mut self, ssl: bool) -> Self {
self.ssl = Some(ssl);
self
}
fn build(self) -> Conexao {
let padrao = Conexao::default();
Conexao {
host: self.host.unwrap_or(padrao.host),
porta: self.porta.unwrap_or(padrao.porta),
usuario: self.usuario.or(padrao.usuario),
senha: self.senha.or(padrao.senha),
pool_tamanho: self.pool_tamanho.unwrap_or(padrao.pool_tamanho),
ssl: self.ssl.unwrap_or(padrao.ssl),
}
}
}
fn main() {
let conn = Conexao::builder()
.host("db.exemplo.com")
.porta(5433)
.usuario("admin")
.ssl(true)
.build();
println!("{:#?}", conn);
}
Exemplo 3: Default em tipos genéricos
#[derive(Debug)]
struct Cache<T: Default> {
dados: Vec<T>,
capacidade: usize,
}
impl<T: Default> Cache<T> {
fn novo(capacidade: usize) -> Self {
Cache {
dados: Vec::with_capacity(capacidade),
capacidade,
}
}
fn obter_ou_padrao(&self, indice: usize) -> T {
if indice < self.dados.len() {
// Precisaríamos de Clone aqui na prática
T::default()
} else {
T::default()
}
}
}
impl<T: Default> Default for Cache<T> {
fn default() -> Self {
Cache {
dados: Vec::new(),
capacidade: 64,
}
}
}
fn main() {
let cache: Cache<String> = Cache::default();
println!("Capacidade: {}", cache.capacidade); // 64
let valor = cache.obter_ou_padrao(999);
println!("Padrão: '{}'", valor); // ""
}
Exemplo 4: Default com Option e unwrap_or_default
fn main() {
// unwrap_or_default usa Default::default() como fallback
let talvez_numero: Option<i32> = None;
let numero = talvez_numero.unwrap_or_default();
println!("{}", numero); // 0
let talvez_texto: Option<String> = None;
let texto = talvez_texto.unwrap_or_default();
println!("'{}'", texto); // ''
// Útil em iteradores
let numeros = vec![1, 2, 3];
let primeiro: i32 = numeros.into_iter().next().unwrap_or_default();
println!("{}", primeiro); // 1
let vazio: Vec<i32> = vec![];
let primeiro: i32 = vazio.into_iter().next().unwrap_or_default();
println!("{}", primeiro); // 0
}
Exemplo 5: Default para inicializar arrays e coleções
use std::collections::HashMap;
fn main() {
// HashMap começa vazio por padrão
let mut contadores: HashMap<String, usize> = HashMap::default();
let palavras = vec!["rust", "é", "rust", "incrível", "rust"];
for palavra in palavras {
// entry().or_default() usa Default::default() para usize (= 0)
*contadores.entry(palavra.to_string()).or_default() += 1;
}
println!("{:?}", contadores);
// {"rust": 3, "é": 1, "incrível": 1}
// Vec de valores padrão
let zeros: Vec<i32> = std::iter::repeat_with(Default::default)
.take(5)
.collect();
println!("{:?}", zeros); // [0, 0, 0, 0, 0]
}
Padrões e Boas Práticas
Use
#[derive(Default)]quando possível: Para a maioria dos tipos, os valores padrão gerados automaticamente são adequados. Economize código.Implemente manualmente quando os defaults importam: Se porta 0 ou host vazio não fazem sentido para seu tipo, implemente
Defaultmanualmente com valores significativos.Combine com struct update syntax: A construção
{ campo: valor, ..Default::default() }é um padrão poderoso para APIs com muitas opções.or_default()em vez deor_insert(0): EmHashMap, prefiraentry(k).or_default()aentry(k).or_insert(T::default()). É mais limpo e idiomático.Default deve ser sensato: O valor padrão deve ser o mais seguro e neutro possível. Não use defaults que possam causar comportamento inesperado.
#[default]em enums: A partir do Rust 1.62, você pode usar#[derive(Default)]em enums marcando a variante padrão com#[default].Default para builder pattern: Use
Defaultcomo base para builders. O padrão..Default::default()é a alternativa mais simples ao builder pattern para structs com poucos campos.
Veja Também
- Clone e Copy — traits de cópia, frequentemente derivados junto com Default
- Display e Debug — outro grupo de traits comumente derivados
- From e Into — conversões de tipos, complementar a Default para inicialização
- Eq e Ord — traits de comparação, parte do conjunto comum de derives
- Padrões de Projeto em Rust — builder pattern e outros padrões