Estructuras en Rust

En Rust, tanto las estructuras (Struct) como las tuplas (Tuple) pueden agrupar varios datos de tipos no necesariamente iguales para formar un todo, pero cada miembro de una estructura y la estructura misma tienen un nombre, de modo que al acceder a sus miembros no es necesario recordar el índice. Las tuplas se usan comúnmente para pasar múltiples valores no definidos, mientras que las estructuras se usan para estandarizar estructuras de datos comunes. Cada miembro de una estructura se llama "campo".

Definición de estructura

Esta es una definición de estructura:

struct Site {
    domain: String,
    name: String,
    nation: String,
    found: u32
}

Nota: si sueles usar C/C++, recuerda que en Rust la declaración `struct` solo se usa para definir, no para declarar instancias, y no necesita;símbolo al final, y después de cada definición de campo se usa,para separar.

Instancia de estructura

Rust está influenciado por JavaScript en muchos aspectos; al instanciar estructuras, utiliza la sintaxis de objetos JSONkey: valuepara realizar la definición:

Ejemplo

let ejemplo = Site {
    domain: String::from("www.example.com"),
    name: String::from("EJEMPLO"),
    nation: String::from("China"),
    found: 2013
};

Si no conoces los objetos JSON, no tienes que preocuparte por ellos, solo recuerda el formato:

Nombre de la clase de estructura {
    Nombre del campo : Valor del campo,
    ...
}

La ventaja de esto es que no solo hace el programa más intuitivo, sino que tampoco necesitas ingresar los valores de los miembros en el orden de definición.

Si la estructura que se está instanciando tiene nombres de campos iguales a los nombres de variables existentes, se puede simplificar la escritura:

Ejemplo


let domain = String::from("www.example.com");
let name = String::from("EJEMPLO");
let ejemplo = Site {
    domain,  // equivalente a domain: domain,
    name,    // equivalente a name: name,
    nation: String::from("China"),
    traffic: 2013
};

Hay una situación así: quieres crear una nueva instancia de una estructura, donde la mayoría de las propiedades deben establecerse igual que las de una estructura existente, y solo necesitas cambiar el valor de uno o dos campos. Puedes usar la sintaxis de actualización de estructuras:

let site = Site {
    domain: String::from("www.example.com"),
    name: String::from("EJEMPLO"),
    ..ejemplo
};

Nota:..ejemploDespués de `..` no puede haber coma. Esta sintaxis no permite copiar exactamente otra instancia de estructura, es decir, al menos debes reasignar el valor de un campo para poder hacer referencia a los valores de otra instancia.

Estructura de tupla

Hay una forma más simple de definir y usar estructuras:Estructura de tupla。

Una estructura de tupla es una estructura que tiene la forma de una tupla.

La diferencia con una tupla es que tiene un nombre y un formato de tipo fijo. Su razón de ser es manejar datos simples que necesitan definirse como tipo (de uso frecuente) sin ser demasiado complejos:

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

let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0);

"color" y "coordenada de punto" son dos tipos de datos comunes, pero si al instanciar hay que escribir unas llaves y luego dos nombres, se sacrifica la conveniencia por la legibilidad. Rust no deja este problema sin resolver. Los objetos de una estructura de tupla se usan igual que las tuplas, accediendo mediante `.` y el índice:

Ejemplo

fn main() {
    struct Color(u8, u8, u8);
    struct Point(f64, f64);

    let black = Color(0, 0, 0);
    let origin = Point(0.0, 0.0);

    println!("black = ({}, {}, {})", black.0, black.1, black.2);
    println!("origin = ({}, {})", origin.0, origin.1);
}

Resultado de la ejecución:

black = (0, 0, 0)
origin = (0, 0)

Propiedad de la estructura

Una estructura debe poseer la propiedad de los valores de sus campos, porque cuando la estructura deja de ser válida, libera todos sus campos.

Esta es la razón por la cual en los ejemplos de este capítulo se usa el tipo `String` y no `&str`.

Pero esto no significa que no se puedan definir campos de tipo referencia en las estructuras; esto se logra mediante el mecanismo de "ciclo de vida" (lifetime).

Pero por ahora es difícil explicar el concepto de "ciclo de vida", así que solo se explicará en capítulos posteriores.

Imprimir estructuras

Durante la depuración, mostrar completamente una instancia de estructura es muy útil. Pero escribir manualmente un formato es muy inconveniente. Por eso Rust proporciona una forma conveniente de imprimir una estructura completa:

Ejemplo

#[derive(Debug)]

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

    println!("rect1 is {:?}", rect1);
}

Como se muestra en la primera línea: asegúrate de importar la biblioteca de depuración#[derive(Debug)]después, en las macros `println` y `print` se puede usar{:?}el marcador de posición para imprimir una estructura completa:

rect1 is Rectangle { width: 30, height: 50 }

Si hay muchos campos, se puede usar otro marcador de posición{:#?} 。

Salida:

rect1 is Rectangle {
    width: 30,
    height: 50
}

Métodos de la estructura

Los métodos (Method) y las funciones (Function) son similares, solo que los métodos se usan para operar sobre instancias de la estructura.

Si has estudiado algunos lenguajes orientados a objetos, seguramente sabes que las funciones generalmente se colocan dentro de la clase y en la función se usa `this` para referirse a la instancia sobre la que se opera.

Rust no es un lenguaje orientado a objetos, como se puede ver por la innovación de su mecanismo de propiedad. Pero las valiosas ideas de la programación orientada a objetos se pueden implementar en Rust.

El primer parámetro de un método de estructura debe ser `&self`, no es necesario declarar su tipo, porque `self` no es un estilo sino una palabra clave.

Calcular el área de un rectángulo:

Ejemplo

struct Rectangle {
    width: u32,
    height: u32,
}
   
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("rect1's area is {}", rect1.area());
}

Salida:

rect1's area is 1500

Ten en cuenta que al llamar a un método de estructura no es necesario escribir `self`, esto se debe a consideraciones de conveniencia.

Un ejemplo con varios parámetros:

Ejemplo

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn wider(&self, rect: &Rectangle) -> bool {
        self.width > rect.width
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 40, height: 20 };

    println!("{}", rect1.wider(&rect2));
}

Resultado de la ejecución:

false

Este programa calcula si `rect1` es más ancho que `rect2`.


Funciones asociadas a la estructura

La razón por la cual los "métodos de estructura" no se llaman "funciones de estructura" es que el nombre "función" se reserva para este tipo de función: está en un bloque `impl` pero no tiene parámetro `&self`.

Este tipo de función no depende de una instancia, pero para usarla se debe declarar en qué bloque `impl` está.

La función que siempre usamosString::from`String::from` es una "función asociada".

Ejemplo

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn create(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    let rect = Rectangle::create(30, 50);
    println!("{:?}", rect);
}

Resultado de la ejecución:

Rectangle { width: 30, height: 50 }

Consejo:Los bloques `impl` de una estructura se pueden escribir varias veces, ¡el efecto equivale a concatenar su contenido!

Estructura unitaria

Una estructura puede ser solo un símbolo sin ningún miembro:

struct UnitStruct;

A este tipo de estructura sin cuerpo lo llamamos estructura unitaria (Unit Struct).

Otras extensiones