Swift vs Rust - обзор Swift с Rust точки зрения

Перевод | Автор оригинала: Rhymu

Я впервые изучил Swift в 2017 году, что для меня было много лет назад. Тогда это была версия 3.1. Я гораздо лучше знаком с Rust, изучаю и использую его уже около 8 месяцев. Поэтому, когда я вернулся, чтобы снова проверить Swift на этой неделе, я был действительно удивлен тремя вещами:

Я подумал, что было бы забавно сесть и делать заметки, читая официальную книгу Swift Book, чтобы «освежить» язык. Будучи очень увлеченным Rust, я подумал, что было бы интересно включить в свои заметки любые сравнения, которые я мог бы провести между Swift и Rust. Это заняло несколько дней, и к концу у меня было записано довольно много заметок. Поэтому я решил немного их очистить и выложить здесь!

Ниже приводится краткое сравнение Swift (версия 5.3) и Rust (версия 2018). Он следует за главами официальной книги Swift. Swift и Rust очень похожи, поэтому в целом, если здесь не упоминается конкретная точка сравнения, возможно, два языка совпадают в этом отношении или «достаточно близки», что, по моему мнению, это не имеет значения. . Например, значения true и false одинаковы для Swift и Rust, а синтаксис для различных типов строковых литералов в основном одинаков, поэтому я не стал их записывать. С другой стороны, то, что здесь не указано, могло быть тем, о чем я не думал или не знал в то время, и в этом случае, пожалуйста, дайте мне знать в комментариях. Заранее спасибо!

Если какая-либо информация ниже не полностью проверена или протестирована, вопросительный знак? будет использоваться пунктуация. Это открытое приглашение для всех, кто может проверить информацию (правильную или неправильную) и тем или иным образом предоставить доказательства.

Во-первых, я нашел несколько интересных тем общего характера, которые не были охвачены непосредственно Swift Book. Я сгруппировал их вместе, чтобы перечислить здесь:

TL;DR - Общие различия

Я написал здесь довольно много заметок, возможно, слишком много для некоторых людей. Итак, вот краткое изложение того, «что важно» при сравнении Swift и Rust:

Раздел мнений

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

Теперь, разобравшись со всем этим, давайте сосредоточимся на сравнениях языков, проведенных в соответствии с Книгой Swift и сравнением с Rust. Во многих местах я буду сокращать, показывая Swift и Rust бок о бок со знаком «длинное тире» (-) между ними. Извините, если форматирование не слишком велико; меня устраивает. ¯ \ _ (ツ) _ / ¯

Основы

Основные типы

Опции

Константы и переменные

Комментарии

Точка с запятой

Приведение типов

Псевдонимы типов

Кортежи

быстрый let status = (код состояния: 200, описание: «ОК») print ("Статус: \ (status.statuscode) (\ (status.description))") ''

Обработка ошибок

Здесь мы видим первое принципиальное отличие Swift от Rust. В Rust ошибки обрабатываются путем представления их как значений варианта типа Result::Error и возврата их вверх по цепочке вызовов через значения Result. Другими словами, успешные возвращаемые значения и возвращаемые значения ошибок по существу следуют одному и тому же пути выхода из вызовов функций. В Swift ошибки представлены значениями, соответствующими протоколу ошибок (трэйту), которые «выбрасываются» в одной точке и «улавливаются» (или нет) где-то в цепочке вызовов. Они идут принципиально отдельным путем от успешных возвращаемых значений.

Функция, которая может вызывать ошибку, должна содержать ключевое слово throws в своем объявлении (перед телом):

