En Rust, el diseño de memoria (layout) de una estructura no está garantizado por defecto. El compilador tiene la libertad de reordenar los campos de un struct para minimizar el padding y reducir el tamaño total de la estructura en memoria. Aunque esto optimiza el uso de recursos, hace que la estructura sea incompatible con otros lenguajes como C, lo que impide el uso de FFI (Foreign Function Interface).

Para controlar cómo se organizan los datos, se utiliza el atributo #[repr]. #[repr(C)] es fundamental cuando se trabaja con librerías de C o C++, ya que obliga al compilador a seguir las reglas de alineación y orden de campos de C, manteniendo el orden tal como se declaró. Por otro lado, #[repr(transparent)] se utiliza en el patrón newtype (un struct con un solo campo) para garantizar que el envoltorio sea bitwise idéntico al tipo contenido; esto es vital para que el tipo sea intercambiable en interfaces FFI.

Existen otros modificadores críticos: #[repr(packed)] elimina todo el padding entre campos, compactando la estructura al máximo, pero su uso es peligroso porque puede causar accesos desalineados (misaligned accesses), lo cual es Comportamiento Indefinido (UB) en muchas arquitecturas al intentar obtener una referencia a un campo desalineado. #[repr(align(N))] permite forzar una alineación específica (por ejemplo, para optimizaciones SIMD o caché), y en los enum, #[repr(u8/i32/etc.)] permite definir el tamaño del discriminante (el valor entero que identifica cada variante).

use std::mem::{align_of, offset_of, size_of};

#[repr(C)]
struct CCompatible {
    a: u8,    // 1 byte
    // 3 bytes de padding para alinear 'b' a 4 bytes
    b: u32,   // 4 bytes
    c: u8,    // 1 byte
    // 3 bytes de padding final para alinear el struct al tamaño de su mayor miembro
}

#[repr(transparent)]
struct Wrapper(u32);

#[repr(packed)]
struct PackedStruct {
    a: u8,
    b: u32, // Sin padding entre 'a' y 'b'
}

#[repr(align(64))]
struct AlignedStruct(u32);

#[repr(u8)]
enum SmallEnum {
    A = 1,
    B = 2,
}

fn main() {
    // Análisis de CCompatible (Layout de C)
    println!("--- CCompatible ---");
    println!("Size: {} bytes, Align: {} bytes", size_of::<CCompatible>(), align_of::<CCompatible>());
    println!("Offset de 'b': {} bytes", offset_of!(CCompatible, b));

    // Análisis de Wrapper (Transparent)
    println!("\n--- Wrapper (Transparent) ---");
    assert_eq!(size_of::<Wrapper>(), size_of::<u32>());
    assert_eq!(align_of::<Wrapper>(), align_of::<u32>());
    println!("Wrapper es idéntico a u32");

    // Análisis de PackedStruct
    println!("\n--- PackedStruct ---");
    println!("Size: {} bytes (sin padding)", size_of::<PackedStruct>());

    // Análisis de AlignedStruct
    println!("\n--- AlignedStruct ---");
    println!("Align: {} bytes", align_of::<AlignedStruct>());

    // Análisis de SmallEnum
    println!("\n--- SmallEnum ---");
    println!("Size: {} byte", size_of::<SmallEnum>());
}

Explicación del Código

  • CCompatible: Gracias a #[repr(C)], el campo b comienza exactamente en el offset 4 para cumplir con la alineación de 4 bytes requerida por u32. El size_of::<CCompatible>() será 12 debido al padding interno y al padding de finalización. offset_of!(CCompatible, b) devuelve 4.
  • Wrapper: Al usar #[repr(transparent)], el compilador trata a Wrapper como si fuera exactamente un u32. size_of::<Wrapper>() y align_of::<Wrapper>() devuelven los mismos valores que u32.
  • PackedStruct: El atributo #[repr(packed)] elimina el padding entre a y b. Esto reduce el size_of::<PackedStruct>() a 5 bytes, pero el campo b estará desalineado en la memoria.
  • AlignedStruct: #[repr(align(64))] fuerza a que cualquier instancia de esta estructura comience en una dirección de memoria múltiplo de 64, independientemente de que su contenido (u32) solo requiera 4.
  • SmallEnum: #[repr(u8)] asegura que el discriminante de SmallEnum ocupe exactamente 1 byte, permitiendo predecir su tamaño.
  • std::mem: Se utilizan size_of::<T>() para obtener el tamaño en bytes, align_of::<T>() para la alineación requerida y offset_of!(T, field) para calcular la distancia en bytes desde el inicio de la estructura hasta el campo field.

130