Default Trait em Rust

Guia completo sobre o trait Default em Rust: valores padrão, derive automático, builder pattern e a sintaxe ..Default::default().

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

TipoValor padrão
boolfalse
i8, i16, i32, i64, i128, isize0
u8, u16, u32, u64, u128, usize0
f32, f640.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

  1. Use #[derive(Default)] quando possível: Para a maioria dos tipos, os valores padrão gerados automaticamente são adequados. Economize código.

  2. Implemente manualmente quando os defaults importam: Se porta 0 ou host vazio não fazem sentido para seu tipo, implemente Default manualmente com valores significativos.

  3. Combine com struct update syntax: A construção { campo: valor, ..Default::default() } é um padrão poderoso para APIs com muitas opções.

  4. or_default() em vez de or_insert(0): Em HashMap, prefira entry(k).or_default() a entry(k).or_insert(T::default()). É mais limpo e idiomático.

  5. 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.

  6. #[default] em enums: A partir do Rust 1.62, você pode usar #[derive(Default)] em enums marcando a variante padrão com #[default].

  7. Default para builder pattern: Use Default como base para builders. O padrão ..Default::default() é a alternativa mais simples ao builder pattern para structs com poucos campos.


Veja Também