Primeros pasos en Rust - Struct

Apuntes de Rust - Parte 10

Una estructura o un struct es a una colección organizada de datos que permite agrupar diferentes tipos de datos bajo una misma variable. Esta característica resulta muy útil, ya que facilita el transporte, acceso y actualización de los datos de manera simple. Una estructura es una especie de plantilla o un modelo, similar a un registro en una base de datos. Al utilizar estructuras, permite organizar y relacionar datos de manera eficiente, ofreciendo mayor flexibilidad y claridad al código. Además, las estructuras permiten definir comportamientos y funciones asociadas a los datos que contienen, lo que amplía sus capacidades.

En conclusión, una estructura o struct en Rust, es un tipo de dato personalizado que permite agrupar y nombrar múltiples valores relacionados que forman un grupo significativo.

¿Cómo definir una estructura en Rust?

Para definir una estructura, se utiliza la palabra clave struct seguida del nombre que se asigna a la estructura. El nombre de una estructura debe reflejar la relevancia de los datos que se agrupan. Dentro del bloque de llaves, se definen los nombres y tipos de datos que describen la entidad que representa la estructura. A estas variables se les llama campos. Por ejemplo, considere una estructura que representa a un jugador de baloncesto de la NBA:

struct JugadorNBA {
    nombre: String,
    equipo: String,
    numero: u32,
    posicion: String,
    puntos_por_partido: f32,
}

En este ejemplo, se define la estructura JugadorNBA con los campos nombre, equipo, numero, posicion y puntos_por_partido.

Para utilizar una estructura después de definida, es necesario crear una instancia de esa estructura especificando valores concretos para cada uno de los campos. Se crea una instancia indicando el nombre de la estructura y luego dentro del bloque de llaves se especifica el valor de cada campo.

Los campos contienen pares clave: valor, donde las claves son los nombres de los campos y los valores son los datos que se desea almacenar en esos campos. No es necesario especificar los campos en el mismo orden en que se defina la estructura.

Como mencionamos, la definición de la estructura es como una plantilla general para un tipo de entidad, y las instancias llenan esa plantilla con datos específicos para crear valores de ese tipo.

struct JugadorNBA {
    nombre: String,
    equipo: String,
    numero: u32,
    posicion: String,
    puntos_por_partido: f32,
}

fn main() {
    let kobe = JugadorNBA {
        nombre: String::from("Kobe Bryant"),
        equipo: String::from("Los Angeles Lakers"),
        numero: 24,
        posicion: String::from("Escolta"),
        puntos_por_partido: 25.0,
    };

    println!("Información del jugador:");
    println!("Nombre: {}", kobe.nombre);
    println!("Equipo: {}", kobe.equipo);
    println!("Número: {}", kobe.numero);
    println!("Posición: {}", kobe.posicion);
    println!("Puntos por partido: {}", kobe.puntos_por_partido);
}

La estructura sé instancia con valores específicos para cada campo y luego se accede a los campos utilizando la notación de punto. El ejemplo crea una instancia de la estructura con los datos específicos de Kobe Bryant. Finalmente, se accede a los campos de la instancia utilizando la notación de punto y se imprimen en la salida estándar.

Si la instancia es mutable, puede cambiar un valor utilizando la notación de punto y asignando un nuevo valor a un campo específico.

struct JugadorNBA {
    nombre: String,
    equipo: String,
    numero: u32,
    posicion: String,
    puntos_por_partido: f32,
}

fn main() {
    // Se modifica la instancia para que sea mutable
    let mut kobe = JugadorNBA {
        nombre: String::from("Kobe Bryant"),
        equipo: String::from("Los Angeles Lakers"),
        numero: 24,
        posicion: String::from("Escolta"),
        puntos_por_partido: 25.0,
    };

    // se actualiza el campo numero de la instancia, utilizando el operador punto. 
    kobe.numero = 8;

    println!("Información del jugador:");
    println!("Nombre: {}", kobe.nombre);
    println!("Equipo: {}", kobe.equipo);
    println!("Número: {}", kobe.numero);
    println!("Posición: {}", kobe.posicion);
    println!("Puntos por partido: {}", kobe.puntos_por_partido);
}

Es importante destacar que toda la instancia debe ser mutable; Rust no permite marcar solo ciertos campos como mutables. Como con cualquier expresión, puede construir una nueva instancia de una estructura como la última expresión en el cuerpo de la función para así realizar un retorno implícito esa nueva instancia.

Por ejemplo, la siguiente es una función crear_jugador, que crea una nueva instancia de JugadorNBA y la retorna.

fn crear_jugador(nombre: String, equipo: String, numero: u32, posicion: String, puntos_por_partido: f32) -> JugadorNBA {
    JugadorNBA {
        nombre: nombre,
        equipo: equipo,
        numero: numero,
        posicion: posicion,
        puntos_por_partido: puntos_por_partido,
    }
}

Ahora, dado que los nombres de los parámetros y los nombres de los campos de la estructura son iguales, podemos utilizar la sintaxis abreviada de inicialización de campos para reescribir crear_jugador de manera que funcione de la misma forma, pero sin completar las parejas clave: valor.

struct JugadorNBA {
    nombre: String,
    equipo: String,
    numero: u32,
    posicion: String,
    puntos_por_partido: f32,
}

fn crear_jugador(nombre: String, equipo: String, numero: u32, posicion: String, puntos_por_partido: f32) -> JugadorNBA {
    JugadorNBA {
        nombre,
        equipo,
        numero,
        posicion,
        puntos_por_partido,
    }
}

fn main() {
    // se invoca a la función crear_jugador
    let mut kobe = crear_jugador(String::from("Kobe Bryant"), String::from("Los Angeles Lakers"), 24, String::from("Escolta"), 25.0);

    // se actualiza el campo numero de la instancia, utilizando el operador punto. 
    kobe.numero = 8;

    println!("Información del jugador:");
    println!("Nombre: {}", kobe.nombre);
    println!("Equipo: {}", kobe.equipo);
    println!("Número: {}", kobe.numero);
    println!("Posición: {}", kobe.posicion);
    println!("Puntos por partido: {}", kobe.puntos_por_partido);
}

Con esta función podemos crear nuevos jugadores. Ejemplo:

let shaq = crear_jugador(
        String::from("Shaquille O'Neal"),
        String::from("Los Angeles Lakers"),
        34,
        String::from("Center"),
        25.0,
    );

Para finalizar, Rust también admite estructuras que se asemejan a tuplas, llamadas "estructuras de tuplas" (tuple structs). Las estructuras de tuplas tienen el significado adicional que proporciona el nombre de la estructura, pero no tienen nombres asociados con sus campos; en cambio, solo tienen los tipos de los campos.

Este tipo de estructuras de tuplas es útil cuando se desea dar un nombre a la tupla completa y hacer que la tupla sea de un tipo diferente a otras tuplas, o cuando nombrar cada campo como en una estructura regular sea redundante o innecesario.

struct Pixel(u8, u8, u8);
struct Posicion(i32, i32);

fn main() {
    let negro = Color(0, 0, 0);
    let blanco = Color(255, 255, 255);
    let origen = Point(0, 0);
    let fin = Point(640, 480);
}

Referencias

  • The Rust Programming Language. 2nd Edition by Steve Klabnik and Carol Nichols, with contributions from the Rust Community. 2023

Did you find this article valuable?

Support Jesse Padilla by becoming a sponsor. Any amount is appreciated!