Как стать автором
Обновить

Rust и Swift (третья, четвёртая, пятая и шестая части)

Время на прочтение 9 мин
Количество просмотров 8.5K
Автор оригинала: Chris Krycho

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


Операторы, их перегрузка и мысли о краткости


Я только что добрался до операторов в Swift. Первый вопрос: операторы — это специальный синтаксис или просто сахар для протоколов (protocol)? Каждый современный язык, который я использую или с которым когда-либо игрался (Python, Ruby, Io, Elixir и Rust — несколько примеров из большого разнообразия эпох и стилей), реализует их просто как сахар для других конструкций языка.

Поразбиравшись, я выяснил, что операторы — это функции (ну ладно), заданные глобальным пространством модулем Swift. Я говорю «ну ладно» вместо «хорошо», поскольку объяснение звучит так: это единственный способ заставить операторы работать как бинарные операторы между существующими типами. Оно упускает тот факт, что в таком случае причина кроется в строении языка. Кажется, сюда отлично вписались бы протоколы, но, возможно, они, в отличие от трейтов (trait) Rust, не способны справиться с проблемой в полной мере? Это открытый вопрос, и я понятия не имею, как на него ответить.

Интересно, что Rust располагает немного меньшим количеством операторов, чем Swift, даже если не считать упомянутых в моем предыдущем посте. В Rust, как и в Python, полностью отсутствуют префиксные и постфиксные операторы, потому что те же результаты можно получить другими более простыми способами. В Swift эти операторы частично сохраняются, несомненно, потому, что большинство программистов, которые имели дело с (Objective) C хорошо знакомы с ними и их идиомами.
Примечание переводчика: в Swift 2.2 убрали операторы инкремента/декремента, которых изначально не было в Rust. С другой стороны, в Swift разрешается создание собственных операторов.

Также я узнал кое-что новое об операторах в Rust: булевы операторы || и && отличаются от битовых | и & операторов не только тем, что первая группа реализует вычисления по короткой схеме. Конечно, с помощью второй группы можно выполнять битовые операции, однако в справочнике подчёркивается разница в плане схемы вычисления. Это вполне оправданно, но раньше я никогда об этом не задумывался.
Примечание переводчика: честно говоря, так и не понял, что хотел автор этим сказать.

В Rust отсутствует тернарный оператор, что связано с тем, как язык работает с выражениями (expressions) и инструкциями (statements). В Swift он есть. Интересная мысль о разнице в языковом дизайне: Rust избавился от этого оператора, поскольку блоки оператора if являются выражениями и, таким образом, он избыточен, а создатели языка стремились убрать ненужные возможности. (Обсуждение об отказе от тернарного оператора и интересное замечание о JavaScript от Брендана Айка читайте здесь). Оговорюсь, что это не критика в сторону Swift, a просто замечание, хотя мне и вправду больше нравится использующий выражения подход Rust.

С другой стороны, мне очень не нравится оператор ?? (nil coalescing operator). Он выглядит как сокращение ради сокращения, частично обусловленное стремлением Swift к краткости опциональных типов. Иногда краткость ведет к потере ясности. Избыточные сокращения усложняют язык и вынуждают замедляться при чтении каждой строки.

Булевы операторы в сравниваемых языках ничем не отличаются и не удивляют.

Интересно, сколько раз слово «краткий» или его синоним употребляется в книге о Swift? Я все отчетливее понимаю, что краткость — это одна из основных целей этого языка. Возможно, мне просто так кажется, но это всё же немного странно. Краткость — это хорошо, но читаемость — намного лучше.


Компромиссы в строении языка на примере работы со строками


И Swift, и Rust решают проблему безопасности и управления памятью, хотя подходят они к ней по-разному: Swift через автоматический подсчёт ссылок, а Rust через концепцию владения. Для повседневной работы подход Swift кажется мне более выигрышным по той же причине, что и Python или Ruby: приятно, когда всё делают за тебя. Rust даёт много возможностей, но вместе с тем заставляет постоянно задумываться о владении.

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

Я обратил на это внимание отчасти потому, что в Swift работать со строками (или другими типами, передающимися по значению) намного проще, чем в Rust. Результаты практически не отличаются, но поскольку в Swift все строки передаются по значению и никогда по ссылке, вам просто не придется задумываться о том что они будут модифицированы.

Конечно, для этой цели в Rust существует трейт Copy — я имею в виду, что Swift немного «эргономичнее».

