Rust: Estructuras de datos

Descubre cómo crear y manejar estructuras de datos en Rust para organizar y optimizar tu código con ejemplos claros y conceptos esenciales.

Aprende Rust GRATIS y certifícate

Estructuras de datos en Rust

Las estructuras de datos constituyen el fundamento sobre el cual construimos aplicaciones eficientes y organizadas. En Rust, estas herramientas nos permiten agrupar información relacionada de manera coherente, facilitando tanto la comprensión del código como su mantenimiento a largo plazo.

Fundamentos conceptuales

Una estructura de datos representa una forma de organizar y almacenar información que refleja las relaciones naturales entre diferentes elementos de nuestro dominio. En el contexto de Rust, las estructuras nos proporcionan un mecanismo para crear tipos personalizados que encapsulan datos relacionados bajo una única entidad lógica.

La sintaxis básica para definir una estructura en Rust utiliza la palabra clave struct, seguida del nombre de la estructura y la definición de sus campos:

struct Usuario {
    nombre: String,
    edad: u32,
    activo: bool,
}

Esta definición establece un nuevo tipo llamado Usuario que agrupa tres campos diferentes, cada uno con su tipo específico. La declaración de tipos en cada campo garantiza la seguridad de memoria característica de Rust.

Creación e inicialización

Para crear una instancia de una estructura, especificamos valores para todos sus campos utilizando una sintaxis similar a la definición:

let usuario1 = Usuario {
    nombre: String::from("Ana García"),
    edad: 28,
    activo: true,
};

Rust también permite la inicialización abreviada cuando las variables locales tienen el mismo nombre que los campos de la estructura:

fn crear_usuario(nombre: String, edad: u32) -> Usuario {
    Usuario {
        nombre,  // equivale a nombre: nombre
        edad,    // equivale a edad: edad
        activo: true,
    }
}

Acceso y modificación de campos

El acceso a los campos se realiza mediante la notación de punto, permitiendo tanto lectura como escritura cuando la instancia es mutable:

let mut usuario = Usuario {
    nombre: String::from("Carlos López"),
    edad: 35,
    activo: false,
};

println!("Nombre: {}", usuario.nombre);
usuario.edad = 36;  // Modificación permitida por ser mutable

La mutabilidad en Rust se aplica a toda la instancia, no a campos individuales. Esto significa que una estructura es completamente mutable o completamente inmutable.

Sintaxis de actualización

Rust proporciona una sintaxis de actualización que facilita la creación de nuevas instancias basadas en instancias existentes:

let usuario2 = Usuario {
    nombre: String::from("María Rodríguez"),
    ..usuario1  // Copia los campos restantes de usuario1
};

Esta característica resulta especialmente útil cuando necesitamos crear variaciones de estructuras existentes modificando solo algunos campos específicos.

Estructuras tupla

Las estructuras tupla ofrecen una alternativa cuando necesitamos agrupar datos pero los nombres de los campos no aportan valor semántico significativo:

struct Coordenadas(f64, f64, f64);
struct Color(u8, u8, u8);

let punto = Coordenadas(10.5, 20.3, 15.7);
let rojo = Color(255, 0, 0);

El acceso a los elementos en estructuras tupla utiliza índices numéricos:

println!("Coordenada X: {}", punto.0);
println!("Componente rojo: {}", rojo.0);

Estructuras unitarias

Las estructuras unitarias no contienen datos pero pueden implementar traits, resultando útiles para crear tipos que representen conceptos sin estado asociado:

struct Marcador;

let instancia = Marcador;

Ownership y borrowing en estructuras

El sistema de ownership de Rust se aplica de manera integral a las estructuras. Cuando una estructura contiene tipos que implementan el trait Copy, la estructura completa puede copiarse:

#[derive(Copy, Clone)]
struct Punto {
    x: i32,
    y: i32,
}

let p1 = Punto { x: 5, y: 10 };
let p2 = p1;  // p1 sigue siendo válido

Sin embargo, cuando una estructura contiene tipos como String que no implementan Copy, se produce un movimiento de ownership:

let usuario1 = Usuario {
    nombre: String::from("Pedro Martín"),
    edad: 42,
    activo: true,
};

let usuario2 = usuario1;  // usuario1 ya no es válido

Referencias en estructuras

Las estructuras pueden contener referencias a datos almacenados en otras ubicaciones, aunque esto requiere el uso de lifetimes para garantizar que las referencias permanezcan válidas:

struct Referencia<'a> {
    contenido: &'a str,
}

let texto = "Hola mundo";
let ref_struct = Referencia {
    contenido: &texto,
};

Esta capacidad permite crear estructuras que referencian datos sin tomar ownership de ellos, optimizando el uso de memoria en ciertos escenarios.

Métodos asociados

Las estructuras en Rust pueden tener métodos asociados que operan sobre sus instancias, proporcionando una interfaz coherente para manipular los datos encapsulados:

impl Usuario {
    fn es_mayor_edad(&self) -> bool {
        self.edad >= 18
    }
    
    fn cumplir_anos(&mut self) {
        self.edad += 1;
    }
}

Los métodos de instancia reciben self como primer parámetro, mientras que las funciones asociadas no lo requieren y se invocan usando la sintaxis :::

impl Usuario {
    fn nuevo(nombre: String, edad: u32) -> Usuario {
        Usuario {
            nombre,
            edad,
            activo: true,
        }
    }
}

let usuario = Usuario::nuevo(String::from("Laura Sánchez"), 25);
Empezar curso de Rust

Lecciones de este módulo de Rust

Lecciones de programación del módulo Estructuras de datos del curso de Rust.

Ejercicios de programación en este módulo de Rust

Evalúa tus conocimientos en Estructuras de datos con ejercicios de programación Estructuras de datos de tipo Test, Puzzle, Código y Proyecto con VSCode.