Rust programming language, despite the comprehensive ideology of data security, has unsafe programming techniques, because sometimes they can increase speed by eliminating unnecessary calculations, and sometimes it is just a vital necessity.
One of them is our today's copy - intrinsika mem::transmute<T, U>
, intended for this and another little by little, coming in handy in extremely unusual situations.
mem::transmute<T, U>
implemented directly by the compiler, since, by definition, it cannot be described by the syntactic means of the Rust language. It simply reinterprets the bits of one data type as bits of another:
pub unsafe extern "rust-intrinsic" fn transmute<T, U>(e: T) -> U
Before reinterpretation, this function assumes ownership of the re-interpreted data type variable T
, and afterwards “forgets” it (but without calling the appropriate destructor).
No copying in physical memory occurs, because this intrinsic simply makes it clear to the compiler that the contents of type T
are, in fact, type U
A compilation error will occur if the types T
and U
have different lengths. Neither the argument nor the return value can be incorrect values .
As you may have already noticed, this function is marked as unsafe, which is logical, because it can generate undefined behavior due to many factors:
non-repr(C)
types;However, this technique justifies itself in some particular cases, for example, getting a bit-pattern of a floating-point data type (or, more generally, type punning, where T
and U
are not raw pointers):
let bitpattern = unsafe { std::mem::transmute::<f32, u32>(1.0) }; assert_eq!(bitpattern, 0x3F800000);
Converting a raw pointer to a pointer to a function. It should be noted that this technique is not portable between those platforms where pointers to functions and regular pointers vary in size.
fn foo() -> i32 { 0 } let pointer = foo as *const (); let function = unsafe { std::mem::transmute::<*const (), fn() -> i32>(pointer) }; assert_eq!(function(), 0);
Extension of liftime or shortening invariant liftime. This is a very insecure and at the same time advanced Rust syntax:
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) }
Basically, as
can, with little effort, prevent the appearance of undefined behavior caused by 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) };
And also safe methods can be used that do the same as a similar call to 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]);
And this source code is demonstrated by three implementations of the slice::split_at_mut()
function: using mem::transmute<T, U>
, as
operators, and the slice::from_raw_parts()
function.
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)) } }
In other words, the use of mem::transmute<T, U>
justified only in cases when nothing else helps (the analogy with reflection in some languages ​​is appropriate here).
The usual intrinsic mem::transmute<T, U>
may be unsuitable in those cases where the transfer of ownership of a variable of type T
impossible. Its variation comes to the rescue mem::transmute_copy<T, U>
:
pub unsafe fn transmute_copy<T, U>(src: &T) -> U
As you might have guessed, instead of moving a single argument, it makes a full copy of it and transfers ownership of the result. This variation will work slower, so it is recommended to use it less often.
Unlike mem::transmute<T, U>
, the current analog does not cause a compilation error if the types T
and U
have different lengths in bytes, but it is strongly recommended to cause it only if they are the same size.
It is also worth remembering that if the size of type U
exceeds the size of T
, then this function generates an indefinite behavior.
Once again, I note that the functions in question should be used only in those cases when you cannot do without them. As Nomikon says, this is actually the most insecure thing you can do in Rust.
All examples from this article are taken from the mem::transmute<T, U>
, and also materials from here and here were used. I hope the article was helpful.
Source: https://habr.com/ru/post/448240/
All Articles