Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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 uma String, e UserId, encapsulando um u32. Adicione um método chamado value a 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 chamada display_user que receba um UserId como primeiro parâmetro e um Username como 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_dev

E com as entradas 1001 e bob_smith:

User #1001: bob_smith

Você 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
}
quiz iconTeste seus conhecimentos

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

Pratique por conta própria: Compilador de Rust online