Padrão Newtype
Parte da seção Programação Orientada a Objetos do Journey de Rust da Coddy. Lição 54 de 61.
Às vezes, você tem dois valores do mesmo tipo que representam coisas completamente diferentes. O e-mail e a senha de um usuário podem ser valores String, mas trocá-los acidentalmente em uma chamada de função seria um erro grave. O padrão Newtype resolve isso criando tipos distintos que encapsulam tipos primitivos.
Um newtype é simplesmente uma struct tupla com um único campo:
struct Password(String);
struct Email(String);
Embora ambos envolvam um String, o compilador os trata como tipos completamente diferentes. Você não pode passar acidentalmente um Password onde um Email é esperado: o compilador detectará o erro.
fn send_reset_email(email: Email, password: Password) {
// ...
}
let email = Email(String::from("user@example.com"));
let pass = Password(String::from("secret123"));
send_reset_email(email, pass); // Ordem correta imposta pelos tipos
// send_reset_email(pass, email); // Não compila!
Para acessar o valor interno, use .0, pois é uma struct tupla:
let pass = Password(String::from("secret123"));
println!("Length: {}", pass.0.len());
Você também pode adicionar métodos ao seu newtype, dando a ele um comportamento específico do que ele representa, como validação ou formatação, que não faria sentido em uma String simples.
Desafio
FácilVamos criar um sistema de registro de usuários com segurança de tipos usando o padrão Newtype! Você criará tipos distintos para Username e UserId a fim de evitar misturá-los acidentalmente, mesmo que ambos encapsulem valores simples.
Você organizará seu código em dois arquivos:
user_types.rs: Defina duas structs newtype públicas:Username, encapsulando umaString, eUserId, encapsulando umu32. Adicione um método chamadovaluea cada uma, que retorna uma referência aos dados internos (para Username, retorne&String; para UserId, retorne&u32). Isso fornece acesso controlado aos valores encapsulados.main.rs: Inclua seu módulo user_types e crie uma função chamadadisplay_userque receba umUserIdcomo primeiro parâmetro e umUsernamecomo segundo parâmetro. A função deve imprimir as informações do usuário. Em seguida, crie instâncias de ambos os tipos usando as entradas fornecidas e chame sua função.
A principal ideia aqui é que, embora UserId e Username sejam simples encapsuladores, o compilador os trata como tipos completamente diferentes. Você não pode passar acidentalmente um nome de usuário onde um ID é esperado!
Sua função display_user deve imprimir neste formato:
User #{id}: {username}Por exemplo, com as entradas 42 e alice_dev:
User #42: alice_devE com as entradas 1001 e bob_smith:
User #1001: bob_smithVocê receberá duas entradas: o ID do usuário (converta para u32) e a string do nome de usuário.
Experimente você mesmo
mod user_types;
use user_types::{Username, UserId};
// TODO: Crie uma função chamada `display_user` que recebe:
// - Primeiro parâmetro: UserId
// - Segundo parâmetro: Username
// A função deve imprimir: "User #{id}: {username}"
// Use o método .value() para acessar os valores internos
fn main() {
// Ler entrada
let mut id_input = String::new();
std::io::stdin().read_line(&mut id_input).expect("Failed to read line");
let id: u32 = id_input.trim().parse().expect("Invalid number");
let mut username_input = String::new();
std::io::stdin().read_line(&mut username_input).expect("Failed to read line");
let username = username_input.trim().to_string();
// TODO: Crie instâncias de UserId e Username usando as entradas
// TODO: Chame display_user com os argumentos corretos
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Métodos e Comportamento
Introdução a Blocos de ImplementaçãoO Parâmetro SelfMétodos MutáveisFunções AssociadasMúltiplos Blocos de ImplementaçãoEncadeamento de MétodosRecapitulação - Ações de Retângulo4Projeto: Pet Virtual
Definindo o PetAlimentando o Pet7Traits Padrão
A Trait DebugA Trait DisplayClone e CopyTraits de IgualdadeRecapitulação - Ponto Imprimível10Projeto: Sistema de Documentos
A Trait DrawComponente de Texto2Encapsulamento e Módulos
Fundamentos de MódulosA Palavra-chave PublicCampos PrivadosGettersSettersRecapitulação - Cofre Seguro5Genéricos
Structs GenéricasMétodos GenéricosMúltiplos Tipos GenéricosFunções GenéricasRecapitulação - Ponto de Coordenada8Traits como Restrições
Sintaxe de Restrições de TraitRestrições MúltiplasA Cláusula WhereRetornando Tipos com TraitsRecapitulação - Impressora Genérica11Padrões de Projeto em Rust
Padrão NewtypeComposiçãoA Trait DropFrom e IntoRecapitulação - Mock de Smart Pointer3Enums Avançados
Enums com DadosMétodos em EnumsCorrespondência de Variantes de DadosO Enum Option RevisitadoRecapitulação - Enum Shape6Definição de Traits
O que é uma Trait?Implementando TraitsImplementações PadrãoSobrescrevendo PadrõesTraits com ParâmetrosRecapitulação - Media PlayerPratique por conta própria: Compilador de Rust online