понедельник, 25 июня 2018 г.

[prog.thoughts] В догонку к посту про Rust 1.27

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


Момент первый, посвященный метапрограммированию в C++ и около того.

К сожалению, в теме C++ного метапрограммирования я не настоящий сварщик. Время от времени что-то из метапрограммирования доводится применять (например, наполнение класса методами в зависимости от типов параметров шаблона). Но настоящие навороты с Boost.MPL или Boost.Hana -- это для меня слишком сложно. А без острой на то необходимости я со сложными решениями предпочитаю не связываться. Ибо тут получается как с сильнодействующими лекарствами: начинаешь лечиться от одного, но побочными эффектами садишь себе что-нибудь другое.

ИМХО, главная проблема с метапрограммированием в C++ связана с тем, что:

  • метапрограммирование было открыто случайно, можно сказать, что это оказалось неожиданным побочным эффектом мощности C++ных шаблонов. Получилось, что метапрограммирование специально не проектировали. А затем, как мне кажется, специально не вкладывались в развитие соответствующих механизмов языка;
  • в C++ очень сильно ощущается подход, когда при наличии кривого способа достичь эффекта X альтернативные простые варианты достижения этого эффекта появляются либо далеко не сразу, либо вообще не появляются. Ну, например, есть ли в C++ штатный, простой, лаконичный и удобный способ проверить наличие у типа T метода M с заданной сигнатурой?

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

Могу ошибаться, но, как мне думается, разработчики D учли опыт C++ и там метапрограммирование имеет более человеческое лицо (за счет менее многословных шаблонов, наличия compile-time рефлексии и конструкций вроде static if и static foreach, а так же текстовых mixin-ов).

Так что я думаю, что хорошо, что в C++ есть возможности метапрограммирования. В каких-то случаях эти возможности могут сильно выручить. Плохо то, что за это приходится расплачиваться сложностью и многословностью. Но хорошо то, что это самое метапрограммирование нужно не часто и далеко не всем.

В общем, выбирай, но осторожно, но выбирай. А хотелось бы, чтобы выбирать можно было менее осторожно ;)

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

Скажу лишь, что всегда с настороженностью отношусь к языкам, у которых очень продвинутая макросистема. Т.к. здесь открывается ну слишком уж большой простор для велосипедостроения и прямой путь к тому, что в Интернетах называют Social problems of Lisp. И да, я не верю в разумность и самоограничения разработчиков. Если уж дают в руки большую пушку, то найдется достаточное количество дятлов (включая меня самого, поскольку мне так же законы не писаны), которые с большим удовольствиям будут палить из нее во все стороны. А когда придет отрезвление и время собирать камни, будет уже поздно :(


Второй момент, на котором хотелось бы остановиться -- это востребованность Rust-а в реальном мире. Ну или даже не востребованность, а отношение к Rust-у. Здесь, как мне кажется, есть любопытное явление.

С одной стороны, как только речь заходит о Rust-е, так сразу же появляется большое количество восторженных персонажей, наполненных восторгами из категории "ну вот сейчас заживем!". С другой стороны, с момента официального релиза Rust 1.0 прошло уже больше трех лет. И?

Рискую ошибаться, но спустя уже пару лет после официального релиза Go 1.0 стало понятно, что Go востребован и проникновение Go в мейнстрим -- это дело времени. (Напомню, официальный релиз Go 1.0 состоялся весной 2012-го, хотя широкой публике о Go стало известно года за три до того. Как, впрочем, и в случае Rust-а). Сейчас лично для меня Go вполне себе мейнстрим. Пока в нескольких узких нишах (но зато это "горячие" и востребованные сейчас ниши). Но, не удивлюсь, если Go окажется "подрывной инновацией" и со временем он подвинет другие языки в совсем других нишах (тем, кто хочет понять, что такое подрывные инновации и какое влияние они оказывают, рекомендую эту книгу).

Если же посмотреть на Rust, то где он спустя три года после официального релиза стабильной версии Rust-а?

Может это я не знаю куда смотреть и не вижу очевидных success stories для Rust-а, коих полно. Но пока виден только информационный шум. А вовсе не массовое появление реальных продуктов на Rust-е.

Т.е. как по мне, то в случае с Rust-ом происходит резкий когнитивный диссонанс между ожиданиями и реальностью. Возможно, активные Rust-оманы со мной не согласятся и смогут привести кучу внедрений Rust-а. Но вот какая штука: я же не следил за проникновением Go в массы. Но примеры его успешного использования сами меня находили. И за Rust-ом я пристально не слежу. Но чтобы найти примеры успешного внедрения Rust-а придется постараться.

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

Но время идет, а какого-то явного сигнала о том, что вот, пора переключаться на Rust, это уже вполне себе рынок... Вот такого сигнала я пока не обнаружил. Буду признателен, если кто-нибудь сможет указать на то, что я заблуждаюсь на этот счет.


Ну и еще в процессе размышлений о Rust-е и будущей судьбе C++ удалось лучше сформулировать вот какую мысль. На С++ (равно как и на Ada, и на Pascal/Modula-2 и других языках с ручным управлением ресурсами) вы концентрируетесь на решении своей задачи. Но при этом вы можете ошибиться при управлении этими самыми ресурсами. Такая вот дилемма: ты можешь решить задачу любым способом, но с высокой вероятностью и ценой ошибки.

Тогда как при программировании на Rust-е ты вынужден решать две задачи. Во-первых, свою прикладную задачу. Во-вторых, задачу по объяснению компилятору Rust-а своего решения. Что, как по мне, выходит дороже. Но зато вероятность и стоимость допущенной ошибки сильно снижается.

Проблема Rust-а в этом смысле в том, что подход "сейчас заплачу подороже, чтобы затем платить меньше" сильно противоречит человеческой природе. Мы ведь массово наблюдаем за тем, как распространен подход "фигак-фигак и в продакшен". И это не спроста. Здесь и сейчас для бизнеса важнее (в общем), чем долгосрочные перспективы. Ибо до этой долгосрочной перспективы можно и не дожить, если не закрыть свои насущные проблемы прямо здесь и сейчас.

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

2 комментария:

NN​ комментирует...

Я нашёл шедевр.
https://habr.com/ru/post/467207/#comment_20627875

extern crate libc;
extern {
fn c_func(x: *mut *mut libc::c_void);
}

fn main() {
let x = 0 as *mut u8;
c_func(&mut (x as *mut libc::c_void));
println!("new pointer is {}", x);
}


А она вне зависимости от действий внешней функции c_func выдаст… 0x0. Чтобы всё работало как надо, указатель нужно описать вот так:

(&mut x) as *mut _ as *mut *mut libc::c_void

:)

eao197 комментирует...

@@NN это просто шикарно!