Интерполяция строк в Swift очень удобна. Это единственное, чего мне не хватает в Rust. Его выполненный в стиле Python макрос для форматирования строк хорош, но интерполяция значений (strings with \(variables) или даже embedded expressions like \(2 + 4)) — это просто замечательно.

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

При этом, поскольку строки Swift обрабатывают все подобные граничные случаи для Юникодa, теряется несколько стандартных паттернов обращения к строкам, что значительно затрудняет (или делает невозможным?) понимание внутреннего строения строки. В зависимости от обстоятельств, это может быть как преимуществом, так и недостатком. Как я и говорил: компромиссы везде.

На самом деле, читая дальше, я понял, что Swift обращается со Юникод-строками довольно изящно и обеспечивает понимание процесса, используя отдельные методы для разных реализаций. В частности, я ценю, что вы можете как работать с типом String, так и получать прямой доступ к "code points" — причём не к какому-то одному, а к любому из UTF8, UTF16 или UTF32. Доверьтесь опыту Apple: к тексту нужно относиться очень внимательно.

Строки в Rust неплохи, но менее замысловаты (предположительно, это сделано ради упрощения представления их в памяти). В этом языке String или str всегда состоят из скалярных значений юникодa (UTF32), закодированных в виде последовательности UTF8 байт. В нем, в отличие от Swift, отсутствуют удобные методы получения других реализаций. Вместе с тем, я полагаю, что в повседневном программировании это проявится редко, а может быть, вообще никогда. Важно то, что оба языка хранят скалярныe значения.

Это первая часть, в которой я не чувствовал явного превосходства Rust над Swift. Некоторые из компромиссов в строении этих языков здесь проявляются более отчетливо, и я ценю, что «эргономика» Swift в их числе.


Преимущества (и сложности) параллельного изучения языков


Я изучаю Swift пару недель, а до этого в течение месяца погружался в глубины Rust. Такой подход — освоение двух языков практически одновременно — совершенно новый для меня, и на то имеются веские основания. Изучать язык программирования нелегко, и, чтобы осмыслить новые знания, необходимо много с ним работать.

Я делаю это из необходимости. Надеюсь, что смогу разработать приложение на очень функциональной и эффективной кросс-платформенной базе языка Rust, но планирую выпустить нативное приложение для OS X только тогда, когда все отшлифую. Мое желание сделать ядро приложения переносимым сразу же исключает использование Swift. Честно говоря, этому способствует и то, что это язык Apple: я с удовольствием использую инструменты Apple на их платформе, но не хочу постоянно зависеть от решений этой компании. К тому же опыт в Rust может пригодиться во многих других случаях.

Итак, мне нужно выучить оба языка.

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

Параллельное изучение двух языков помогло мне увидеть, на какие компромиссы идет каждый из них, заставило задуматься о том, чем обусловлены иx различия. В частности, я думаю, что теперь лучше понимаю, что происходит «за кулисами» этих языков и, знаю, чего от них ожидать. Это, в свою очередь, существенно повлияло на скорость изучения языка. Конечно, здесь также сыграло свою роль то, что я знаю несколько языков и последнее время активно расширял свой кругозор: читал о Haskell, функциональных паттернах в JavaScript и т.д.

Конечно, в обоих языках мне еще предстоит длинный путь. Читать по ночам и выходным и немного играться с каждым из них — это не то же самое, что вцепиться зубами в проект и искать болевые точки. Тем не менее, я действительно рад, что изучаю эти языки одновременно. Если вы готовы принять вызов, можете тоже попробовать. Вы удивитесь тому, как много сумеете выучить.


Типы коллекций и различие между синтаксисом и семантикой


Думаю, следующее предложение во многих отношениях характеризует языковой дизайн Swift:


Хотя две формы идентичны с функциональной точки зрения, более краткая форма является предпочтительной и употребляется в этом пособии при ссылке на тип массива данных. —The Swift Programming Language (Swift 2 Prerelease)

Документация о различных типах в модуле Rust std::collections интересна и полезна. Очень рекомендую.

Читая эту главу в руководстве по Swift, я заметил одну вещь: в Rust нет именованных параметров, а в Swift они есть. В обоих случаях это решение обоснованно, но мне кажется, что это одна из деталей, которых мне будет больше всего не хвастать в Rust. Python меня избаловал.
Примечание переводчика: имеется RFC по добавлению именованных параметров в Rust.

