Las tuple structs son una variante de las estructuras que carecen de nombres para sus campos, utilizando en su lugar la posición para definirlos. Los valores se acceden mediante índices numéricos (empezando desde .0). Son ideales cuando los nombres de los campos no aportan claridad adicional o cuando se busca implementar el Newtype pattern. Este último es una técnica fundamental para la seguridad de tipos: al envolver un tipo existente (como un f64) en una nueva estructura (como Metros(f64)), creamos tipos distintos que el compilador no confundirá, evitando errores lógicos como sumar accidentalmente kilogramos con metros.
Es vital no confundir una tupla común (T, U) con una tuple struct. La tupla es un tipo anónimo, mientras que la tuple struct define un tipo nominal único en el sistema de tipos de Rust, permitiendo implementar traits específicos para ella.
Las unit structs son estructuras que no contienen campos. Al no tener datos, son tipos de tamaño cero (Zero-Sized Types o ZST), lo que significa que no ocupan memoria en tiempo de ejecución. Se utilizan principalmente para implementar traits en tipos que actúan como marcadores (markers), para representar estados en una máquina de estados, o para cumplir con una interfaz de un trait sin necesidad de almacenar datos internos.
Finalmente, el patrón Newtype es esencial cuando necesitamos implementar un trait de la biblioteca estándar (std) en un tipo que ya está definido por otro módulo o biblioteca externa. Dado que Rust no permite la implementación de traits en tipos externos (regla de la coherencia), envolvemos el tipo externo en una tuple struct propia para ganar control sobre su comportamiento.
// 1. Tuple Struct: Usada para agrupaciones simples sin nombres de campos
struct Color(u8, u8, u8);
// 2. Newtype Pattern: Diferencia tipos semánticamente distintos
struct Metros(f64);
struct Kilos(f64);
// 3. Unit Struct: Actúa como un marcador (no ocupa memoria)
struct Marcador;
trait Saludar {
fn saludar(&self);
}
impl Saludar for Marcador {
fn saludar(&self) {
println!("Hola desde una unit struct!");
}
}
// 4. Newtype Pattern para implementar traits externos en tipos externos
use std::fmt;
struct MiVec<T>(Vec<T>);
impl<T: fmt::Display> fmt::Display for MiVec<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Contenido: {:?}", self.0)
}
}
fn main() {
// Uso de Color (acceso por índice)
let rojo = Color(255, 0, 0);
println!("Rojo: {}, {}, {}", rojo.0, rojo.1, rojo.2);
// Newtype: Prevención de errores de tipo
let distancia = Metros(10.5);
let peso = Kilos(5.0);
// let error = distancia + peso; // Esto fallaría al compilar
println!("Distancia: {}m, Peso: {}kg", distancia.0, peso.0);
// Uso de Unit Struct
let m = Marcador;
m.saludar();
// Uso de Newtype para extender comportamiento
let mi_lista = MiVec(vec![1, 2, 3]);
println!("{}", mi_lista);
}
Explicación del Código
struct Color(u8, u8, u8): Define una tuple struct donde los componentes se acceden medianterojo.0,rojo.1yrojo.2.struct Metros(f64)ystruct Kilos(f64): Implementan el Newtype pattern. Aunque ambos contienen unf64,distancia(de tipoMetros) ypeso(de tipoKilos) son tipos incompatibles para el compilador, evitando errores de mezcla de unidades.struct Marcador: Es una unit struct. No almacena datos y se usa para implementar el traitSaludar. Enmain,mes una instancia que no consume memoria.struct MiVec<T>(Vec<T>): Es un envoltorio (wrapper) utilizado para el Newtype pattern. Permite implementar el traitfmt::Displaysobre unVec<T>(un tipo de la librería estándar), algo que no sería posible directamente debido a las reglas de coherencia de Rust.self.0dentro de la implementación defmt::Display: Accede al campo interno delMiVecpara poder imprimir el contenido delVecoriginal.println!("{}", mi_lista): Llama a la implementación personalizada deDisplayque definimos paraMiVec.
N° 36