Estructuras (struct)

Las estructuras en Rust y C# comparten algunas similitudes:

  • Se definen con la palabra clave struct, pero en Rust, struct simplemente define los datos/campos. Los aspectos de comportamiento en términos de funciones y métodos se definen por separado en un bloque de implementación (impl).

  • Pueden implementar múltiples traits en Rust de la misma manera que pueden implementar múltiples interfaces en C#.

  • No pueden ser subclasificadas.

  • Se asignan en la pila (stack) por defecto, a menos que:

    • En .NET, se haga boxing o se castee a una interfaz.
    • En Rust, se envuelvan en un puntero inteligente como Box, Rc/Arc.

En C#, un struct es una forma de modelar un value type (tipos de valor) en .NET, que suele ser algún primitivo específico del dominio o compuesto con semántica de igualdad de valores. En Rust, un struct es la construcción principal para modelar cualquier estructura de datos (la otra siendo un enum).

Un struct (o record struct) en C# tiene copia por valor y semántica de igualdad de valores por defecto, pero en Rust, esto requiere simplemente un paso más utilizando el atributo #derive y enumerando los traits que se deben implementar:

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct Point {
    x: i32,
    y: i32,
}

En C#/.NET, los Value Types suelen ser diseñados por un desarrollador para ser inmutables. Se considera una práctica recomendada desde el punto de vista semántico, pero el lenguaje no impide diseñar un struct que realice modificaciones destructivas o en el lugar. En Rust, es lo mismo. Un tipo debe ser conscientemente desarrollado para ser inmutable.

Dado que Rust no tiene clases y, en consecuencia, jerarquías de tipos basadas en la subclase, el comportamiento compartido se logra mediante traits y genéricos, y el polimorfismo a través de la despacho virtual utilizando trait objects.

Considera la siguiente struct que representa un rectángulo en C#:

struct Rectangle
{
    public Rectangle(int x1, int y1, int x2, int y2) =>
        (X1, Y1, X2, Y2) = (x1, y1, x2, y2);

    public int X1 { get; }
    public int Y1 { get; }
    public int X2 { get; }
    public int Y2 { get; }

    public int Length => Y2 - Y1;
    public int Width => X2 - X1;

    public (int, int) TopLeft => (X1, Y1);
    public (int, int) BottomRight => (X2, Y2);

    public int Area => Length * Width;
    public bool IsSquare => Width == Length;

    public override string ToString() => $"({X1}, {Y1}), ({X2}, {Y2})";
}

El equivalente en Rust sería:

#![allow(dead_code)]

struct Rectangle {
    x1: i32, y1: i32,
    x2: i32, y2: i32,
}

impl Rectangle {
    pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
        Self { x1, y1, x2, y2 }
    }

    pub fn x1(&self) -> i32 { self.x1 }
    pub fn y1(&self) -> i32 { self.y1 }
    pub fn x2(&self) -> i32 { self.x2 }
    pub fn y2(&self) -> i32 { self.y2 }

    pub fn length(&self) -> i32 {
        self.y2 - self.y1
    }

    pub fn width(&self)  -> i32 {
        self.x2 - self.x1
    }

    pub fn top_left(&self) -> (i32, i32) {
        (self.x1, self.y1)
    }

    pub fn bottom_right(&self) -> (i32, i32) {
        (self.x2, self.y2)
    }

    pub fn area(&self)  -> i32 {
        self.length() * self.width()
    }

    pub fn is_square(&self)  -> bool {
        self.width() == self.length()
    }
}

use std::fmt::*;

impl Display for Rectangle {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "({}, {}), ({}, {})", self.x1, self.y2, self.x2, self.y2)
    }
}

Ten en cuenta que un struct en C# hereda el método ToString de object y, por lo tanto, anula la implementación base para proporcionar una representación de cadena personalizada. Dado que no hay herencia en Rust, la forma en que un tipo indica el soporte para alguna representación formateada es mediante la implementación del trait Display. Esto permite que una instancia de la estructura participe en el formateo, como se muestra en la llamada a println! a continuación:

fn main() {
    let rect = Rectangle::new(12, 34, 56, 78);
    println!("Rectangle = {rect}");
}