Проглотил давеча за пару-тройку дней небольшую бесплатную книженцию "Why Rust" от O'Reilly. Книжица толковая. Собрает воедино все основные плюшки Rust-а в небольшом объеме текста. Что для меня очень большой плюс, т.к. эпизодические подходы к разным частям официальной документации позволяли разобраться в том или ином отдельном аспекте, но не создавали целостной картинки того, чем же должен брать Rust. Посему, если кто-то хочет составить себе впечатление об этом языке, но не хочет штудировать официальный "The Book", то имеет смысл взять и одолеть "Why Rust".
После прочтения у меня сформировалось несколько пунктов, по которым можно что-то сказать.
Блин, синтаксис реально имеет значение. Первое, что отталкивает от желания связываться с Rust-ом (и отталкивает весьма сильно) -- это непонятное для меня желание экономить на всем. fn вместо func или function, impl вместо implementation, mut вместо mutable, u8 вместо uint8 и т.д.
Казалось бы, ну синтаксис и синтаксис, можно привыкнуть к большой вариативности синтаксисов, почему же Rust-овский синтаксис так раздражает?
Думаю, что это из-за высокой плотности содержимого в Rust-овском коде. Т.е. когда ты видишь что-то вроде:
fn firsts<'a, 'b>(x: &'a Vec<i32>, y: &'b Vec<i32>) -> (&'a i32, &'b i32)
то взгляд должен цепляться буквально за каждый символ, ибо здесь любая закорючка имеет значение. Думаю, что если бы использовался какой-то такой синтаксис, то ситуация была бы получше:
func firsts<borrow A, borrow B>(x: borrow<A> Vector<int32>, y: borrow<B> Vector<int32>) -> tuple<borrow<A> int32, borrow<B> int32>
Upd. Или вот так:
func firsts(x Vector<int32> in A, y Vector<int32> in B) -> tuple<int32 in A, int32 in B>
То код читался бы быстрее, особенно при наличии подсвеченного синтаксиса: распознавание отдельных частей выражения было бы проще.
Да и вообще, если язык задумывался как безопасная альтернатива C/C++, то язык следовало бы делать максимально похожим по синтаксису на C и C++, тем более, что шаблоны в С++ и так позволяют записывать довольно сложные вещи. Так что можно было бы придумать нечто вроде:
template<borrow A, borrow B> tuple<borrow<A, int32>, borrow<B, int32>> firsts(borrow<A, vector<int32>> x, borrow<B, vector<int32>> y)
Имхо, мимикрия возможностей Rust-а под синтаксис C++ позволила бы не только упростить вход в язык для C++ников. Но и позволила бы с намного меньшими затратами переделывать уже существующий код под Rust. Если у меня 10MLOC кода на C++, то, понятное дело, на Rust я это все конвертировать не буду. Но, если у меня всего 25KLOC, то вместо того, чтобы переписывать все на Rust заново, я бы мог просто тщательно пройтись напильником по уже имеющемуся у меня коду и с минимальными услилями превратил бы C++ный код в Rust-овый код. Кстати говоря, когда-то на небольших программках я так поступал с переделкой C++ного кода в D-шный код.
У меня складывается впечатление, что Rust стоит относить к той же группе, что и языки вроде Eiffel и Ada. Т.е. на них нельзя "наговнякать что-то по-быстрому". Нужно сесть, подумать, основательно разобраться в том, что нужно сделать, как это сделать, почему именно так. Потом еще побороться немного с компилятором, чтобы договориться о том, где ты ему веришь, а где он тебе должен верить. В итоге получится работающий код, в стабильность и надежность которого ты более-менее веришь (хотя так это или нет -- это отдельный вопрос, т.к. отсутствие утечек памяти и повисших указателей вовсе не гарантирует корректную работу приложения). Но все это будет не быстро, а по-началу и не легко.
Так что да, если хочешь, чтобы все было "глобально и надежно" и у тебя есть возможность умножить свои сроки и бюджеты на 1.5, а в замен получить увеличение коэффициента спокойного сна на 15-20%, то Rust может быть хорошим выбором.
Но вот возможности что-то накалякать прям на коленках, проверить, убедиться в жизнеспособности, а потом уже раскручивать до чего-то более серьезного, Rust не предоставит. В отличии от C++, который был предназначен для того, чтобы дать программисту возможность делать все, что захочется, Rust создан для того, чтобы бить программиста по рукам в попытках выйти за рамки.
Есть сильное ощущение, что Rust сейчас пользуется тем, что это новый язык, без груза совместимости с чем-то еще (как у C++ с C) и без необходимости поддерживать туеву хучу легаси кода. Поэтому в Rust-е все можно сразу сделать хорошо и "правильно". Отсюда и появляются нештяки, вроде встроенных в язык АлгТД, модулей и системы Cargo, единственного компилятора для всех платформ и пр.
Пытаясь сопоставить C++ и Rust понимаешь, что не смотря на то, что в C++ можно обеспечить довольно таки высокий уровень безопасности кода (смотрим хелперы, вроде not_null и иже с ними), все-таки без серьезной модификации языка в C++ не получится завести те же самые АлгТД, а так же избавиться от таких вещей, как возможность вернуть ссылку на временный объект, который будет разрушен после возврата. Здесь Rust находится в заведомо лучшей позиции, чем C++.
При этом, однако, уже сейчас в Rust-е даже при поверхностном знакомстве обнаруживаются несколько странные вещи. Например, по умолчанию конструкция:
let a = ...; let b = a;
приводит к тому, что содержимое a переносится в b. Но не всегда. Если a принадлежит примитивному типу или типу, реализующему трейт Copy, то произойдет копирование. Т.е. в каких-то случаях после b=a значением a пользоваться нельзя и компилятор будет бить за это по рукам. А в каких-то случая -- можно. Имхо, такая неоднозначность не есть хорошо, в частности для обобщенного программирования.
Отдельным вопросом, о который уже сломано множество копий, является подход к обработке ошибок в Rust-а. Типа, везде должен возвращаться Result. Но и паники есть. Поэтому тем, кто озабочен написанием повторно-используемых библиотек, которые будут использоваться хрен знает в каких обстоятельствах, обеспечению exception safety (или правильно называть panic safety?) в Rust-е придется уделять столько же внимания, как и в C++. Но при этом еще и нужно будет долбаться с протягиванием кодов ошибок. Т.е. придется еще и писать код в духе "без try! ни строчки", как, например, это показывается в стандартной документации Rust-а:
let f = try!(File::create("foo.txt")); let metadata = try!(f.metadata()); let permissions = metadata.permissions(); println!("permissions: {}", permissions.mode());
Правда, в намедни вышедшем Rust 1.13 уже можно заменить try! вопросиками:
let f = File::create("foo.txt")?; let metadata = f.metadata()?; let permissions = metadata.permissions(); println!("permissions: {}", permissions.mode());
Но тут мы опять приходим к тому, что в Rust-е имеет значение каждая закорючка в коде.
Опять же, возьмем обобщенное программирование, в котором в Rust-е можно назначать аргументам шаблонов требования -- какие трейты должен поддерживать шаблон. Это сильно напоминает уже готовые concept-ы, которые в C++ все никак не могут завезти. Отсутствие ограничений на параметры шаблонов в C++ приводят к более сложной диагностики ошибок. Но, при этом, в C++ действительно возможен duck typing в compile-time: ну буквально, если шаблон ожидает "утку" в качестве параметра, то ему можно подсунуть все, что угодно, если это хоть сколько-нибудь похоже на "утку". И в этом огромная сила C++ных шаблонов и причина успеха шаблонов в C++. А вот в Rust-е, если шаблон ждет "утку", то и подсовывать ему нужно будет именно, что "утку". Может для тех, кто переходит на Rust с Java или C# это кажется нормальным, а вот мне это кажется очередным ограничением. Очередной попыткой ударить разработчика по рукам (см. пункт №2 выше).
В общем, с одной стороны у Rust-а в распоряжении чистый лист, на котором можно изобразить все, что угодно и так, как захочется. С другой стороны, что-то странное людям хочется. И, есть подозрение, что это только цветочки, ягодки еще впереди.
В самом начале книги "Why Rust" автор сделал заход, который мне показался жульническим и из-за которого я ко всему остальному материалу относился с изрядной долей скепсиса. Речь о наезде на понятие UB (undefined behavior) в языках C и C++. Мол, если вы в C/C++ обратитесь за пределы массива, то возникнет UB. А вот в Rust-е, мол, никакого UB -- вы получите вполне себе панику в run-time.
Как по мне, так странный заход. Из категории, "мой дедушка боролся за то, чтобы не было бедных, а не за то, чтобы не было богатых". Типа того, что у программиста руки все-равно кривые, поэтому мы не будем заморачиваться тем, как вообще сделать невозможными выходы за пределы буфера. Мы просто сделаем контроль в run-time. А за счет того, что у нас есть итераторы, а так же компилятор умный, мы большинство таких проверок выбросим и вы даже никаких дополнительных накладных расходов не получите.
Есть у меня подозрение, что подход разработчиков Rust-а можно выразить так: "Мы подумали о том, что вам может пригодится, а что не может, и дали вам то, чем вам стоит пользоваться. Этого вам хватит на все времена." Только вот я не уверен, что они [разработчики Rust] правы в своих убеждениях. Это касается разных вещей в языке. Начиная от необходимости ухода в unsafe для написания банального двусвязного списка, и заканчивая подходом к обеспечению thread safety. Так, есть у меня подозрения, что некоторые C++ные навороты, к которым приходится прибегать при работе с реальными проектами, в принципе возможны в Rust. Посему, если в реальной жизни придется столкнуться с чем-то нетривиальным (вроде реализации своего many-readers/single-writer spin-lock-а), то скорее всего с Rust-ом придется бороться, т.е. его сильные стороны могут начать работать против разработчиков.
Пора подводить сухой остаток. При наличии свободного времени, было бы полезно попробовать переписать какой-нибудь из маленьких C++ных проектиков и посмотреть на практике как это -- программировать на Rust-е. Но времени нет, а C++ного кода, который востребован и нуждается в развитии -- больше чем нужно. Посему смысла сейчас бросаться в Rust для меня нет. Не хочу сказать, что не верю в перспективы Rust-а и что у него нет шансов. Скорее всего шансов как раз предостаточно. Слишком уж много C++хейтеров сейчас вынуждены писать на C++ и страдать. Понятно, что большинство из них не имеет никакого влияния на выбор языка реализации для проектов. Тем не менее, Rust может найти свое место под Солнцем (и, скорее всего, найдет). Только вот у меня нет пока желания с Rust-ом связываться. Если Rust достигнет успеха, можно будет на него переключиться. Но пока этого не произошло, дальнейшая работа с C++ выглядит перспективнее.
Комментариев нет:
Отправить комментарий