Если Вы имеете в виду Optional<T> - это да, тип, но этот тип конструируется параметризацией Optional дженериком T (тайп параметром).
Сам по себе Optional, без подстановки туда чего-угодно, не может быть типом, соответственно, нельзя его использовать в сигнатурах типов не указав параметр.
Аналогично и с Array. Пока не заполнены все "дырки", неважно чем, конкретными типами или тайп параметрами - у нас нет типа, есть только конструктор типа.
но зачем делать так, чтобы Optional сам был вью и мог стать этим самым T2 - я не понимю.
К примеру, если брать широкими мазками, то если логикой какой-то generic third-party View не предусмотрено Optional поле, а вы хотите отобразить в ней ток 2 из 3 передаваемых снаружи вьюх, то третью вы можете передать как nil, и получить желаемое поведение, не переписывая компонент и не матеря других разрабов. E - extensibility.
То, что возможно подобная задумка (опять спекуляция ^_^), плохо легла конкретно на ваш кейс - вопрос отдельный. И вообще, так ли оно работает на самом деле в Swift?
Это нормально. Как я уже писал - боттом может быть чем угодно, и реализовывать что угодно, ведь его значение всё равно никогда нельзя создать. На то он и боттом, это соответствует его дизайну.
По факту, это должно автоматически работать, но видимо ещё не сделали:
А вот это уже не норм. Я как-то упустил из виду что там:
public typealias Body = Never
В таком виде оно действительно бесполезно. Я ожидал бы там увидеть что-то типа Wrapped.Body (не силён в синтаксисе Swift'а).
Почему влепили именно Never - сложно понять мотивацию. Уже ли не в void* полиморфизм они пытаются? >_<
Думаю этот вопрос лучше задать разрабам языка напрямую, на соответствующих офф. ресурсах.
У опционала два значения. Другое дело, что в этом случае одно из них невозможно создать и его никогда не будет в рантайме.
Optional - не тип, а конструктор типа, и у него значений быть не может. Optional<Int> - это уже тип, и у него 2 значения. Optional<Never> - это тоже тип, и у него всего одно значение.
Покажите, пожалуйста документацию этого факта, что все нил - это опционал невера.
Там действительно дело не в Neverах как я ожидал, просто семантика is довольно хитрая для Optionalов:
Nil Casting: if T and U are any two types, then Optional<T>.none is Optional<U> == true
Удачи в поисках "Motivation" раздела RFC для таких подводных камней =)
Это спекуляция.
Не надо ехидничать, пожалуйста
Цели издеваться над вами у меня нет. Я вообще мимокрокодил и Swift не знаю, но с Neverами бодался много в других языках. Уповая на то, что разработчики Swift'а более менее следуют "общим практикам", лишь подкидываю идеи чем бы этого могло быть. Конечно же всё это спекуляции. Я и не претендовал на истину.
В такие типы позволяется кастить что угодно, ибо они не населены (нельзя создать значение этого типа, соответственно, эта ветвь кода никогда не будет вызвана в рантайме).
Дальше, Optional<Never> - это тип имеет всего одно значение none (ах да, в Swift это то же что и nil).
Соответственно, через Never? суть выражается протокол (или неявный родительский тип), требующий быть nil.
nil, some(nil), и так далее, просто удовлетворяют этому протоколу.
Можно сказать что это хитрый и законный способ заабьюзить систему типов (автоприведение к боттому) для определения IsNil протокола. Это не особенность реализации компилятора, и рантайм тут совершенно ни при чём.
Принцип ведь простой же — инструмент/практику/парадигму имеет смысл применять, если она уменьшает стоимость поддержки кода и вероятность ошибок. 40 строк тривиального кода + смок тест в данной ситуации намного более понятны, проще и поддерживаемы, нежели ворох "паттернов" по SOLID с 100% покрытием через моки.
В больших же проектах — наоборот. По мере роста проекта появляется много дублирования кода, изначально тривиальные вещи становятся повторящимся бойлерплейтом и копипастой, что увеличивает и вероятность ошибок, и стоимость дальнейшей поддержки. И здесь SOLID вместе з другими хорошими практиками как раз и становятся теми инструментами, которые уменьшают вероятность ошибок и стоимость поддержки.
Инструмент, который хорошо работает в одной ситуации — не обязательно хорошо работает в другой. Да и смысл не в самом инструменте, а в том, чтобы работало хорошо.
Но не это ссуть — а проблема наблюдается таки системная — без ансейфа раста практически нет. Сравним с C# или Go?
Ну давайте сравним коробку-механику и коробку-автомат, нет? Странно слышать претензии к механике, что она не умеет автоматически переключать передачи, ещё и сцепление, такую гадость, выжимать нужно, чтобы переключиться.
Rust, в сравнении с Go и C#, не ограничивает в управлении и даёт возможность настолько ювелирно настроить процессы руками, насколько нужно. Потому "сцепление выжимать" будут неизбежно чаще. В самых низкоуровневых абстракциях, будь то std, FFI, либо аля-std фундаментальные крейты (tokio и т.п.) unsafe необходим, и это именно то, что в Go/C# в основном поставляется рантаймом и std. Но почему-то там мы считаем, что всего этого нет, а в Rust говорим, что "всё, капец, без ансейва нет ни одной программы, только перекладывание байтиков в массивы!!!!11". При этом почему-то игнорируется что с каждым новым слоем абстракции градус unsafe падает, и когда доходит до прикладного юзер-кода, юзающего все эти абстракции, unsafe применять смысла уже нету и туча высокоуровневых крейтов с #![forbid(unsafe)] (могу дать примеров). Они точно так же опираются на safe абстракции своих зависимостей, как и в C#/Go, просто что у последних основная масса unsafe абстрагирована уже в рантайме и std, в то время как у Rust это дело больше вынесено в зависимости.
С таким подходом можно сказать что без ансейва — вообще ничего нет. Потому что любой язык и его экосистема в своём фундаменте опирается на тщательно абстрагированные небезопасные операции.
И, к слову, за 2 года использования Rust я не словил ни одного сегфолта от низлежащих зависимостей. Но зато когда я работал с PHP, я их ловил достаточно часто, просто переходя на новую версию всеми используемого PHP-расширения. И как же так? Получается PHP — это не safe язык ведь, верно? Ведь в нём небезопасная операция просочилась в safe код, который гарантированно должен был быть safe.
Понимаете абсурдность претензии?
Ну вот, к примеру, либа miniz_oxide. Вполне жизнеспособная, быстрее оригинала на C. unsafe не содержит.
Её зависимость crc32fast содержит чуток unsafeов, но не потому что "язык нежизнеспособен", ибо без unsafe там всё так же будет работать хорошо, а всего лишь потому что хочется "чуточку сладких интринсинков":
Due to the use of SIMD intrinsics for the optimized implementations, this crate contains some amount of unsafe code.
Другая её зависимость libc — это FFI, который по определению unsafe (никакой язык не может гарантировать что будет происходить внутри кода другого языка). Опять же, причина не в ограниченой выразительности.
Всё. По сути, средствами выразительности только safe Rust реализована либа, ничуть не хуже эталонного оригинала на С.
Так что "потому иначе он совершенно нежизнеспособен" — это явно перегиб с Вашей стороны, из-за недостаточной подкованности в теме.
Safe Rust действительно в данный момент не даёт средств для выражения self-referential структур c помощью ссылок, потому такое в нём невозможно. Но когда задача требует действительно использования self-referential структур, то приходится расчехлять unsafe и использовать сырые указатели, что и позволяет делать вещи вида:
struct AsyncFuture {
x: [u8; 128],
read_into_buf_fut: ReadIntoBuf<'self>, // ссылается на поле `x`
}
А работая с подобной структурой, даже уже в safe Rust, очень легко получить UB, ведь при любом перемещении AsyncFuture (мы её только что создали, мы ею владеем, и мы решили её тут же переместить) мы получим dangling pointer в read_into_buf_fut, ибо само по себе перемещение не обновит указатель, указывающий на x, а borrow checker не отслеживает сырые указатели.
Соответственно, за'Pin'ив указатель read_into_buf_fut, мы получаем гарантию на уровне системы типов, что данные позади указателя не будут перемещены (либо что их перемещение не нарушит инвариантов типа, см. Unpin). И если у нас код где-то мувает запиненное значение, то он просто не скомпилируется.
Хорошо, что это просто коммент к "горячей" статье на Хабре
Было бы гораздо грустнее, если бы это был релиз, уехавший в прод. Клиенты "просто забыл" не сильно понимают.
To err is to be human, but compilers never forget.
Условия задачи не полны, либо она не разрешима в ряде случаев. Если бы Вы соответствующие инварианты попробовали описать формально в типах, то точно бы зацепились за этот момент.
Почти вся. Есть ещё системы идеального шифрования. Они не подходят ни под это допущение, ни под описанные выше.
Давайте представим, что мы обладаем неограниченным ресурсом для полного перебора. Какое кол-во вариантов нам не нужно было бы перебрать, мы делаем это максимум за 1 сек. Перед нами сыпется почти вся криптография, ибо подобрать секретный ключ — тривиальное дело. Вся, кроме, к примеру, "одноразового блокнота", который является примером идеального шифра, потому что в нём длинна ключа совпадает с длинной самого шифруемого сообщения. Таким образом перебрав абсолютно все варианты ключей, мы получим абсолютно все возможные осмысленные тексты заданной длинны. Вопрос: какой из них тот, который был зашифрован изначально? Увы, здесь ответить мы уже не можем никак.
Им и живы =)
Но там тоже все ещё много работы в плане макросов. Декларативные раскрываются хорошо и дают нормальный автокомплит, но вот редактирование синтаксиса внутри декларативного макроса — только руками. У процедурных все не очень в плане раскрытия, но вот их написание вполне ОК, так как это обычный код работающий с токенами и AST.
Нет, это не тип.
Если Вы имеете в виду
Optional<T>
- это да, тип, но этот тип конструируется параметризациейOptional
дженерикомT
(тайп параметром).Сам по себе
Optional
, без подстановки туда чего-угодно, не может быть типом, соответственно, нельзя его использовать в сигнатурах типов не указав параметр.Аналогично и с
Array
. Пока не заполнены все "дырки", неважно чем, конкретными типами или тайп параметрами - у нас нет типа, есть только конструктор типа.Вроде они и сами недовольны таким решением:
https://github.com/apple/swift-evolution/blob/master/proposals/0083-remove-bridging-from-dynamic-casts.md#removing-special-case-optional-and-container-handling-from-dynamic-casts
К примеру, если брать широкими мазками, то если логикой какой-то generic third-party
View
не предусмотреноOptional
поле, а вы хотите отобразить в ней ток 2 из 3 передаваемых снаружи вьюх, то третью вы можете передать какnil
, и получить желаемое поведение, не переписывая компонент и не матеря других разрабов. E - extensibility.То, что возможно подобная задумка (опять спекуляция ^_^), плохо легла конкретно на ваш кейс - вопрос отдельный. И вообще, так ли оно работает на самом деле в Swift?
Это нормально. Как я уже писал - боттом может быть чем угодно, и реализовывать что угодно, ведь его значение всё равно никогда нельзя создать. На то он и боттом, это соответствует его дизайну.
По факту, это должно автоматически работать, но видимо ещё не сделали:
https://github.com/apple/swift-evolution/blob/master/proposals/0102-noreturn-bottom-type.md#never-as-a-universal-bottom-subtype
А вот это уже не норм. Я как-то упустил из виду что там:
В таком виде оно действительно бесполезно. Я ожидал бы там увидеть что-то типа
Wrapped.Body
(не силён в синтаксисе Swift'а).Почему влепили именно
Never
- сложно понять мотивацию. Уже ли не вvoid*
полиморфизм они пытаются? >_<Думаю этот вопрос лучше задать разрабам языка напрямую, на соответствующих офф. ресурсах.
Optional
- не тип, а конструктор типа, и у него значений быть не может.Optional<Int>
- это уже тип, и у него 2 значения.Optional<Never>
- это тоже тип, и у него всего одно значение.Там действительно дело не в
Never
ах как я ожидал, просто семантикаis
довольно хитрая дляOptional
ов:https://github.com/apple/swift/blob/main/docs/DynamicCasting.md#optionals
Удачи в поисках "Motivation" раздела RFC для таких подводных камней =)
Цели издеваться над вами у меня нет. Я вообще мимокрокодил и Swift не знаю, но с
Never
ами бодался много в других языках. Уповая на то, что разработчики Swift'а более менее следуют "общим практикам", лишь подкидываю идеи чем бы этого могло быть. Конечно же всё это спекуляции. Я и не претендовал на истину.А в чём проблема с тем что
Optional
этоView
, если содержимое тожеView
? Это обычное прокидывание свойств вверх по враперам, и улучшает полиморфизм.Never
тип - это особый тип: https://en.wikipedia.org/wiki/Bottom_typeВ такие типы позволяется кастить что угодно, ибо они не населены (нельзя создать значение этого типа, соответственно, эта ветвь кода никогда не будет вызвана в рантайме).
Дальше,
Optional<Never>
- это тип имеет всего одно значениеnone
(ах да, в Swift это то же что иnil
).Соответственно, через
Never?
суть выражается протокол (или неявный родительский тип), требующий бытьnil
.nil
,some(nil)
, и так далее, просто удовлетворяют этому протоколу.Можно сказать что это хитрый и законный способ заабьюзить систему типов (автоприведение к боттому) для определения
IsNil
протокола. Это не особенность реализации компилятора, и рантайм тут совершенно ни при чём.Это какой-то карго-культ уже.
"Here's what happens — granpa knows every damn word, doesn't understand a single one!" ©
Принцип ведь простой же — инструмент/практику/парадигму имеет смысл применять, если она уменьшает стоимость поддержки кода и вероятность ошибок. 40 строк тривиального кода + смок тест в данной ситуации намного более понятны, проще и поддерживаемы, нежели ворох "паттернов" по SOLID с 100% покрытием через моки.
В больших же проектах — наоборот. По мере роста проекта появляется много дублирования кода, изначально тривиальные вещи становятся повторящимся бойлерплейтом и копипастой, что увеличивает и вероятность ошибок, и стоимость дальнейшей поддержки. И здесь SOLID вместе з другими хорошими практиками как раз и становятся теми инструментами, которые уменьшают вероятность ошибок и стоимость поддержки.
Инструмент, который хорошо работает в одной ситуации — не обязательно хорошо работает в другой. Да и смысл не в самом инструменте, а в том, чтобы работало хорошо.
Ну давайте сравним коробку-механику и коробку-автомат, нет? Странно слышать претензии к механике, что она не умеет автоматически переключать передачи, ещё и сцепление, такую гадость, выжимать нужно, чтобы переключиться.
Rust, в сравнении с Go и C#, не ограничивает в управлении и даёт возможность настолько ювелирно настроить процессы руками, насколько нужно. Потому "сцепление выжимать" будут неизбежно чаще. В самых низкоуровневых абстракциях, будь то std, FFI, либо аля-std фундаментальные крейты (
tokio
и т.п.)unsafe
необходим, и это именно то, что в Go/C# в основном поставляется рантаймом и std. Но почему-то там мы считаем, что всего этого нет, а в Rust говорим, что "всё, капец, без ансейва нет ни одной программы, только перекладывание байтиков в массивы!!!!11". При этом почему-то игнорируется что с каждым новым слоем абстракции градусunsafe
падает, и когда доходит до прикладного юзер-кода, юзающего все эти абстракции,unsafe
применять смысла уже нету и туча высокоуровневых крейтов с#![forbid(unsafe)]
(могу дать примеров). Они точно так же опираются на safe абстракции своих зависимостей, как и в C#/Go, просто что у последних основная масса unsafe абстрагирована уже в рантайме и std, в то время как у Rust это дело больше вынесено в зависимости.С таким подходом можно сказать что без ансейва — вообще ничего нет. Потому что любой язык и его экосистема в своём фундаменте опирается на тщательно абстрагированные небезопасные операции.
И, к слову, за 2 года использования Rust я не словил ни одного сегфолта от низлежащих зависимостей. Но зато когда я работал с PHP, я их ловил достаточно часто, просто переходя на новую версию всеми используемого PHP-расширения. И как же так? Получается PHP — это не safe язык ведь, верно? Ведь в нём небезопасная операция просочилась в safe код, который гарантированно должен был быть safe.
Понимаете абсурдность претензии?
Ну вот, к примеру, либа miniz_oxide. Вполне жизнеспособная, быстрее оригинала на C.
unsafe
не содержит.Её зависимость crc32fast содержит чуток
unsafe
ов, но не потому что "язык нежизнеспособен", ибо безunsafe
там всё так же будет работать хорошо, а всего лишь потому что хочется "чуточку сладких интринсинков":Другая её зависимость
libc
— это FFI, который по определениюunsafe
(никакой язык не может гарантировать что будет происходить внутри кода другого языка). Опять же, причина не в ограниченой выразительности.Всё. По сути, средствами выразительности только safe Rust реализована либа, ничуть не хуже эталонного оригинала на С.
Так что "потому иначе он совершенно нежизнеспособен" — это явно перегиб с Вашей стороны, из-за недостаточной подкованности в теме.
Safe Rust действительно в данный момент не даёт средств для выражения self-referential структур c помощью ссылок, потому такое в нём невозможно. Но когда задача требует действительно использования self-referential структур, то приходится расчехлять
unsafe
и использовать сырые указатели, что и позволяет делать вещи вида:А работая с подобной структурой, даже уже в safe Rust, очень легко получить UB, ведь при любом перемещении
AsyncFuture
(мы её только что создали, мы ею владеем, и мы решили её тут же переместить) мы получим dangling pointer вread_into_buf_fut
, ибо само по себе перемещение не обновит указатель, указывающий наx
, а borrow checker не отслеживает сырые указатели.Соответственно, за'
Pin
'ив указательread_into_buf_fut
, мы получаем гарантию на уровне системы типов, что данные позади указателя не будут перемещены (либо что их перемещение не нарушит инвариантов типа,см. Unpin
). И если у нас код где-то мувает запиненное значение, то он просто не скомпилируется.В данный момент не zero cost, так как подкапотная реализация опирается на TLS (thread local storage).
Но, вот буквально уже завтра выходит Rust 1.44, в котором
async
/.await
сделали zero cost, и они будутдоступны для использования в #[no_std]
.vectore.dev не пробовали? Хотелось бы его видеть вместо fluentd.
Хорошо, что это просто коммент к "горячей" статье на Хабре
Было бы гораздо грустнее, если бы это был релиз, уехавший в прод. Клиенты "просто забыл" не сильно понимают.
Забавно...
Условия задачи не полны, либо она не разрешима в ряде случаев. Если бы Вы соответствующие инварианты попробовали описать формально в типах, то точно бы зацепились за этот момент.
Что там было про высокую вероятность?
Ну и ещё тут, если кратко и по сути.
Почти вся. Есть ещё системы идеального шифрования. Они не подходят ни под это допущение, ни под описанные выше.
Давайте представим, что мы обладаем неограниченным ресурсом для полного перебора. Какое кол-во вариантов нам не нужно было бы перебрать, мы делаем это максимум за 1 сек. Перед нами сыпется почти вся криптография, ибо подобрать секретный ключ — тривиальное дело. Вся, кроме, к примеру, "одноразового блокнота", который является примером идеального шифра, потому что в нём длинна ключа совпадает с длинной самого шифруемого сообщения. Таким образом перебрав абсолютно все варианты ключей, мы получим абсолютно все возможные осмысленные тексты заданной длинны. Вопрос: какой из них тот, который был зашифрован изначально? Увы, здесь ответить мы уже не можем никак.
Никак. Но добиться чего-то "пахнущего как" можно.
Последние потуги описаны тут: varkor's blog: Idiomatic monads in Rust.
Им и живы =)
Но там тоже все ещё много работы в плане макросов. Декларативные раскрываются хорошо и дают нормальный автокомплит, но вот редактирование синтаксиса внутри декларативного макроса — только руками. У процедурных все не очень в плане раскрытия, но вот их написание вполне ОК, так как это обычный код работающий с токенами и AST.
Пока что с IDE, и в особенности их работой с макросами, всё далеко не сладко, увы.
Собственно, здесь неплохой разнос:
Factual inaccuracies of “Facebook Libra is Architecturally Unsound”
Было бы неплохо видеть на Хабре перевод в продолжении этой темы.
С этим в целом глупо спорить. Но хорошая новость в том, что в этом Rust тоже достаточно не плох:
Playground
Как видим, не ядерная физика в тривиальных случаях =)
В остальных будет больнее, да.