Свежий взгляд на Rust

в 9:11, , рубрики: api, armin ronacher, python, Rust, thoughts, Программирование

Я программирую на Rust уже достаточно давно, но на самом деле это не имеет большого значения. Rust настолько динамичен, что стоит отвлечься на пару месяцев, и придётся писать уже на фактически другом языке. Однако одна вещь остаётся неизменной: вектор развития. С каждым обновлением, каждой модификацией язык становится всё лучше и лучше.

Изменениям всё еще не видно конца, но даже сейчас язык уже кажется более стабильным, чем несколько месяцев назад, и начинают появляться некоторые устойчивые шаблоны проектирования API. Я подумал, что настало время исследовать всё это поглубже и решил переписать мою библиотеку для redis.

Где ниша Rust?

Больше всего я работаю с тремя языками программирования: Python, C и C++. С последним у меня очень противоречивые отношения, потому что я никогда не уверен какую часть языка я должен использовать. Си прямолинеен, потому что прост. С++ же заключает в себе кучу особенностей, среди которых ты выбираешь множество используемых тобой, и в тоже время практически гарантированно кто-нибудь другой выберет что-то другое. И хуже всего, когда начинаются холивары. Я одинаково не испытаю любви ни к STL, ни к boost, причём началось это еще до того, как я связался с разработкой игр. И каждый раз когда всплывает эта тема, находится как минимум один человек, который заявляет, что я неправ и не понимаю сути языка.

Rust для меня как раз вписывается в область использования Python, C и C++, но занимает эту нишу в разных категориях. Python я использую для написания как небольших утилит, так и масштабируемых серверных приложений. И он устраивает меня потому, что у него большая экосистема, и когда что-то ломается, это можно очень быстро отладить.

У Python, в отличие от других динамических языков, есть одна интересная особенность: он очень предсказуем. Может быть это из-за того, что я в меньшей степени завишу от сборщика мусора, чем в других языках: Python для меня равен CPython, a CPython подразумевает подсчет ссылок. Я тот человек, который разбивает циклы введением слабых ссылок, и который добавляет проверку на количество ссылок до и после запроса, чтобы убедится, что циклы не образовываются. Почему? Потому что мне нравится, что можно объяснить, как работает система. Я не настолько сумасшедший, чтобы отключать cycle collector, но я хочу, чтобы всё было предсказуемо.

Да, Python медленный, у него большие проблемы с параллельным кодом, интерпретатор слабоват и иногда кажется, что он должен работать по-другому, но на самом деле мне это не доставляет больших проблем. Я могу что-то запустить, уйти на месяц, вернуться – и оно всё еще будет работать.

Rust повёрнут на работе с памятью и данными, в чём очень похож на С и C++. Но в отличие от этих двух языков он куда больше похож на Python с точки зрения программирования API из-за вывода типов и отлично написанной стандартной библиотеки, которая удовлетворяет все нужды программиста.

Биться об стену

Забавно, что в начале программирование на Rust похоже на постоянное битьё об стену. Да, это не Python, и множество вещей, привычных для него, не работают в Rust. В то же время это не C++, так что проверка зависимостей станет вашим самым большим врагом. Вы будете часто задумываться: ведь это же должно работать, так какого чёрта эта тупая штуковина думает, что знает что-то лучше меня, и запрещает мне это делать?

Правда в том, что borrow checker не идеален. Он защищает от опасностей, но порой слишком многое запрещает. Исходя из своего опыта, я могу сказать, что он на самом деле неправ гораздо реже, чем вы предполагаете; просто нужно начать думать немного по другому. И самое главное, что он порой предотвращает самые опасные ошибки проектирования, которые потом сложнее всего исправлять. Python использует GIL для работы с потоками, и он там действительно необходим с момента появления самого языка. Интерпретатор написан таким образом, что сейчас уже практически невозможно ничего исправить.

Если постараться, то можно ошибиться с принятием решения при распараллеливании и в Rust, но для этого действительно придется постараться. Язык заставляет больше думать, и я считаю, что это хорошо. Я не хочу опускаться до проповедования тезиса «ООП – ошибка на миллиард долларов», но считаю, что код, который пишут люди, по большей части зависит от языка программирования. Мы пишем объектно, потому что это просто в C++, Java, Python. Ага, и птицы становятся объектами класса «Животные». Если же отобрать такой инструмент, то ты начинаешь мыслить немного по другому, и это хорошо. Мощности CPU уже растут не так быстро, и уже бессмысленно рассматривать только один объект в единицу времени. Приходится больше рассуждать о коллекциях и необходимых преобразованиях.

Rust вдохновляет

Мне программирование на Rust приносит радость. Да, я до сих пор не могу согласится со всем, что язык заставляет меня делать, но могу сказать, что уже давно не получал так много удовольствия от программирования. Язык даёт мне кучу новых идей для решения проблем, и я просто не могу уже дождаться стабильного релиза.

Rust вдохновляет по многим причинам. И главная из них – он практичен. У меня есть опыт работы с Haskell, я пробовал Erlang, и ни один из них не похож на практичный язык. Я знаю, что множество программистов обожают их, но эти языки явно не для меня.

