Transformaciones de tipo de datos mágicos en Rust: Intrinsika mem :: transmute


Introduccion


El lenguaje de programación Rust , a pesar de la ideología integral de la seguridad de los datos, también tiene métodos de programación inseguros, porque a veces pueden aumentar la velocidad al eliminar cálculos innecesarios, y a veces es solo una necesidad vital.


Una de ellas es nuestra instancia actual: intrínseca mem::transmute<T, U> , diseñada para ambos un poco, lo que resulta útil en situaciones extremadamente inusuales.


Descripción Funcional


mem::transmute<T, U> implementado directamente por el compilador, ya que por definición no puede ser descrito por la sintaxis del lenguaje Rust. Simplemente reinterpreta los bits de un tipo de datos como los bits de otro:


 pub unsafe extern "rust-intrinsic" fn transmute<T, U>(e: T) -> U 

Antes de reinterpretar, esta función se hace cargo de la propiedad de la variable reinterpretada del tipo de datos T y luego la "olvida" (pero sin llamar al destructor correspondiente).


No se realiza ninguna copia en la memoria física, porque esta característica intrínseca simplemente deja en claro al compilador que los contenidos del tipo T son, de hecho, del tipo U


Se producirá un error de compilación si los tipos T y U tienen longitudes diferentes. Ni el argumento ni el valor de retorno pueden ser valores no válidos .


Comportamiento indefinido


Como habrás notado, esta función está marcada como insegura, lo cual es lógico, porque puede generar un comportamiento indefinido debido a muchos factores:


  • Crear una instancia de cualquier tipo con un estado no válido;
  • Crear una primitiva con un valor no válido;
  • Para satisfacer la inferencia de tipos, es posible un tipo de salida completamente inesperado si no se especifica;
  • Conversiones entre tipos non-repr(C) ;
  • La conversión a un enlace regular sin un tiempo de vida especificado explícitamente da como resultado un tiempo de vida no relacionado ;
  • La conversión de un enlace inmutable a uno mutable.

Oportunidades abiertas


Sin embargo, esta técnica se justifica en algunos casos especiales, por ejemplo, la obtención de un patrón de bits de un tipo de datos de punto flotante (o, más generalmente, el punteo de tipos, donde T y U no son punteros sin formato):


 let bitpattern = unsafe { std::mem::transmute::<f32, u32>(1.0) }; assert_eq!(bitpattern, 0x3F800000); 

Convierta un puntero sin formato en un puntero en una función. Vale la pena señalar que esta técnica no es portátil entre aquellas plataformas donde los punteros de función y los punteros regulares varían en tamaño.


 fn foo() -> i32 { 0 } let pointer = foo as *const (); let function = unsafe { std::mem::transmute::<*const (), fn() -> i32>(pointer) }; assert_eq!(function(), 0); 

Extensión del tiempo de vida o acortamiento del tiempo de vida invariante. Esta es una sintaxis Rust muy insegura y al mismo tiempo avanzada:


 struct R<'a>(&'a i32); unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> { std::mem::transmute::<R<'b>, R<'static>>(r) } unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> { std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r) } 

Alternativas de ahorro


Básicamente, as puede evitar sin esfuerzo un comportamiento indefinido causado por mem::transmute<T, U> :


 let ptr = &mut 0; let val_transmuted = unsafe { std::mem::transmute::<&mut i32, &mut u32>(ptr) }; //   `as`  .  ,  //   `as    let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) }; 

Y también se pueden usar métodos seguros, haciendo lo mismo que una llamada similar a mem::transmute<T, U> :


 //        //    let slice = unsafe { std::mem::transmute::<&str, &[u8]>("Rust") }; assert_eq!(slice, &[82, 117, 115, 116]); //      `as_bytes()`,  //     let slice = "Rust".as_bytes(); assert_eq!(slice, &[82, 117, 115, 116]); //      //   assert_eq!(b"Rust", &[82, 117, 115, 116]); 

Y este código fuente se demuestra mediante tres implementaciones de la función slice::split_at_mut() : usando mem::transmute<T, U> , as operadores y la función slice::from_raw_parts() .


 use std::{slice, mem}; //     ,    //     fn split_at_mut_transmute<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { let len = slice.len(); assert!(mid <= len); unsafe { let slice2 = mem::transmute::<&mut [T], &mut [T]>(slice); // -, mem::transmute<T, U>   , //  ,    -    T  U. // // -,      ,  //        (&mut slice[0..mid], &mut slice2[mid..len]) } } //       ; `&mut *` //    `&mut T`  `&mut T`  `*mut T`. fn split_at_mut_casts<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { let len = slice.len(); assert!(mid <= len); unsafe { let slice2 = &mut *(slice as *mut [T]); //        ,  //        (&mut slice[0..mid], &mut slice2[mid..len]) } } //    ,    //  fn split_at_stdlib<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { let len = slice.len(); assert!(mid <= len); unsafe { let ptr = slice.as_mut_ptr(); //      ,  //    : `slice`, r-value ret.0  r-value ret.1. // // `slice`     `let ptr = ...`,   // ,    "",  ,  //      . (slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), len - mid)) } } 

En otras palabras, el uso de mem::transmute<T, U> justifica solo cuando nada más ayuda (la analogía con la reflexión en algunos idiomas es apropiada aquí).


Variación mem :: transmute_copy <T, U>


Los intrínsecos habituales mem::transmute<T, U> pueden no ser adecuados en los casos en que la transferencia de la propiedad de una variable de tipo T imposible. Su variación mem::transmute_copy<T, U> viene al rescate:


 pub unsafe fn transmute_copy<T, U>(src: &T) -> U 

Como habrás adivinado, en lugar de mover un solo argumento, hace una copia completa y transfiere la propiedad del resultado. Esta variación funcionará más lentamente, por lo que se recomienda usarla con menos frecuencia.


A diferencia de mem::transmute<T, U> , el análogo actual no genera un error de compilación si los tipos T y U tienen longitudes diferentes en bytes, pero se recomienda llamarlo solo si tienen el mismo tamaño.


También vale la pena recordar que si un tamaño de tipo U excede un tamaño de T , entonces esta función genera un comportamiento indefinido.


Conclusión


Una vez más, noto que las funciones en cuestión solo deben usarse en aquellos casos en que no pueda prescindir de ellas. Como dice Nomicon , esto es realmente lo más inseguro que puedes hacer en Rust.


Todos los ejemplos de este artículo están tomados de la mem::transmute<T, U> , y también se utilizaron materiales de aquí y de aquí . Espero que el artículo te haya sido útil.

Source: https://habr.com/ru/post/448240/


All Articles