Pull to refresh
-1
-0.1
Дмитрий @qrKot

Энтузиаст

Send message

По идее, если отстраниться от различия между кооперативной и преемптивной асинхронностью (т.е. наличием/отсутствием явного await), tokio::spawn работает точно так же, как ключевое слово go

Не совсем. tokio::spawn - паттерн actor, go - csp.

Вот тут есть сравнение подходов: https://en.wikipedia.org/wiki/Communicating_sequential_processes

Прям раздел есть Actor vs CSP

Вкратце:

  • актор - управляемый, CSP - нет.

  • csp - обмен сообщениями эксклюзивно через каналы

  • actor позволяет отправку сообщения с отложенной обработкой, csp - блокирует отправку, пока читатель не готов к получению.

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

tokio::spawn работает точно так же, как ключевое слово go.

tokio:spawn сложнее, чем go . Горутины легче.

Неа. tokio::spawn создаёт task-у, которая может быть запущена в любом потоке из имеющегося пула.

Да в смысле "неа"?

У вас async внутри tokyo::spawn! Буквально M асинхронных задач внутри одного потока, M:1 внутри 1:N.

Асинхронные задачи, которые будут выполняться внутри этой таски, исполнятся ровно в том потоке, в котором таска запущена!

В Go - любая асинхронная задача может выполниться на произвольном потоке.

M:N - задачи, закинутые в spawn, распределяются по всем доступным ядрам, если не сказать прямо "работаем только в текущем потоке".

async при этом явно говорит "работаем в текущем потоке".

tokio::spawn(async {

Присмотритесь внимательно:

tokio::spawn - 1:N

async {} - M:1

Это не M:N, это, по сути, от thread::spawn отличается только наличием "легковесного потока".

То, что вы написали - это "запустить поток, внутри которого выполнить асинхронный код". Т.е. это будет N раз по M:1. Это не то же самое, что M:N.

Просто для ясности ситуации:

Допустим, вы спавните (tokio::spawn) 2 потока, должны дождаться выполнения обоих и с полученными данными дальше что-то делать. В первом вы асинхронно запускаете 10 задач, во втором - 2. Допустим, время выполнения асинхронной задачи константно, берем за единицу.

За сколько выполнится ваш код? Очевидно, за 10 (потому что 10 задач на одном потоке выполнялись, а второй выполнил 2 и благополучно сдох).

Теперь представим, что у нас 2 потока доступно гошному рантайму (на самом деле мне, как разработчику, откровенно насрать, сколько их там, я могу и имею право даже не знать). Мы запустили 10 горутин в одном блоке и 2 - в другом. Итого 12. Делим на 2 потока - сделали за 6.

Все, на этом "фишки" языка Go окончены. Он создан ровно ради этой фичи. Во всех остальных сценариях Rust круче, C# круче, Java кручке, все круче (кроме, простихоспади, PHP, Basic'а и "Языка программирования 1С" ).

Я не покажу, я в расте так себе. Но скорее всего, оно M:N, потому что токио - мультитредовый рантайм.

Да, токио - мультитредовый рантайм, и нет, оно не M:N. Оно 1:N.

async/await - M:1, tokyo - 1:N. M:N, кроме Go, из коробки, вроде, Haskell только (или Erlang, не помню, не большой специалист в хаскелях и эрлангах) и умеют. Собственно, весь язык ровно из плясок над "M:N с человеческим лицом" и состоит. Все, что мешает этой концепции - совершенно обоснованно идет лесом. Исключения, например, одна из вещей, которая мешает.

Как типизация влияет на 1:N/M:1/M:N? Разговор был о том, что в го так себе реализованы ошибки.

Типизация, в общем смысле, не мешает. А try/catch, ? и прочие примитивы обработки исключительных/ошибочных ситуаций - мешает. Я вот про это говорю.

И, не смотря на то, что вы считаете, что в других языках так же, то нет, не так же, а получше.

Дык, там и M:N нету. Другие языки решают другие проблемы.

В Go нет исключений и они вряд ли там к месту. Забейте уже на них)

Я бы уже давно забил, если бы вы мне в каждом первом комментарии не рассказывали, какие они классные в C#, и почему в Go они прямо необходимы.

Читаемость роняет мусорный бойлерплейт.

Все же, еще раз про исключения: в го они этим самым мусорным бойлерплейтом и станут, без вариантов.

А растаманский подход с Result/Optional и т.д... ну, в виде дженериков - хоть сейчас пиши. Полноценная реализация на уровне языка - кортежи мешают (они слишком дофига свободы дают)

Еще раз приведу примеры из стандартной библиотеки, где это делается.

Еще раз говорю, это не проброс ошибки, а возврат значения.

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

И это не хорошо и не плохо, это просто цена 100-тысячных RPS.

Может этот код и не вызывается в горутинах, не копал, но явно противоречит вашим ультимативным заявлениям, что так никто не делает и вообще не рекомендуется.

В том и дело, что Go явным образом утверждает, что любой код может запускаться в горутине. А еще Go настаивает, что ошибка - это значение, а значит, может быть проигнорирована. В случае запуска в отдельной горутине, более того, будет проигнорирована.

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

На выходе мы имеем 2 сценария групповой обработки ошибок/исключительных ситуаций:

  1. обработать те, с которыми мы знаем, что делать, остальные уйдут по стеку наверх (exception-style) с падением приложения при выходе за границы стека - неприменимо в Go, т.к. наличие "наверх" никто не гарантирует. Мы вырождаемся в обязанность никогда не возвращать ошибки, что выглядит как бред.

  2. гарантировать обработку всех ошибок (назовем это rust-style) - ну, какбэ, бойлерплейт по обработке рискует превратиться в портянку такого размера, что if err != nil {} покажется цветочками.

А потом поняли, что так жить грустно и начали фичи завозить.

Фичи завозят, стоит признать, аккуратно, дозированно, и только те, которые признаны удачными.

Богатый синтаксис - это ведь круто.

А вот тут - не всегда. Смотрел списки фич в Python'е, чейнджлоги того же PHP... А работать когда?

Ошибка возникает в parse_string_to_smth. Что должен делать parse_file? На месте обработать? Кинуть панику или вернуть что-то пустое? 

Если знает, что с ошибкой делать - обработать на месте. Если не знает - обернуть своей ошибкой и вернуть. Панику - а зачем? Ну, если нужна паника - кинуть панику. В чем проблема-то?

Так это почти всегда (если нормально делать) не зона ответственности этой функции.

Ну, я и говорю:
если не зона ответственности

  • if err != nil { return nil, err }

  • if err != nil { return nil, fmt.Errorf("%w: some package error", err) }

  • if err != nil { return nil, errors.New("error parsing string") }

если зона ответственности: if err != nil { // do something }

И в одном случае там может быть просто варнинг, потому что данные из файла вспомогательные и у нас есть какое-то дефолтное значение. А во втором случае надо паниковать, потому что данные из файла необходимы.

А вот тут вы, внезапно, не очень умную вещь сказали.

"потому что данные из файла" - тут, как минимум, "из файла" - лишнее.

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

А дальше, собственно, от требований пляшем.

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

var ErrShitHappened = errors.New("shit happens")
var ErrWeCanIgnore = errors.New("ignore this")

func readData(someParamsHere...) (any, err) {
  if err := openResource(); err != nil {
    return nil, fmt.Errorf("%w: can't open resourse %w", ErrShitHappens, err)
  }
  if err := parser.ParseResource(); err != nil {
    if errors.Is(err, parser.ErrCritical) {
      return nil, fmt.Errorf("%w: parser failed with %w", ErrShitHappens, err)
    }
    return nil, ErrWeCanIgnore
  }
  if len(result.Rows) == 0 {
    return nil, ErrWeCanIgnore
  }
  .....
  return value, nil
}

func business() {
  data, err := readData(someParams)
  if err != nil && errors.Is(ErrShitHappened) {
    panic(err)
  }
    ...
}

Так что обычно как-раз пробрасывают ошибку от "утилитарной" до какой-то более "сведущей" функции.

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

Я хз, может в го не так, там свой way, надо всегда паниковать или nil вместо значений возвращать

Паниковать надо примерно никогда, ошибки обрабатывать в месте их возникновения, если пришла ошибка, в значение рядом с ней не смотреть. Все, набор рекомендаций закончен.

, но почему тогда я в го-коде вижу if err != nil { return err } ?

Потому что таков путь, и в 99% случаев этого достаточно. Можно бы было забить, но все еще есть 1%, где этой конструкции не хватает. Ну да, такова жизнь, более удачных решений не наблюдается (и не надо опять поднимать песню про исключения. Они а) сами далеки от идеала, б) полноценно нереализуемы в парадигме Go (а эрзац - нафиг не нужен).

Погуглите пропозалы

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

что ж вы так вцепились в исключения?

Вы мне их активно сватаете, я отбиваюсь.

Тем более вы кроме if err != nil в языке других проблем не видите. А они там есть, причем в ассортименте, и гораздо более важные, чем "среднее количество букв, уходящее на обработку ошибок".

Достойный довод, чтобы не язык не развивался)

