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 campobcomienza exactamente en el offset 4 para cumplir con la alineación de 4 bytes requerida poru32. Elsize_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 aWrappercomo si fuera exactamente unu32.size_of::<Wrapper>()yalign_of::<Wrapper>()devuelven los mismos valores queu32.PackedStruct: El atributo#[repr(packed)]elimina el padding entreayb. Esto reduce elsize_of::<PackedStruct>()a 5 bytes, pero el campobestará 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 deSmallEnumocupe exactamente 1 byte, permitiendo predecir su tamaño.std::mem: Se utilizansize_of::<T>()para obtener el tamaño en bytes,align_of::<T>()para la alineación requerida yoffset_of!(T, field)para calcular la distancia en bytes desde el inicio de la estructura hasta el campofield.
N° 130