En Rust, existen dos formas principales de manejar nombres de tipos: los type aliases (alias de tipo) y el newtype pattern (patrón de nuevo tipo). Aunque ambos pueden parecer similares al leer el código, su comportamiento semántico y técnico es radicalmente distinto.

Un type alias se crea con la palabra clave type. Su función es puramente de conveniencia y legibilidad. Es un nombre alternativo para un tipo ya existente; el compilador lo trata como el tipo original de forma transparente. Es ideal para simplificar firmas de funciones complejas, como un Result anidado, o para dar nombres más descriptivos a tipos genéricos. Sin embargo, no ofrece seguridad de tipos: si creas un alias type Milisegundos = u64, el compilador permitirá sumar accidentalmente Milisegundos con Segundos si ambos son u64.

El newtype pattern utiliza una tuple struct para envolver un tipo existente. A diferencia del alias, esto crea un tipo nuevo y distinto. Es opaco. Si defines struct Metros(f64), no puedes usarla donde se requiera un f64 sin acceder explícitamente al campo .0. Esta opacidad es su mayor virtud: permite la seguridad de tipos (evita errores de lógica como confundir unidades), permite añadir métodos propios al tipo y, crucialmente, permite saltarse la “regla del huérfano” (orphan rule), permitiéndote implementar tus propios traits en tipos externos (como f64) al envolverlos primero.

A nivel de rendimiento, el newtype es una abstracción de costo cero (zero-cost abstraction). El compilador optimiza el wrapper de modo que, en el código máquina, el tipo resultante es exactamente el mismo que el tipo interno.

// --- Type Alias ---
// Solo un nombre más corto para un tipo complejo. Es transparente.
type Resultado<T> = Result<T, String>;

// --- Newtype Pattern ---
// Crea tipos totalmente distintos para evitar errores de lógica.
struct Metros(f64);
struct Segundos(f64);

// Implementamos un trait propio en un tipo externo (usando el Newtype)
// Esto sería imposible con un alias o un f64 directo debido a la orphan rule.
trait Describible {
    fn describir(&self) -> String;
}

impl Describible for Metros {
    fn describir(&self) -> String {
        format!("Distancia: {} metros", self.0)
    }
}

// Función que exige tipos específicos para evitar confusiones de unidades
fn calcular_velocidad(distancia: Metros, tiempo: Segundos) -> f64 {
    // Accedemos al valor interno mediante el campo .0
    distancia.0 / tiempo.0
}

fn main() -> Resultado<()> {
    let d = Metros(100.0);
    let t = Segundos(10.0);

    // El compilador garantiza que no pasaremos f64 por error
    let velocidad = calcular_velocidad(d, t);

    println!("Velocidad calculada: {} m/s", velocidad);
    println!("{}", d.describir());

    // Uso del alias para una firma de retorno limpia
    let operacion_exitosa: Resultado<()> = Ok(());
    operacion_exitosa?;

    Ok(())
}

Explicación del Código

  • type Resultado<T> = Result<T, String>;: Define un Resultado que es simplemente un sinónimo de Result. Si intentas usar Resultado donde se espera un u32 y pasas un String en el error, el compilador lo aceptará porque es transparente.
  • struct Metros(f64); y struct Segundos(f64);: Implementan el newtype pattern. Aunque ambos contienen un f64, Metros y Segundos son tipos incompatibles entre sí para el compilador.
  • trait Describible: Se define un trait personalizado para demostrar la ventaja de la orphan rule. Al envolver f64 en Metros, ahora podemos implementar Describible para Metros.
  • impl Describible for Metros: Implementación del método describir para el tipo Metros. Accedemos al valor interno mediante self.0.
  • fn calcular_velocidad(distancia: Metros, tiempo: Segundos): Esta función es segura. Si intentáramos llamar a esta función pasando dos f64 o dos Metros, el compilador emitiría un error de tipos.
  • distancia.0 / tiempo.0: Para realizar operaciones aritméticas, debemos extraer el valor interno del wrapper usando el acceso por índice .0.
  • let d = Metros(100.0);: Instanciación de la estructura de tupla.
  • let operacion_exitosa: Resultado<()> = Ok(());: Demuestra cómo el alias Resultado simplifica la declaración de variables con tipos genéricos complejos.

103