Дык, в том и дело, что развивается.

Кастомные ошибки - завезли (возможность однозначной идентификации ошибки и матчинга ошибок по стеку с целью более гибкой обработки - гораздо более важная штука, чем уменьшение количества if err != nil )

Дженерики - завезли (тоже фича сильно более интересная, чем неработающий try/catch).

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

Решаются те проблемы, которые а) можно решить, б) важно решить.

Все необходимое там есть и активно используется.

Ключевое слово return - это возврат, а не проброс. Вы же понимаете, что возврат и проброс чем-то семантически различаться должны? Не только ключевое слово throw вместо return, но и причина, по которой это слово появилось. Оно должно делать что-то другое, отличное от того, что return делает.

throw Exception - выбросить исключение, не обработать исключение в текущем методе - пробросить исключение.

if err != nil { return err } - это не проброс, а закат солнца вручную какой-то. Нету в Go механики выброса, проброса, переброса, пофигчегоброса, есть только возврат.

Rust, где система обработки ошибок не идеальная

Ну вот как станет идеальная, так сразу и скопипастим целиком!)

В C# есть исключения

А в Go - нету, потому что они сломают горутины. А весь язык ровно заради горутин и придумывался.

Но обработка ожидаемых ошибок там тоже не вызывает восхищения.

Вот и я говорю, что ровно тот же хрен, просто в другой руке.

Хотя есть некоторые приятные фичи, вроде кастомных implicit cast'ов, которые могут действовать, как трейт From<T> в Rust.

А могут и не действовать. И вреда от них может быть больше, чем пользы.

Везде пробрасывают. И даже в Go пытаются.

Каким образом в Go пытаются, если в языке нет возможности проброса? Вернуть значение - пожалуйста, что-то пробросить - ваще без вариантов.

// Запускаем параллелльно

1:N

// Запускаем асинхронно

M:1

M:N покажете?

thread::spawn

Системный тред? Вот тот самый, который 12МБ памяти на старте просит? С отдельным коллстеком? А тысячу сможем запустить? А 10 тысяч?

Вы говорите, что исключения - ерунда неудобная

Я говорю, что в контексте Go роль исключений сводится к синтаксическому сахару над if err != nil {} (т.е. полезность в контексте применимости в Go - очень спорна)

отсутствие в Go try-оператора

Не надо он там, только читаемость роняет.

кастомных implicit cast'ов

Явное лучше неявного, имхо. Палка о двух концах, которая а) может больно ударить (особенно в контексте достаточно куцей системы типов в Go), б) шаг в сторону слабой (нестрогой) типизации. Очень дискуссионно, причем вы - на позиции "я так привык".

типов-сумм

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

Что мешает пробрасывать ошибки в вызывающую функцию?

Да блин, сколько повторять-то? ОТСУТСТВИЕ ВЫЗЫВАЮЩЕЙ ФУНКЦИИ очень сильно мешает пробрасывать в нее ошибки! Ну хоть одним глазком посмотрите, как горутины устроены, и что такое CSP. Не обязательно все читать, хотя бы просто тезисно пройтись.

