はじめに
Rustプログラミング言語には、データセキュリティの包括的なイデオロギーにも関わらず、不必要な計算を排除することで速度を上げることができる場合や、必要不可欠な場合があるため、安全でないプログラミング方法があります。
それらの1つは現在のインスタンスです-組み込み関数mem::transmute<T, U>
、それらの両方のために少し設計されており、非常に珍しい状況で役立ちます。
機能説明
mem::transmute<T, U>
、定義によってRust言語の構文では記述できないため、コンパイラによって直接実装されます。 あるデータ型のビットを別のデータ型のビットとして単純に再解釈します。
pub unsafe extern "rust-intrinsic" fn transmute<T, U>(e: T) -> U
再解釈する前に、この関数はデータ型T
再解釈された変数の所有権を引き継ぎ、その後「忘れる」(ただし、対応するデストラクタを呼び出さずに)。
物理メモリにはコピーがありません。この組み込み関数は、コンパイラに対して、タイプT
の内容が実際にはタイプU
であることを明確にするだけですU
タイプT
とU
長さが異なる場合、コンパイルエラーが発生します。 引数も戻り値も無効な値にはできません。
未定義の動作
お気づきかもしれませんが、この関数は多くの要因により未定義の動作を生成する可能性があるため、安全でないとマークされています。これは論理的です。
- 無効な状態の任意のタイプのインスタンスを作成します。
- 無効な値を持つプリミティブを作成します。
- 型の推測を満たすために、指定されていない場合は完全に予期しない出力型が可能です。
-
non-repr(C)
タイプ間の変換。 - ライフタイムを明示的に指定せずに通常のリンクに変換すると、 無関係なライフタイムが発生します。
- 不変リンクから可変リンクへの変換。
オープンな機会
ただし、この手法は、たとえば浮動小数点(または、より一般的には、 T
およびU
が生のポインターではない型のパンニング)を使用してデータ型のビットパターンを取得するなど、いくつかの特別な場合にそれ自体を正当化します:
let bitpattern = unsafe { std::mem::transmute::<f32, u32>(1.0) }; assert_eq!(bitpattern, 0x3F800000);
生のポインターを関数ポインターに変換します。 この手法は、関数ポインターと通常のポインターのサイズが異なるプラットフォーム間で移植できないことに注意してください。
fn foo() -> i32 { 0 } let pointer = foo as *const (); let function = unsafe { std::mem::transmute::<*const (), fn() -> i32>(pointer) }; assert_eq!(function(), 0);
寿命の延長または不変の寿命の短縮。 これは非常に安全ではなく、同時に高度なRust構文です。
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) }
代替案
基本的に as
は、 mem::transmute<T, U>
によって引き起こされる未定義の動作を簡単に防ぐことができ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) };
また、 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]);
そして、このソースコードは、 slice::split_at_mut()
関数の3つの実装によって実証されていslice::split_at_mut()
mem::transmute<T, U>
as
演算子as
し、 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)) } }
言い換えれば、 mem::transmute<T, U>
他に何も役に立たない場合にのみ正当化されます(一部の言語でのリフレクションとの類推はここで適切です)。
バリエーションメモリ:: transmute_copy <T、U>
通常の組み込み関数mem::transmute<T, U>
はmem::transmute<T, U>
T
型の変数の所有権の譲渡T
不可能な場合には不適切な場合があります。 そのバリエーションmem::transmute_copy<T, U>
がmem::transmute_copy<T, U>
ます。
pub unsafe fn transmute_copy<T, U>(src: &T) -> U
ご想像のとおり、1つの引数を移動する代わりに、完全なコピーを作成し、結果の所有権を転送します。 このバリエーションの動作は遅くなるため、使用頻度を減らすことをお勧めします。
mem::transmute<T, U>
とは異なり、タイプT
とU
長さがバイト単位で異なる場合、現在のアナログではコンパイルエラーは発生しませんが、同じサイズの場合にのみ呼び出すことを強くお勧めします。
また、タイプU
のサイズがT
のサイズを超える場合、この関数は未定義の動作を生成することを覚えておく価値があります。
おわりに
繰り返しますが、問題の機能は、これらの機能なしでは実行できない場合にのみ使用する必要があります。 Nomiconが言うように、これは本当にRustでできる最も安全でないことです。
この記事のすべての例は、 mem::transmute<T, U>
から取得したものであり、こことここの資料も使用しました。 この記事がお役に立てば幸いです。