func can_throw() throws { vs func does_not_throw() {

При вызове такой функции вы должны префикс вызова с ключевым словом try:

try can_throws()

Чтобы автоматически поймать ошибку на сайте вызова и преобразовать ее в необязательную, используйте try? (эквивалент Result::ok() для возвращаемого значения функции в Rust):

let x: Int? = try? something() — let x: Option<isize> = something().ok()

Чтобы автоматически поймать ошибку на сайте вызова и завершить работу с ошибкой времени выполнения, используйте команду try! (эквивалент Result::unwrap() для возвращаемого значения функции в Rust):

let x: Int = try! something() — let x: isize = something().unwrap()

Вывести ошибки с помощью оператора throw:

throw some_error

Разве не застали на месте звонка с попыткой? или попробуйте !, ошибки «раскручивают стек» или приводят к возврату выполнения кода из функций до тех пор, пока явно не «перехватят». Вы обнаруживаете ошибки за пределами места вызова с помощью оператора «do-catch»:

do {
    try something()
} catch some_error {
    handle_specified_error(some_error)
} catch {
    handle_unspecified_error(error)
}

Обратите внимание, что специальная ошибка имени доступна в неопределенном блоке обработки ошибок.

Утверждения и предварительные условия

Основные операторы

Строки и символы

Типы коллекций

В Swift есть три основных типа коллекций:

Единственный упорядоченный тип коллекции - это массив.

Использование массивов

Использование наборов

Использование словарей

Поток управления

Функции

func average(_ numbers: Double...) -> Double {
    /* body omitted for brevity */
}
average(1, 2, 3)
average(1.2, 3.5)

Замыкания

Перечисления

Структуры и классы

Характеристики

Методы

Индексы

Наследование

Поскольку наследование полностью чуждо для Rust, в этом разделе будет сравниваться наследование в Swift с наследованием в C++.

Инициализация

Деинициализация

Необязательная цепочка

Необязательная цепочка в Swift относится к использованию оператора вопросительного знака (?) В выражении, которое вызывает свойство или метод необязательного значения. Это аналог Option::map или Option::and_then в Rust.

Например, в Swift:

let result: SomeType? = value.property?.change()

Это аналогично следующему в Rust:

let result: Option<SomeType> = value.property().map(Property::change)

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

Необязательная цепочка, которая заканчивается присваиванием, приводит к тому, что это присваивание происходит только в том случае, если все левое выражение не равно нулю.

Swift автоматически "сглаживает" оцененные значения в необязательной цепочке, тогда как в Rust разработчик должен выбрать либо Option::map, либо Option::and_then в зависимости от того, возвращается ли необязательное значение или необязательное значение в любой заданной точке для избегать вариантов "вложенности". Другими словами, в Swift:

Обработка ошибок

Обработка ошибок в основном уже рассматривалась в Основах. По сути, Swift использует подход «исключение / перехват» для обработки ошибок, тогда как Rust вместо этого использует подход «возврат успеха / неудачи».

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

Приведение типов

При работе с классами в системе типов Swift необходимы два дополнительных оператора из-за характера наследования классов:

Вложенные типы

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

Расширения

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

Протоколы

Протоколы в Swift аналогичны «трэйтам» в Rust.

Дженерики

Непрозрачные типы

Ключевое слово some в Swift используется аналогично ключевому слову impl, особенно в позиции возвращаемого значения, для обозначения «некоторого типа, который реализует протокол (трэйт)», где компилятор может определить фактический конкретный тип:

  • -> some Shape — -> impl Shape

Автоматический подсчет ссылок

  • Слабые ссылки: weak var tenant: Person? - пусть мут-арендатор: Слабый <Личность>
  • «Необязательная ссылка» имеет ключевое слово unowned перед объявлением необязательного значения. Такая ссылка не участвует в автоматическом подсчете ссылок, но, в отличие от слабого указателя, она не является «необязательной» (не может быть нулевой). «Висячая» ссылка, не имеющая владельца, при обращении к ней вызывает ошибку времени выполнения. В Rust нет четкого аналога этому; * const T и * mut T близки к реализации этой концепции, но не совпадают точно?
  • «Необязательная ссылка без владельца» - это то же самое, что и «Необязательная ссылка», за исключением того, что значение является необязательным типом. Это делает его аналогом * const T или * mut T в Rust?

Безопасность памяти

По сути, Swift выполняет ограниченную форму «проверки заимствования», обнаруживая «конфликты памяти», когда есть некоторое перекрытие (множественное заимствование) при обращении к значениям. В книге приведены следующие примеры:

  • Параметры ввода-вывода: передача значения параметра ввода-вывода функции, которая имеет доступ к тому же значению.
  • «Self»: передача значения параметра inout методу с таким же значением.
  • Передача двух частей одного и того же кортежа в качестве параметров inout в функцию

Контроль доступа

  • «Модуль» в Swift - это «крэйт» в Rust.
  • Уровни доступа относятся к модулю и исходному файлу.
  • Swift имеет более тонкий контроль доступа, чем Rust:
    • open - как общедоступный, но позволяет создавать подклассы в других модулях.
    • public - по сути паб в Русте
    • internal - по сути паб (крэйт) на Rust
    • fileprivate - аналог pub (self) в Rust, если находится в модуле верхнего уровня для исходного файла.
    • private - аналог приватного в C++; не применимо к Rust.

Расширенные операторы

  • &+ — {integer}::overflowing_add
  • &- — {integer}::overflowing_sub
  • &* — {integer}::overflowing_mul
  • static func + — std::ops::Add
  • static prefix func - — std::ops::Neg
  • static postfix func ! для постфиксных операторов (нет эквивалента в Rust?).
  • В Swift можно определять собственные операторы, например: static prefix func +++.
    • Для настраиваемых инфиксных операторов вы можете определить, к какой группе приоритета операторов они должны принадлежать следующим образом: infix operator + -: AdditionPreference.

Заключительные мысли

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