или крайности: либо суровая явная явность

Дык, прямо лозунг Go "Суровая Явная Явность". С самого начала так декларировалось.

полностью неявный флоу исключений

Полностью явный флоу исключений называется Checked Exceptions. Его критикуют, сильно и небезосновательно.

то это будут разные (вложенные) try-блоки, аналогично работе с исключениями

Но ведь именно в этом (пусть и вырожденном) случае try/catch более многословен и даже местами некрасив.

if err := someFunc(); err != nil {
  return err
}
try {
  someFunc();
} catch e Exception {
  throw e;
}

И кто тут более многословный?

Нет, в расте могу их поднимать наверх насколько хочу

Ну вот только в Расте и можете.

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

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

То есть в любом языке с легковесными тредами нет исключений?

В любом языке с горутинами нет исключений. Потому что концепция горутин напрямую противоречит концепции исключений.

Да и "любых языков с легковесными тредами" не так много. Для статистики откровенно маловато. Где там CSP из коробки еще в языке есть, кроме Go? В Limbo, разве что. Но там тоже с исключениями не богато, вроде?

А почему в ерланге они тогда есть?

Потому что Erlang предоставляет модель акторов из коробки, а Golang - CSP? Потому что не перехваченное исключение в эрланге уронит актора, а в Go - все приложение? Потому что в эрланге легковесные потоки не шарят стейт между собой, а гошные горутины напрямую шарят память? Каких еще аргументов надо?

Не "ради", исключения в C# - это родной инструмент

В C# - родной. Вы же сетуете, что такой штуки в Go нет. Ну вот в Go, окромя обязательного перехвата всех исключений в конце каждой функции, вариантов нет. Полезная нагрузка - возможность обработать исключительные ситуации пачкой. Геморрой - обязанность обработать все исключительные ситуации в конце каждой функции.

Т.е. вы бесячий if err != nil {} предлагаете на ровно такой же try/catch Exception {} поменять.

По идее, ничто не мешает задизайнить в языках такую штуку для легковесных типов: аналог try-оператора, который позволял бы типу "всплывать", но не из функции, а в до ближайшего catch внутри вызывающей функции. То есть чисто синтаксический сахар над легковесными типами ошибок в стиле исключений, но без их затрат на раскрутку стека и собирание бэктрейсов.

При этом, ни в одном языке такого не сделали... Может, единогласно решили, что оно не надо?

Например, потому что ошибка NotFoundInDatabase , полученная из какой-нибудь CheckUserValid и CheckUserBalance должны, все равно, быть обработано по-разному?

Не понимаю, о чем вы. Бизнес-логика выше, обработка исключений - отдельно ниже.

Тут вопрос в том, что в довесок - обязанность обязательно обработать исключение в конце функции.

Исключения можно безболезненно ловить выше

Дак я вам еще раз говорю: нельзя, нету никакого "выше". Мы в Go.

а try-функции или аналогичные конструкции - для ситуативного, что необходимо обрабатывать тут же.

А это в Go есть. Он именно так и делает: обрабатывает "тут же".

Понимаете, 2 флоу: исключения и ошибки. Ошибки везде (включая Go) обрабатываются на месте, а исключения в Go не работают, потому что горутины.

Не каждая ошибка - это исключительная ситуация. Поэтому для них отдельный флоу, который обладает супер-способностями, но не очень подходит для прочих ошибок.

... а флоу обработки "прочих ошибок" в других языках не отличается от Go-шного... В C# ситуация, получается, ровно такая же, как в Go, но там - красиво, а здесь - нет...

Альтернативы-то какие?

Так нужна не альтернатива, а допиливание имеющегося. Чтобы в Go тоже можно было пробрасывать ошибки выше без боли.

Это Go, "выше" может не быть. Куда пробрасывать-то? И, самое главное, зачем? Нигде не пробрасывают, а в Go всенепременнейше надо!