Rust же одна из тех вещей, которую может попробовать каждый, и это будет весело. Во-первых (пока вы не наткнётесь на баг компилятора), он не будет крэшится. И будет выдавать милые сообщения при ошибках компиляции. И еще у него есть менеджер пакетов с отслеживанием зависимостей, так что можно начать использовать чужие библиотеки не боясь наткнуться на поганую или уже несуществующую экосистему. Работа с пакетами в Python достаточно сильно эволюционировала за последние несколько лет, но это всё еще одна из самых разочаровывающих его частей. Cargo, пакетному менеджеру Rust, всего полгода, но у него есть постоянный мейнтейнер, и его классно использовать.

Даже инсталлятор высшего качества. Он предоставляет компилятор, утилиту для документирования и пакетный менеджер. И большой вклад в удовольствие от программирования привносит как раз инструмент для работы с документацией, который рождает отлично выглядящую справку «из коробки». Хотя я и хочу, чтобы он был чуть более похож на Sphinx, чем на javadoc, это действительно хороший задаток на будущее.

Но самое интересное в Rust – это мелочи. Когда я только начинал играться с ним, я был поражен хорошей поддержкой FFI: кроме того, что можно просто вызывать что-то из сишных библиотек, компилятор сам их находит и линкует. И таких вещей в языке запрятано неописуемое множество. Есть макрос include_str!, который прочитает файл во время компиляции в строку в бинарнике, вау! И ведь можно даже затянуть переменные окружения в исполняемый файл, например.

Проектирование API

Самое занимательное сейчас в работе с Rust – это поиск пути правильного и красивого написания API. Rust как язык несомненно сложнее многих других именно с этой точки зрения: как программист вы будете разрываться между написанием прямолинейного (как в языках системного программирования) и предоставлением красивого высокоуровневого интерфейса (как в Python).

И я склонен как раз к написанию красивых API, потому что сам язык побуждает такой подход. С одной стороны язык очень выразительный, с другой он предоставляет неимоверное количество возможностей.

Одна из таких «плюшек» – это чертовски классная система типов. Язык статически типизированный, но механизм выведения типов позволяет писать действительно красивый код. Например, мой драйвер для Redis позволяет писать так:

extern create redis;

fn main() {
    let client = redis::Client::open("redis://127.0.0.1/").unwrap();
    let con = client.get_connection().unwrap();

    let (k1, k2) : (i32, i32) = redis::pipe()
        .cmd("SET").arg("key_1").arg(42i).ignore()
        .cmd("SET").arg("key_2").arg(43i).ignore()
        .cmd("GET").arg("key_1")
        .cmd("GET").arg("key_2").query(&con).unwrap();

    println!("result = {}", k1 + k2);
}

И для сравнения код на Python:

import redis

def main():
    client = redis.Redis('127.0.0.1', 6379)
    pipe = client.pipeline()
    rv = pipe 
        .set("key_1", 42) 
        .set("key_2", 43) 
        .get("key_1") 
        .get("key_2").execute()
    k1 = int(rv[2])
    k2 = int(rv[3])
    print 'result = {}'.format(k1 + k2)

if __name__ == '__main__':
    main()

Интересно, что хотя библиотека на Rust по размеру и «чистоте» походит на библиотеку на Python, она куда более низкоуровневая. Питоновская библиотека даёт каждому вызову отдельный метод, версия для Rust (потому что она молодая) – просто обёртка для low-level API, и запрос приходится создавать вручную как последовательность вызовов для каждого аргумента. Тем не менее конечный результат для пользователя выглядит так же хорошо. И всё же Rust требует немного больше обработчиков ошибок (хотя я избегал этого использованием unwrap, который заставляет приложение завершаться, но то же самое происходит и в версии для Python, где я так же пропускал проверку ошибок).

Большой плюс в пользу версии для Rust – типобезопасность. И это при том, что в общей сложности есть всего два места, где упоминаются типы, причем это те же самые места, где даже в Python использовалось приведение к целому.

Однако это не лучшее, что мы можем сделать с помощью Rust: у него есть расширения компилятора, которые открывают целое море возможностей. Например, есть библиотека, которая проверяет команды Postgres SQL на правильность, rust-postgres-macros:

test.rs:8:26: 8:63 error: Invalid syntax at position 10: syntax error at or near "FORM"
test.rs:8     let bad_query = sql!("SELECT * FORM users WEHRE name = $1");
                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error

И вот это действительно захватывающе.

P.S. Пообщаться на тему Rust API design можно в IRC-сети Mozilla на канале #rust-apidesign

Будущее

Настолько ли мощна концепция управления памятью в Rust, чтобы принять её как модель программирования? Я не уверен. Но я верю, что язык уже может стоять на ногах. И даже если народ решит, что borrow checker не нужен, мне кажется, это не помешает широкому распространению языка. Rust обещает быть отличным, и к тому же отлично работает без сборщика мусора.

Это исключительный проект с открытым исходным кодом. И ему нужно больше помощи. Поддержка Windows становится всё лучше и лучше, но тем не менее требует много работы.

Автор: komissarex

Источник

Поделиться

* - обязательные к заполнению поля