FFI и Rust

Продолжаю бороться с типами. Есть надежда, что FFI (Foreign Function Interface) – это самая сложная и последняя часть, где система типов в Rust будет доставлять серьезные неудобства. Пока что, главное выстраданное правило гласит: если тебе Rust не дает написать какую-то конструкцию, то эта конструкция зло. То есть не надо пытаться обмануть систему типов и писать “как привык в C++”. Довольно простой, если верить документации на сайте Rust, интерфейс FFI оказался с заковырками. Даже пришлось создать маленькую песочницу для игр именно с FFI.

Наверное самая поразившая меня фича в этой области Rust-а – преобразование типов, особенно при работе с массивами. Простейший пример (type_of из предыдущего поста):

let array: &[u8] = unsafe { mem::transmute("Rust") };       // (1)
println!("type: {}, ptr: 0x{:x}, len {}",
    type_of(&array), array.as_ptr() as u64, array.len());

let new_array: &[u32] = unsafe { mem::transmute(array) };   // (2)
println!("type: {}, ptr: 0x{:x}, len {}",
    type_of(&new_array), new_array.as_ptr() as u64, new_array.len());

Классический вопрос из разряда “а что оно напечатает?”. Кажется что всё невероятно просто, создаем массив 1 из uchar, размер которого будет 4. Конвертируем 2 массив uchar в массив uint32 с размером 1. В итоге лично я ожидал чего-то такого:

type: &'static [u8], ptr: 0x1037a1414, len 4
type: &'static [u32], ptr: 0x1037a1414, len 4

Но был сильно удивлен. Дело в том, что по мнению компилятора Rust второй массив хоть и является массивом uint32, но по прежнему содержит 4 элемента, т.е. конверсия делается в лоб и только для типа, но не размера и физический размер “вырос” в 4 раза без перераспределения памяти.

При этом вроде как правильное решение будет выглядеть следующим образом:

let new_array_2 = unsafe {
    slice::from_raw_parts_mut(array.as_ptr() as *mut u32,
        array.len() / mem::size_of::<u32>())
};
println!("type: {}, ptr: 0x{:x}, len {}",
    type_of(new_array_2), new_array_2.as_ptr() as u64, new_array_2.len());

Хотя меня гложут сомнения на тему того, что я правильно всё делаю, так как вывести новый размер массива вроде очень просто из чего следует что я вызвал какую-то неправильную функцию, или правильную, но криво…

Мелкие пакости: время жизни переменной в Rust

Допустим, хочется получить текстовое представление типа переменной в Rust. При этом в язык входит такая замечательная функция как type_name() -> &’static str принимающая тип выдающая его тектовое обозначение. Само собой, хочет применить его не только для типа (название типа не так уж и полезно в диагностических целях), а к переменной. Логичным для C++ разработчика выглядит приблизительно следующее решение:

fn type_of<'a, T>(_: T) -> &'a str {
    unsafe { std::intrinsics::type_name::<T>() }
}

Но тут возникнет довольно занятная проблема, так как переменная становится недоступной после (с некоторыми ньюансами в зависимости от типа) получения её имени:

error: use of moved value: `*variable_name` [E0382]

После небольшой фрустрации понимаешь, что в принципе это ж фича и компилятор не должен догадываться о моих намерениях только лишь получить тип, а не реально использовать значение. Но делать что-то нужно. Единственным подходящим решением оказывается передача по ссылке (ссылке в понимании Rust, а не C++), что ожидаемо, но немного странно для C++ разработчика.

fn type_of<'a, T>(_: &T) -> &'a str {
    unsafe { std::intrinsics::type_name::<T>() }
}

Вообще, все эти мелкие пакости модели памяти постоянно преследуют при программировании на Rust. Никак не могу понять, это реально зло или я просто еще не привык и просто мыслю моделью памяти C++?