- PVSM.RU - https://www.pvsm.ru -

Магические трансформации типов данных в Rust: Интринсика mem::transmute<T, U>

Магические трансформации типов данных в Rust: Интринсика mem::transmute<T, U> - 1

Введение

Язык программирования Rust [1], невзирая на всеохватывающую идеологию безопасности данных, располагает и небезопасными методиками программирования, ведь порой они могут повышать скорость путём устранения лишних вычислений, а порой это просто жизненная необходимость.

Одной из них является наш сегодняшний экземпляр — интринсика mem::transmute<T, U> [2], предназначенная для того и другого понемногу, пригождаясь в крайне необычных ситтуациях.

Описание функционала

mem::transmute<T, U> реализована непосредственно компилятором, так как по определению не может быть описана синтаксическими средствами языка Rust. Она попросту реинтерпретирует биты одного типа данных как биты другого:

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

Перед реинтерпретацией данная функция берёт на себя владение реинтерпретируемой переменной типа данных T, а после будто "забывает" её (но без вызова соответствующего деструктора).

Никакого копирования в физической памяти не происходит, ибо данная интринсика просто даёт понять компилятору, что содержимое типа T — это, на самом деле, тип U.

Ошибка компиляции возникнет в том случае, если типы T и U имеют разные длины. Ни аргумент, ни возвращаемое значение не могут выступать неправильными значениями [3].

Неопределённое поведение

Как Вы могли уже заметить, данная функция помечена как небезопасная, что логично, ибо она способна порождать неопределённое поведение [4] вследствие множества факторов:

  • Создание экземпляра любого типа с недопустимым состоянием;
  • Создание примитива с недопустимым значением;
  • Чтобы удоволетворить вывод типов, возможно порождение совершенно неожиданного выходного типа при его неуказании;
  • Преобразования между non-repr(C) типами;
  • Преобразование в обычную ссылку без явно заданного лайфтайма приводит к несвязанному лайфтайму [5];
  • Конверсия иммутабельной ссылки к мутабельной.

Открытые возможности

Однако данная методика оправдывает себя в некоторых частных случах, например, получения бит-паттерна типа данных с плавающей точкой (или, что более обобщённо, type punning, где 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 [6] может без особых усилий предотвратить появление неопределённого поведения, вызванного 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() [7]: с использованием mem::transmute<T, U>, as операторов и функции slice::from_raw_parts() [8].

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> оправдано лишь в тех случаях, когда ничто другое уже не помогает (здесь уместна аналогия с рефлексией в некоторых языках).

Вариация mem::transmute_copy<T, U>

Обычная интринсика mem::transmute<T, U> может оказаться непригодной в тех случах, где передача владения переменной типа T невозможна. На помощь приходит её вариация mem::transmute_copy<T, U> [9]:

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

Как Вы уже могли догадаться, она вместо перемещения единственного аргумента совершает его полное копирование и передаёт владение полученного результата. Данная вариация будет работать медленнее, поэтому её рекомендуется применять реже.

В отличие от mem::transmute<T, U>, текущий аналог не порождает ошибку компиляции, если типы T и U имеют разную длину в байтах, но вызывать её настоятельно рекомендуется лишь в том случае, когда они имеют одинаковый размер.

Также стоит помнить о том, что если размер типа U превышает размер T, то данная функция порождает неопределённое поведение.

Заключение

Ещё раз отмечу, что рассматриваемые функции следует применять лишь в тех случаях, когда без них совсем не обойтись. Как говорит Номикон [10], это действительно самая небезопасная вещь, которую вы можете сделать в Rust.

Все примеры из данной статьи взяты с официальной документации mem::transmute<T, U> [2], а также были использованы материалы отсюда [9] и отсюда [10]. Надеюсь, статья была Вам полезна.

Автор: Gymmasssorla

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/nenormal-noe-programmirovanie/314744

Ссылки в тексте:

[1] Rust: https://www.rust-lang.org/tools/install

[2] mem::transmute<T, U>: https://doc.rust-lang.org/std/mem/fn.transmute.html

[3] неправильными значениями: https://doc.rust-lang.org/nomicon/what-unsafe-does.html

[4] неопределённое поведение: https://doc.rust-lang.org/reference/behavior-considered-undefined.html

[5] несвязанному лайфтайму: https://doc.rust-lang.org/nomicon/unbounded-lifetimes.html

[6] оператор as: https://doc.rust-lang.org/std/keyword.as.html

[7] slice::split_at_mut(): https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut

[8] slice::from_raw_parts(): https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html

[9] mem::transmute_copy<T, U>: https://doc.rust-lang.org/std/mem/fn.transmute_copy.html

[10] Номикон: https://doc.rust-lang.org/nomicon/transmutes.html

[11] Источник: https://habr.com/ru/post/448240/?utm_campaign=448240