Patrón Newtype
Parte de la sección Object Oriented Programming del Journey de Rust de Coddy. Lección 54 de 61.
A veces tienes dos valores del mismo tipo que representan cosas completamente diferentes. El email de un usuario y su password podrían ser ambos valores String, pero intercambiarlos accidentalmente en una llamada a una función sería un error grave. El patrón Newtype resuelve esto creando tipos distintos que envuelven tipos primitivos.
Un newtype es simplemente una estructura de tupla con un solo campo:
struct Password(String);
struct Email(String);
Aunque ambos envuelven un String, el compilador los trata como tipos completamente diferentes. No puedes pasar accidentalmente un Password donde se espera un Email: el compilador detectará el error.
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); // Orden correcto impuesto por los tipos
// send_reset_email(pass, email); // ¡No compilará!
Para access el valor inner, usa .0 ya que es una struct de tupla:
let pass = Password(String::from("secret123"));
println!("Length: {}", pass.0.len());
También puedes añadir métodos a tu newtype, dándole un comportamiento específico de lo que representa, como validación o formato, que no tendría sentido en un String simple.
Desafío
Fácil¡Construyamos un sistema de registro de user con seguridad de tipos usando el patrón Newtype! Crearás tipos distintos para Username y UserId para evitar mezclarlos accidentalmente, aunque ambos envuelvan valores simples.
Organizarás tu código en dos archivos:
user_types.rs: Define dos estructuras newtype public:Username, que envuelve unString, yUserId, que envuelve unu32. Añade un method llamadovaluea cada una que returns una reference a los datos inner (para Username, devuelve&String; para UserId, devuelve&u32). Esto proporciona access controlado a los valores envueltos.main.rs: Incorpora tu módulo user_types y crea una function llamadadisplay_userque takes unUserIdcomo el primer parameter y unUsernamecomo el segundo parameter. La function should print la información del user. Después crea instances de ambos tipos usando los inputs proporcionados y llama a tu function.
La idea clave aquí es que, aunque UserId y Username son envoltorios simples, el compilador los trata como tipos completamente diferentes. ¡No puedes pasar accidentalmente un nombre de user donde se espera un ID!
Tu function display_user should print con este formato:
User #{id}: {username}Por ejemplo, con los inputs 42 y alice_dev:
User #42: alice_devY con los inputs 1001 y bob_smith:
User #1001: bob_smithRecibirás dos inputs: el ID del user (parse como u32) y la cadena de username.
Pruébalo tú mismo
mod user_types;
use user_types::{Username, UserId};
// TODO: Crea una función llamada `display_user` que tome:
// - Primer parámetro: UserId
// - Segundo parámetro: Username
// La función debe imprimir: "User #{id}: {username}"
// Usa el método .value() para acceder a los valores internos
fn main() {
// Leer 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: Crea instancias de UserId y Username usando las entradas
// TODO: Llama a display_user con los argumentos correctos
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Métodos y Comportamiento
Intro a Bloques de ImplementaciónEl Parámetro SelfMétodos MutablesFunciones AsociadasMúltiples Bloques de ImplementaciónEncadenamiento de MétodosResumen - Acciones de Rectángulo4Proyecto: Mascota virtual
Definir la mascotaAlimentar a la mascota7Traits estándar
El trait DebugEl trait DisplayClone y CopyTraits de igualdadResumen - Punto imprimible10Proyecto: Sistema de documentos
El Trait DrawComponente de texto2Encapsulamiento y módulos
Conceptos básicos de módulosLa palabra clave PublicCampos privadosGettersSettersRepaso - Casillero seguro5Genéricos
Estructuras genéricasMétodos genéricosMúltiples tipos genéricosFunciones genéricasResumen - Punto de coordenadas8Traits como límites
Sintaxis de límites de traitsLímites múltiplesLa cláusula whereRetornar tipos con traitsResumen: Impresora genérica11Patrones de diseño en Rust
Patrón NewtypeComposiciónEl Trait DropFrom e IntoResumen - Mock de Smart Pointer3Enums avanzados
Enums con datosMétodos en EnumsCoincidencia de variantes de datosEl Enum Option revisitadoResumen - Enum Shape6Definición de Traits
¿Qué es un Trait?Implementación de TraitsImplementaciones por defectoSobrescribir valores por defectoTraits con parámetrosResumen - Reproductor multimediaPractica por tu cuenta: Compilador de Rust online