Вау, снимаю шляпу! Мсье знает толк...

Это не решение проблемы, это вынужденный отказ от удобства.

Пропозалов по решению проблем я несколько уже видел, решения - нет. Вы его тоже не показали.

Кортеж с ошибкой на C#

Ну, и что следует из того, что в C# можно сделать такую же обработку ошибок, как в Go? Удобство-то где?

Лол. Вы сначала этот сахар нарекаете "исключениями", а потом говорите с такой экспрессией, будто я предлагал сделать настоящие исключения в Go.

Дык, вы мне всю дорогу демонстрируете красоту try/catch. В каком языке try/catch используется не для обработки исключений? Исключение и ошибка - это ведь не одно и то же. Исключение - это альтернативный флоу обработки исключительных ситуаций.

Если ошибка остается в скоупе возвращаемых значений и нет механики перехвата исключений выше по стеку (а мы уже выяснили, что в Go она нереализуема) - смысл городить альтернативный флоу обработки ошибок? Просто заради "чтобы было красиво"? Ну, оно не всегда красиво. В большинстве случаев красиво, но не всегда.

Придумают удобный синтаксис обработки (пока нигде не придумали) - затащат в язык. Не придумают - ну, будем страдать вместе со всеми.

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

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

Флоу Go:

// удаляем блок целиком
if err := someFuncToDelete(); err != nil {
  if error.Is(err, ErrSomeError)
  ... // пофиг, что внутри
}
// вот прям досюда. 
// Мы знаем, что эта ошибка - результат конкретного вызова, она не аффектит соседей.

// тоже, вроде, видно, где удалять
val, err := someAnotherFuncToDelete()
if err != nil {
  if errors.Is(err, ErrSomeError)
  ... аналогично пофиг, сколько проверок
}

// и даже так не особо спутаешь
val, err := oneMoreFuncToDelete()
if errors.Is(err, ErrSomeError) {
  // обработали
}
...
if err != nil {
  // пофиг какая ошибка
}
// ну видно же, что после этой строки уже можно не проверять?

value, err := someFuncToNotDelete()

Поиск того, что, возможно, надо удалить, ограничен набором ошибок, которые конкретный вызов может вернуть, и находится в одном месте

Флоу с исключениями:

try {
  someMethodNotToDelete()
  someMethodToDelete() // вот это удаляем
  someAnotherMethodNotToDelete()
} catch e1 SomeException {
  // вот этот блок удаляем?
} catch e2 SomeAnotherException {
  // а этот? он все еще нужен, или уже нет?
} catch e Exception {

}

Вы, видимо, как-то вычленяете список исключений, которые потенциально может конкретный вызов выкинуть, а потом - проверяете по остальным инструкциям, не могут ли они выкинуть аналогичное?

Квадратичная сложность алгоритма поиска того, что можно удалить?

Это все слова, выше я приводил практически идентичный код, где вся разница - в наличии скобок вокруг кортежа и new перед ошибкой.

Код аналогичный, только обернутый в try/catch? И в конце обработка известных исключений?

В Go при оборачивании ошибки будет боксинг исходной ошибки, к слову.

Зато при возврате кортежа из значения + потенциальной ошибки - не будет.

Сообщество говорит, что надо.

Не увидел там требований вкорячить исключения, если честно. То, что чуть больше 10% опрошенных считают error-handling в Го сложным - вижу. Даже пару proposal'ов видел про групповую обработку ошибок. Все забритые (по вполне объективным причинам). Уверен, что прямо в текущий момент кем-то пилятся штук 5 новых.

Только там синтаксический сахар для того, чтобы не писать if err != nil , просят. Не исключения!

Будет лучше

Или еще лучше

Вкусовщина же, чесслово. И даже не короче получилось...

  • обработка ошибок уезжает ниже;

А оно настолько круто, чтобы заради этой "фишки" целые исключения городить?

А не вы ли говорили, что бизнесовые проверки, типа UserExists() или user.CanPostComments() лучше во флоу исключений не ссыпать? Да еще и продемонстрировали (!TryCheckCanComment(user.Id, out var badRequestResponse))