Тип Array в Swift аналогичен типу Vec в Rust (который обычно создается макросом vec!). Оба контейнера могут динамически изменять размер и хранят элементы в куче (heap), в то время как массивы в Rust имеют статический размер и создаются на стеке. Синтаксис для создания массивов в обоих языках весьма похож (хотя результат и различается):


let an_array: [Int] = [1, 2, 3] // Массив фиксированного размера
var an_array = [1, 2, 3] // Массив динамического размера

let an_array: [i32, 3] = [1, 2, 3]; // Массив 
let a_vector: Vec<i32> = vec![1, 2, 3]; // Вектор

Это можно записать короче, так как оба языка умеют выводить типы, так что вам редко придётся писать именно так. Более привычный вариант будет выглядеть следующим образом:


let an_array = [1, 2, 3]
var an_array = [1, 2, 3]

let an_array = [1, 2, 3];
let a_vector = vec![1, 2, 3];

Rust также добавляет концепцию "срезов" (slices), которые предоставляют доступ к части массива и представляют собой указатель на массив и размер (количество элементов).

Операции с массивами в Swift вполне логичны и на удивление наглядны. Они в хорошем смысле напоминают мне операции со списками в Python.

У контейнера Vec в Rust богатое API, и это неплохо. Меня немного удивило отсутствие метода для обхода элементов, но затем я обнаружил, что он присутствует у структуры IntoIter в том же модуле, для которой реализован трейт Iterator. Как результат, соответствующий метод возвращает экземпляр структуры Enumerate. (Подозреваю, что под капотом массивы в Swift просто реализуют протокол Iterable, что в каком-то роде похоже на подход Rust.)

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

Заметьте, что я говорил о семантике, а не синтаксисе. Swift и Rust — отличный пример того, как очень похожий синтаксис может скрывать различия в семантике. Ещё один пример: сравните синтаксис и семантику JavaScript и Java — на первый взгляд, они похожи синтаксически, но тем не менее между их семантикой пролегает гигантская пропасть.

Как Set в Swift, так и его грубый аналог HashSet в Rust имеют в распоряжении contains метод, который очень похож на ключевое слово in в Python. Нет ничего удивительного в том, что оба типа реализуют немало одинаковых методов. Наверное, этого следовало ожидать, учитывая тот факт, что множества представляют собой стандартное математическое понятие.

По причине более строгой типизации и Rust, и Swift требуют указания типов, используемых в ассоциативных массивах (HashMap в Rust и Dictionary в Swift), хотя, конечно, оба языка могут выводить типы в определённых случаях. Вы не можете смешивать использование разных типов ключей, как это разрешается в Python, но на практике это не должно вам мешать по двум причинам:


  1. Как правило, не рекомендуется использовать ключи разных типов. Как по мне, это часто указывает на то, что вам стоит тщательнее подумать об используемых типах и структурах данных.
  2. Мне интересно, можно ли в редких случаях, когда это уместно, использовать дженерик-тип в Rust или Swift. Я планирую прояснить этот вопрос попозже!

Было бы здорово, если бы Swift использовал Python-подобный синтаксис ({'ключ': 'значение'}) для инициализации ассоциативных массивов. Тем не менее, я понимаю, почему это невозможно: фигурные скобки уже заняты под блоки, а в Python такой проблемы нет, так как он использует отступы. Но это действительно удобно.

Я понимаю, почему дизайнеры Swift использовали для инициализации последовательностей скобки ([...]): это значительно упрощает парсинг. В результате, с первого взгляда тяжело понять, с чем вы имеете дело. Это может быть массив, множество или ассоциативный массив.

Это подчёркивает недооценённый аспект дизайна языков программирования — читаемость. Как бы нам, программистам, не нравилось писать код, в реальности мы тратим много, вероятно, большую часть, времени на его чтение. Таким образом, хотя и нужно уделять внимание удобству написания, стоит так же задуматься о лёгкости чтения кода. Синтаксис и соглашения, принятые в языке, составляют немалую часть этого.

Тип Dictionary в Swift очень похож на свой аналог в Python, вплоть до совпадения имён нескольких методов. Это справедливо и для HashMap в Rust. Это вовсе не плохо.


Послесловие от переводчика


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

Теги:
Хабы:
+14
Комментарии 10
Комментарии Комментарии 10

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн