O Padrão Newtype
Envolva um tipo existente em uma struct fina para criar um tipo distinto com seus próprios invariantes, implementações de trait e API sem sobrecarga em tempo de execução.
Receita
#[repr(transparent)]
struct UserId(u64);
impl UserId {
pub fn new(id: u64) -> Option<Self> {
if id > 0 { Some(UserId(id)) } else { None }
}
}Quando usar isso: IDs de domínio, strings validadas ou implementação de traits em tipos externos.
Exemplo de Trabalho
struct Email(String);
impl Email {
pub fn parse(s: impl Into<String>) -> Result<Self, &'static str> {
let s = s.into();
if s.contains('@') { Ok(Email(s)) } else { Err("invalid email") }
}
}
impl AsRef<str> for Email {
fn as_ref(&self) -> &str { &self.0 }
}O que isso demonstra:
UserIdeOrderIdnão são intercambiáveis, embora ambos envolvamu64- Validação centralizada no construtor/
TryFrom repr(transparent)preserva a ABI para wrappers FFI- Contorno da regra "orphan": implementa trait local em
Email, não diretamente emString
Análise Profunda
Newtype vs Alias
type UserId = u64; // alias: mesmo tipo
struct UserId(u64); // newtype: tipo distintoserde
#[serde(transparent)]
struct Meters(f64);Armadilhas
- Campo interno público - contorna a validação. Correção: campo privado + construtor.
- Deref excessivo - newtype se torna um alias ergonomicamente. Correção: implemente apenas os métodos necessários, evite
Derefpara o interno, a menos que intencional. - Esqueceu Eq/Hash para IDs - chaves de mapa quebram. Correção:
#[derive(PartialEq, Eq, Hash)]no newtype. - FFI sem repr transparente - incompatibilidade de layout. Correção:
#[repr(transparent)]em um wrapper de campo único.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Type alias | Apenas clareza interna | Precisa de segurança de tipo na API |
| Enum | Pequeno conjunto fixo de estados | IDs ilimitados |
| Genérico fantasma | Marcação de tempos de vida | Simples envolvimento de ID |
FAQs
Posso implementar Display?
Sim, no newtype; controle a formatação e a ocultação.
newtype para String?
Comum para Email, Nome de Usuário, Slug com validação.
Custo de desempenho?
Zero em tempo de execução; mesmo layout do interno com repr transparente.
Mapeamento sqlx?
UserId(row.get::<i64,_>(0)? as u64) ou implementação customizada de FromRow.
Argumentos clap?
Analise em newtype no value_parser do clap para entrada CLI validada.
newtype em chave HashMap?
HashMap<UserId, User> evita misturar tipos de chave.
Transparent e ZST?
Transparent requer um único campo não-ZST.
Documentar invariante?
Rustdoc na struct explica quais valores são legais.
TryFrom vs new?
TryFrom para conversões; new inerente para API de domínio.
newtype sobre Arc?
Possível para marcação de handle compartilhado; menos comum que IDs Copy.
Relacionado
- Noções Básicas de Padrões Rust - visão geral
- From/Into & TryFrom - conversões
- Modelagem de Domínio com Tipos - design
- Diretrizes de API Rust
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4 e Polars 0.46+.