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++?

Rust в Dropbox

Как мне кажется, не так давно произошло довольно знаковое событие для мира C++: Dropbox заявил о том, что перевел часть своей инфраструктуры на компоненты написанные на Rust. Чуть позже программисты из Dropbox ответили на вопросы интересующихся посвященные этой миграции. Немного интересных ответов:

> Are you happy using rust ?
Yes, overall the team has been very pleased with it. Compile times are the only serious complaint.

> How many lines of rust code are you using in production?
About 60k of our own, about 300k incl crates.

> Are you using nightly? or just stable version of rust?
We’re pinned to a particular nightly right now, as we rely on a fair amount of features that are still stabilizing. I imagine we’ll be on a stable by this summer.

> What drove the move to Rust precisely? Most of the players in the industry are somewhat reluctant about moving to Rust.
Well, we basically needed C++, but:
1) Dropbox doesn’t have a strong C++ ecosystem on the backend, and it takes a lot of work to build one.
2) Given (1), we had basically a blank slate, and our team is C++ and Haskell type folks, we decided to use Rust so we could get C++ with better type safety and fewer sharp corners.
So realistically, if we had been at a place with an awesome preexisting set of C++ libraries, practices, etc, we probably never would have used Rust.

Что наиболее показательно, так этот тот факт, что C++ перестал быть выбором по умолчанию для таких ситуаций, хотя еще несколько лет назад он был бы просто безальтернативным решением. С одной стороны немного грустно, мой основной рабочий инструмент продолжает уходить даже из тех областей, где он всегда был невероятно силен, с другой стороны, уход C++ из продуктовой разработки должен положительно сказаться на качестве и легкости поддержки сложных решений.

Релиз Rust 1.0. Возможности и сомнения

Разработчики Rust прошли долгий путь и 15 мая ожидается выпуск первой версии языка с вечеринкой по случаю новорожденного. Лично я долго ждал этого события, хотя и вызывает оно довольно смешанные чувства. Вроде что-то и родилось, но хочу ли я это что-то использовать и стоит ли оно того? Вот в чем вопрос. Дело в том, что в процессе развития Rust претерпел довольно сильные изменения и теперь это далеко не тот же язык, о котором я писал в 2013.
Continue reading

Приближение выхода Rust 1.0

Судя по записи в официальном блоге Rust, ожидать выхода Rust 1.0 осталось всего ничего:

We plan to ship the 1.0 beta around the end of the year.

Что на мой взгляд просто отличная новость! А дальше еще интереснее:

After 1.0 is released, future 1.x releases will be backwards compatible

Что недвусмысленно намекает на то, что разработчики языка Rust куда более вменяемые по сравнению с разработчиками D. Как следствие, возлагаемые на язык надежды в плане использования в промышленной среде становятся все более и более оправданными.

А вот заключительное утверждение:

In many ways, Rust 1.0 is not so much an endpoint as it is a starting point.

Напоминает мне шутку о том, что C++ разработчики скоро разделятся на две группы: те кто успел выучить язык до выхода C++17 и те кто уже не выучит %)

Rust 0.10

Сегодня вышел, на мой взгляд, интереснейший релиз Rust 0.10. Ломающих изменений в нем не очень много, зато был проведен просто грандиозный рефакторинг: две основные библиотеки libstd и libextra были разделены на полтора десятка библиотек “одной функции” в процессе чего libextra перестала существовать. Это хорошо в первую очередь тем, что появилась реальная возможность начать использовать Rust практически в пределах libstd (все же необходимо еще немного порезать libstd для отключения работы с задачами), а не только самого компилятора, на низком уровне, к примеру в драйверах. Сейчас как раз занимаюсь подобным экспериментом, выглядит ну очень заманчиво!

Ну, все изучать Rust!

Kaleidoscope на Rust

Существует довольно известное руководство, посвященное процессу разработки нового языка программирования с использованием LLVM в качестве back-end под названием Kaleidoscope. Руководство очень грамотно доносит то, с какими трудностями можно столкнуться в процессе разработки нового языка и как с ними справится. Для любого разработчика, у которого внезапно мелькнула в голове шальная мысль “хочу свой язык” к прочтению обязательно.
Так вот, на прошлой неделе в сообществе Rust промелькнула интересная ссылка на тот же Kaleidoscope, но написанный не на C++, а на Rust.

Callbacks, closures и модель памяти Rust

Реализуемая Rust модель памяти оставляет свой отпечаток на всем, включая такие вещи как замыкания и функции обратного вызова. Привычные по другим языкам концепции в случае с Rust начинают вести себя иначе и далеко не с первого взгляда очевидно почему такое происходит.

В Rust имеются два вида замыканий: стековые и уникальные и указатели на функции. В некоторых случаях замыкания взаимозаменяемы и совместимы с указатели на функции, в некоторых нет. Поведение данных замыканий идентично поведению стековых данных и данных адресуемых посредствам уникальных указателей. Так как весь этот набор выглядит довольно обширным, то мне кажется что лучше всего разбираться на примерах. Continue reading

Разрушающее сопоставление с образцом

В Rust используется разрушающее сопоставление с образцом, что в купе с моделью памяти Rust, иногда, дает очень занятные эффекты. Для примера возьмем структуру MyStruct и создадим две переменные типа Option в которых будет лежать наша структура, в одном случае 1 в виде стекового объекта, а во втором в виде уникального указателя 2.

struct MyStruct {
    val: int
}
let stack_data = Some(MyStruct{val:42});   // (1)
let own_data = Some(~MyStruct{val:42});    // (2)

Continue reading