Если так, то ведь оно вообще страшненько получится:

// чем, по сути, вот это:
if (!TryCheckCanComment(user.Id, out var badRequestResponse)) {
  return badRequestResponse;
}

// отличается от вот этого:
c

// тем, что дальше еще обязательно надо exception обработать?
try {
  if (!TryCheckCanComment(user.Id, out var badRequestResponse)) {
    return badRequestResponse;
  }
} catch e ErrorException {
  // здесь тоже надо что-то сделать
}
  • блок try{...} задает границы, выделяющие зависимые части алгоритма.

// блок, даже прям с ограниченной областью видимости
if ok, err := TryCheckCanComment(user.Id); err != nil || !ok {
  return badRequestResponse(err);
}

// не знаю, тоже не вижу проблем с идентификацией, к чему проверка относится
ok, err := TryCheckCanComment(user.Id)
if err != nil || !ok {
  return badRequestResponse(err);
}

В итоге код бизнес-логики читается гораздо легче:

Дело привычки же. Мне вот ваш вариант не нравится - мозг менее приучен такое парсить.

И тут же приводите пример, где для GetUser обрабатываются разные типы ошибок.

Специально же и привел, чтобы продемонстрировать кейс, когда мы знаем, что делать с 2 из всех возможных ошибок, а остальные просто выбрасываем наверх, чтобы обработать выше.

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

Однако, цель здесь - демонстрация того, что условный C#, несмотря на наличие исключений, при обработке ошибок может запросто "деградировать" до подхода Go там, где нужна наглядность обработки ошибок

Деградировать - это в легкую! Можно и до return-codes деграднуть, чо останавливаться-то?)

Только он становится еще более многословным и неудобным, чем Go, на таких кейсах.

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

А, ну да, забыл, можно же еще боксинг-анбоксинг накрутить!

раскрутка стека вызовов не нужна

Так в том и прикол, что раскрутка стека вызовов в Go работает ровно до первого go func() в коде.

Т.е., по факту, ее нет. И перехватить исключение выше вы не можете. А "исключения" в виде синтаксического сахара для группового сравнения err с nil... А надо оно вообще?

Кто обработает some error? Никто, эта ошибка была потеряна.

Все верно, только возвращаем мы таки не строку, а ошибку. Но в данном контек.

Пофиг, в текущем контексте все верно.

Почему потерянная ошибка - нормально, а потерянное исключение - страшно-страшно?

Потому что потерянная ошибка, в отличие от потерянного (не обработанного) исключения, не приводит к крашу приложения?

Но даже в Go не любая функция предназначена для запуска в горутине.

В том и дело, что все функции в Go одинаковы. И все могут быть запущены в горутине, а значит неизбежно будут.

Понимаете, в C# есть правило "любая функция может выбросить исключение", в го - "любая функция может быть запущена в горутине". Вы живете с одним ограничением, я - с другим.

У вас всегда есть стектрейс от любой точки приложения до точки входа (до main'а), у меня - совершенно не обязательно. Зато у меня есть горутины (M:N-асинхронность), у вас - нет.

Как минимум, функции, возвращающие err, лучше в горутине напрямую не запускать.

Почему нет-то? Если меня не волнует возвращаемый результат функции, кто мне запретит?

Исключений в Go нет. Вернулась ошибка - ну ок, вернулась, дальше что? Приложение не упадет, т.к. ошибка - это не исключение, я не обязан ее перехватывать.

Наличие ошибки сообщает вызывающему коду о том, что остальным возвращаемым параметрам нельзя доверять. Там может быть nil, может быть неудобоваримое говно, может быть невалидный результат. Мы не можем с ним дальше работать. Это, бесспорно, грустно... Но...

go _, _ = someFunc() - посмотрите внимательно, мы и не собирались работать с этим результатом. Нельзя уронить программу по обращению к nil, если ты к нему не обращаешься!

1
23 ...

Information

Rating
Does not participate
Location
Бийск, Алтайский край, Россия
Date of birth
Registered
Activity