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 unResultadoque es simplemente un sinónimo deResult. Si intentas usarResultadodonde se espera unu32y pasas unStringen el error, el compilador lo aceptará porque es transparente.struct Metros(f64);ystruct Segundos(f64);: Implementan el newtype pattern. Aunque ambos contienen unf64,MetrosySegundosson tipos incompatibles entre sí para el compilador.trait Describible: Se define un trait personalizado para demostrar la ventaja de la orphan rule. Al envolverf64enMetros, ahora podemos implementarDescribibleparaMetros.impl Describible for Metros: Implementación del métododescribirpara el tipoMetros. Accedemos al valor interno medianteself.0.fn calcular_velocidad(distancia: Metros, tiempo: Segundos): Esta función es segura. Si intentáramos llamar a esta función pasando dosf64o dosMetros, 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 aliasResultadosimplifica la declaración de variables con tipos genéricos complejos.
N° 103