Transformações mágicas de tipo de dados em Rust: Intrinsika mem :: transmute


1. Introdução


A linguagem de programação Rust , apesar da ideologia abrangente de segurança de dados, também possui métodos de programação inseguros, porque às vezes eles podem aumentar a velocidade eliminando cálculos desnecessários, e às vezes é apenas uma necessidade vital.


Uma delas é a nossa instância atual - intrinsics mem::transmute<T, U> , projetada para elas um pouco, sendo útil em situações extremamente incomuns.


Descrição funcional


mem::transmute<T, U> implementado diretamente pelo compilador, pois, por definição, não pode ser descrito pela sintaxe da linguagem Rust. Simplesmente reinterpreta os bits de um tipo de dados como os bits de outro:


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

Antes da reinterpretação, essa função assume a propriedade da variável reinterpretada do tipo de dados T e depois a esquece (mas sem chamar o destruidor correspondente).


Nenhuma cópia ocorre na memória física, porque essa intrínseca simplesmente deixa claro para o compilador que o conteúdo do tipo T é, de fato, do tipo U


Um erro de compilação ocorrerá se os tipos T e U tiverem comprimentos diferentes. Nem o argumento nem o valor de retorno podem ser valores inválidos .


Comportamento indefinido


Como você deve ter notado, essa função é marcada como insegura, o que é lógico, pois pode gerar um comportamento indefinido devido a vários fatores:


  • Criando uma instância de qualquer tipo com um estado inválido;
  • Criando um primitivo com um valor inválido;
  • Para satisfazer a inferência de tipo, um tipo de saída completamente inesperado é possível se não for especificado;
  • Conversões entre tipos non-repr(C) ;
  • A conversão em um link regular sem um tempo de vida especificado explicitamente resulta em tempo de vida não relacionado ;
  • A conversão de um link imutável em um link mutável.

Oportunidades abertas


No entanto, essa técnica se justifica em alguns casos especiais, por exemplo, obter um padrão de bits de um tipo de dados de ponto flutuante (ou, geralmente, punção de tipo, onde T e U não são ponteiros brutos):


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

Converta um ponteiro bruto em um ponteiro em uma função. Vale ressaltar que essa técnica não é portátil entre as plataformas em que ponteiros de função e ponteiros regulares variam em tamanho.


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

Extensão do tempo de vida ou redução do tempo de vida invariável. Esta é uma sintaxe de Rust muito insegura e ao mesmo tempo avançada:


 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 poupadoras


Basicamente, as pode facilmente impedir um comportamento 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) }; 

E métodos seguros também podem ser usados, fazendo o mesmo que uma chamada semelhante para 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]); 

E esse código-fonte é demonstrado por três implementações da função slice::split_at_mut() : usando mem::transmute<T, U> , as operadores e a função 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)) } } 

Em outras palavras, o uso de mem::transmute<T, U> justificado apenas quando nada mais ajuda (a analogia com a reflexão em algumas línguas é apropriada aqui).


Variação mem :: transmute_copy <T, U>


Os intrínsecos usuais mem::transmute<T, U> podem não ser adequados nos casos em que a transferência de propriedade de uma variável do tipo T impossível. Sua variação mem::transmute_copy<T, U> vem ao resgate:


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

Como você deve ter adivinhado, em vez de mover um único argumento, ele faz uma cópia completa e transfere a propriedade do resultado. Essa variação funcionará mais lentamente, por isso é recomendável usá-la com menos frequência.


Ao contrário de mem::transmute<T, U> , o analógico atual não gera um erro de compilação se os tipos T e U tiverem comprimentos diferentes em bytes, mas é altamente recomendável chamá-lo apenas se tiverem o mesmo tamanho.


Também é importante lembrar que, se um tamanho do tipo U exceder o tamanho de T , essa função gerará um comportamento indefinido.


Conclusão


Mais uma vez, observo que as funções em questão devem ser usadas apenas nos casos em que você não pode ficar sem elas. Como a Nomicon diz, essa é realmente a coisa mais insegura que você pode fazer em Rust.


Todos os exemplos deste artigo são retirados da mem::transmute<T, U> , e os materiais daqui e aqui também foram usados. Espero que o artigo tenha sido útil para você.

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


All Articles