Инкапсуляция, пожалуй, единственный неотъемлемый компонент ООП. Под инкапсуляцией подразумевается, как правило, ограничение доступа к внутреннему состоянию объекта извне. Остальное - наносное.
Очаровательно-инфантильный подход на особенно удачном примере "банковских транзакций" (к которым вас с вашей ЛЛМ на пушечный выстрел подпускать нельзя)
По порядку:
Представим сложный объект "Банковский счет" с методом Transfer(BankAccount other, decimal amount).
Зачем мы его представим? В продакшене мы такое никогда использвоать не будем. У нас будет достаточно простой объект BankTransaction(decimal amount, BankAccount from, BankAccount to). Целиком всю высосанную из пальца проблему решит.
Проблема композиции и Deadlock: ее нет, это транзакция, она одной операцией выполняется. Мы лочим оба баланса разом.
Проблема гранулярности блокировки: ее нет. У нас нет сложного объекта. Есть объект BankAccountBalance, есть BankAccountHistory, есть BankAccountOwner - буковка S из замечательной аббревиатуры SOLID. И мы всегда знаем, что лочить.
Блокирующий I/O... Кхм, тут только цитировать:
Что если в конце Transfer нужно записать транзакцию в базу данных? Делать это внутри lock — самоубийство.
Самоубийство - это писать банковскую проводку в БД вне лока транзакции. Вот тут прям настоящее самоубийство.
Предлагаемое вами решение - эпический фейл.
Вы предлагаете Аккаунт1.СписатьДеньги -> Аккаунт2.ЗачислитьДеньги -> ПисательВБД.ЗаписатьВБД. Теперь, внимание, вопрос: с первого аккаунта деньги списались, а Аккаунт2 зафейлился. Чо делаем? А если оба ОК, но БД упала - чо делаем? Если упростить, то буквально:
Как откатить изменения в счетах, которые уже "отпустили"? Это требует сложнейшей логики компенсаций.
Ах, блин, это же в недостатках логики с локами написано! Или ваша модель ортогональна решаемой проблеме (и более того, абсолютнейшим образом не подходит для ее решения)?
ООП говорит: "Вот объект BankAccount. У него есть приватное поле _balance. Только его методы, например Deposit(), могут менять это поле. Я, объект, гарантирую целостность своих данных". Это и есть инкапсуляция.
Это не инкапсуляция.
"Я объект BankAccount, представляющий банковский счет. У меня есть методы Deposit и Withdraw, позволяющие менять баланс счета. Я гарантирую целостность своих данных. Как я это делаю и в каком виде храню баланс - не твое собачье дело" - вот это инкапсуляция.
На самом деле, "истинное" значение баланса лежит не в памяти моего C# объекта, а в строке таблицы Accounts в PostgreSQL
Вообще по барабану, где оно лежит и как реализовано до тех пор, пока подробности не торчат наружу.
Смысл абстракции именно в том, чтобы ты не знал, что внутри. Это гарантия, что ты можешь безопасно поменять внутреннюю реализацию, не задевая зависящих от нее частей программы.
Вот у тебя вышеуказанный BankAccount. Допустим, ты пишешь реализацию: методы Deposit и Withdraw набирают номер телефона оператора и заставляют нейросеть зачитать голосом "увеличить баланс счета №192839102931012 на 100 рублей". Реализация говно, конечно, но абстракция - идеальна.
Почему идеальна абстракция? Ну, например, ты достаточно быстро понял, что дозвон может занимать до 5 минут, что сильно тормозит поток транзакций. Надо что-то делать. Например, батчевать. Пока номер набирается, копить команды и вываливать на оператора разом то, что накопилось: "счет 1923891243 +100, счет 932181292348 -100". Прелесть в том, что весь твой код, который подсчитывает, на сколько менять баланс, вообще ни в одной строчке не изменился. Все изменения - внутри класса BankAccount.
Окей, на 18-й итерации до тебя дошло, что с оператором что-то не так: то опечатывается, то трубку не берет, то рыдает и в запой уходит. Ты ставишь какую-нибудь постгрю и выкидываешь к чертовой матери всю эту дичь с телефонией. Заменяешь тупым запросом к БД. Изменения - монументальные, а абстракция не изменилась. Чтобы поменять баланс, тебе нужно дергать все те же ручки с теми же параметрами.
Вот это - абстракция, и она не течет. Хочешь пример текущей абстракции - посмотри на слайсы в Go. А вот то, что ты напридумывал, не течет.
Базовая реализация воркерпула над каналом выглядит, все же, вот так:
func RunWorkerPool[T any](workerCount int, in <-chan T, fn func(T)) {
var wg sync.WaitGroup
defer wg.Wait()
for range workerCount {
wg.Add(1)
go func() {
defer wg.Done()
for item := range in {
fn(item)
}
}()
}
}
Воркерпул ужасен. Прям пример "как не надо делать".
Зачем воркеру вообще знать, что он в вейтгруппе работает? Воркер - это тупая штука, которая, просто берет по одному значению из канала и что-то с ним делает, пока канал не закроется. Зачем ему при этом знать, что он в вейтгруппе живет? Просто чтобы нельзя было без вейтгруппы его использовать?
Fan-In/Fan-Out - это же вот отсюда скопипащено? https://habr.com/ru/companies/otus/articles/886740/ Ну там плохому учат. Давайте начнем с того, что fan-in и fan-out (как и мультиплексор/демультиплексор) две достаточно разные, не зависящие друг от друга штуки. "По классике" в Go все обычно сводится к сбору N каналов в 1 или распределению данных из одного канала в N других.
В вашем примере просто еще один воркерпул. Вот прям идентичный алгоритм с новыми названиями функций и переменных.
Зачем буфер у канала? Не разобрались просто, как тут правильно с вейтгруппой работать? Ну вот эталонно-плохой код получился...
Пайплайн получился достаточно опасный. Каждый пионер Go-разработчик в курсе, что запускать запись в канал до гарантии чтения из него - достаточно печальная идея. Поэтому методы вашего "пайплайна" лучше инвертировать. Да и конструкторы можно будет "зачейнить", и потом быть уверенным, что достаточно закрыть последний вернувшийся канал, и все само схлопнется.
В PubSub'е отлично бы смотрелся Fan-Out на выходе, как пример того, что паттерны отлично реализуются поверх других паттернов. Ну и от неприятия defer'ов пора бы уже избавляться - 2025 год на дворе.
Select-с-таймаутом вообще не паттерн. Ну и демонстрировать на этом молодежи работу с таймаутами, утверждая, что это для сценариев "Сетевой клиент, который пытается подключиться к серверу и останавливается, если сервер не отвечает вовремя." - форменная бессовестность. Покажите людям контекст.
Семафор... Ну давайте будем с собой честными, в примере - просто еще один воркерпул.... Вы бы хотя бы up/down вне воркера делали, в этом был бы смысл. А так - оно ж даже количество горутин не ограничивает...
Рейтлимитер... Ну, с тикером работаем странно. Вот здесь как раз select { case <- ticker.C: } отлично зайдет. Закрыли неаккуратно, так память течет. Да и в сценарии, если fmt.Println("Обработка запроса", req)завершился позже тикера - будем ждать следующего тика зачем-то... Он тоже плохой.
new почти никогда не используют для chan, map, slice. Да есть случаи но это оч редко и очень специфически
Давайте будем честными и сократим до "new почти никогда не используют. Да есть случаи но это оч редко и очень специфически"
для array и struct я же привел пример, ели передавать по значению а не ссылку то оригинальный массив или структура не измениться
Ну, собственно, в случае со слайсом - тоже не изменится. Я вам больше скажу, слайс в принципе не может измениться, он иммутабельный. Аппенд всегда новый слайс создает.
a := make([]int, 0, 3)
a = append(a, 1)
b = append(a, 2)
fmt.Println(a,b) // внезапно [1] и [1,2]
Я их называю ссылочными переменными
Ну, собственно, зря.
"Ссылочные типы данных в программировании – это типы, которые хранят не само значение, а адрес (ссылку) на место в памяти, где хранится это значение"
Из перечисленных вами slice, map и channel вышеуказанное верно только для map, которая буквально алиас на *hMap. Слайс и канал - иммутабельные структуры, со всеми вытекающими.
Для value type мы можем использовать ключевое слово new или литералы для создания
Для reference type мы должны использовать ключевое слово makeдля создания или литерал, иначе вы получите panic при попытки что-то сделать с nil
Вот это откровенная неправда.
Возьмем слайс: make - запросто, литералом - да не вопрос, var a []int - вообще по барабану. nil-слайс - валидная структура.
Нил-мапа? Ну, при попытке чтения, например, ничего страшного не случится. А вот при попытке записать - паника будет. Но я вам интереснее скажу: при инициализации литералом попытка записи вызывала панику до одной из последних версий.
Канал... А как вы его литералом инициализировать собираетесь?
Да и вообще, в кучу кони и люди. Литералы ортогональны ссылочности/нессылочности. Они вообще вещь в себе - просто сокращенная запись для "создать переменную, проинициализировать ее начальным значением". Для каких-то типов есть, для каких-то не придуманы.
я тут не стал вдаваться в подробности, и надеялся, что это и так понятно читателю
Ну т.е. вы не стали в принципе вдумываться в то, что написали, и решили, что читателю и так понятно: а) что фраза "в го есть 3 ссылочных типа: мапа, слайс и канал" означает "в го не используется терминология ссылочных типов" б) что фраза "слайс должен быть явно инициализирован словом make, иначе вы получите панику" на самом деле означает "слайс можно инициализировать как угодно, паники от него не добьешься"
Берем Донована/Кернигана "Язык программирования Go". Глава первая: Hello World. Глава вторая: ключевые слова, переменные, указатели. Глава третья: типы данных.
"Уровень", необходимый для чтения книг по программированию - умение читать.
ссылочные структуры, к которым относятся только три типа: slice, map и chan
Давайте попробуем понять, что такое "ссылочная структура". Ссылочный тип, вы имели в виду?
Вот есть мапа - она ссылочная (только не структура, она буквально алиас для указателя на hMap). А слайс с каналом тут каким боком?
и просто так через «var» их не завести — получим nil и панику при первом же обращении
Зачем вы неправду-то рассказываете?
var m map[int]int
fmt.Println(m[0]) // 0, нет паники
var ch chan int
<- ch // все еще нет паники (есть fatal error: deadlock, но только если рядом нет другой горутины)
Но есть разница: &User{} создаёт сразу полностью известную структуру
new(User) создает ровно такую же.
1. В generic‑коде, где T неизвестен на этапе компиляции:
func NewPointer[T any]() *T {return new(T)}
Это невозможно выразить через &T{}, потому что T может быть чем угодно: int, []byte, chan string, и у вас просто нет доступа к литералу.
func NewPointer[T any]() *T {
var t T
return &t
}
А вот так, внезапно, можно
Когда вы хотите получить нулевой объект, но не хотите указывать поля, и вам неважно, что они все обнулены:
conn := new(net.Conn)
А почему не вот так?
conn := &net.Conn{}
var cn net.Conn
conn = &cn
В структурах, где не нужны начальные значения, например, когда вы вручную наполняете поля позже:
type Builder struct {
parts []string
}
b := new(Builder)
b.parts = append(b.parts, "step1", "step2")
То же самое, но не обязательно целиком в куче:
b := &Builder{}
b.parts = append(b.parts, "step1", "step2")
Можно ли делать new([]int)? А new(map[string]string)?
Вместо make? Нельзя. Ну, просто потому, что new([]int) вернет указатель на слайс, а make([]int) - собственно слайс.
Можно вот так: `slice := *new([]int)` - подумайте, для чего тут "звездочка"
В этом и есть подвох: new вернёт указатель на пустой контейнер. Это будет nil, и при попытке использовать его как полноценный slice или map вы быстро огребёте:
m := new(map[string]int)
(*m)["foo"] = 42 // panic: assignment to entry in nil map
Ну вот, вырожденный пример с мапой. Мапа паникует не из-за того, что ее через new создавали, а из-за того, что в нее явно запрещена запись до инициализации. `fmt.Println((*m)[1])` - вот эта штука не паникует, нормальная мапа.
И ничего не случилось, никаких паник, все законно.
А вот тут:
func SetDefaultPort(p *int) {
if p == nil {
p = new(int) // а если эту вот строчку убрать - ничего не сломается
// но кое что изменится, угадайте, что
*p = 8080
}
fmt.Println("Port:", *p)
}
Или для паттерна «опциональное значение через nil»:
type Options struct {
RetryCount *int
}
А здесь вам new зачем нужен?
&Options{} и new(Options) в поле RetryCount будут содержать совершенно идентичные nil'ы...
Да и вообще паттерн Options не так реализовывается.
make в Go — это не про выделение памяти как new. Это про инициализацию ссылочных структур, которые не могут существовать без подготовки
Вполне существуют, просто надо понимать, что с ними неинициализированными делать можно...
Если вы создаёте слайс через make, то Go делает примерно следующее:
Вот почему make([]int, 0, 100) — это рабочий, но нулевой по длине слайс. Его можно append'ить без аллокаций до 100 элементов. Если бы вы написали var s []int, вы бы получили nil‑слайс, у которого и ptr, и len, и cap — нули.
make([]int, 0, 100) - это 24 байта заголовка + new([100]int) - непрерывный кусок памяти в хипе.
Попробуйте взять у него s[0] — получите панику. make защищает от этого, создавая слайс, который реально готов к работе.
Когда ты читаешь make(chan error, 1), ты сразу видишь: «канал односторонний, используется для отправки единственного сигнала».
Это где я это должен сразу увидеть???
Откуда "односторонний" канал-то??? `<-chan Type`, `chan<- Type` - односторонние, а вот это - обычный канал.
А откуда "единственный сигнал"? 1 - это размер буфера! Я могу миллион "сигналов" этих ваших напосылать, при наличии читателя. Причем даже без буфера вообще - тоже могу.
Да и откуда "сигналы"-то взялись? Явно же написано chan error, ошибки это, а не сигналы.
Ну, насколько я в курсе, с t490 по t14 gen2 вся линейка была в одном корпусе с метрически-совместимыми компонентами. Т.е., условно, можно взять 490й и в тупую вкорячить туда материнку от t14gen2 - встанет как родная. Дисплеи взаимозаменяемы, но может понадобиться переходник.
Ну как минимум про SEQUEL (Structured English QUEry Language) и его идейного наследника SQL не слышал только ленивый.
we believed that it should be possible to design a relational language that would be more accessible to users without formal training in mathematics or computer programming
Формулировка, конечно, корявая. Но в целом - вполне рабочая схема. Язык заносит какую-то классную фичу, конкуренты ее воспроизводят. А дальше либо языку есть что предложить сверх этой фичи, либо он становится не нужен, потому что все его киллер-фичи уже есть в конкурирующих языках.
В целом достаточно много языков так и вымерло, оставив по себе память в виде фич, осевших в других языках.
Инкапсуляция, пожалуй, единственный неотъемлемый компонент ООП. Под инкапсуляцией подразумевается, как правило, ограничение доступа к внутреннему состоянию объекта извне. Остальное - наносное.
Вот это - гарантируется. Что не так?
Очаровательно-инфантильный подход на особенно удачном примере "банковских транзакций" (к которым вас с вашей ЛЛМ на пушечный выстрел подпускать нельзя)
По порядку:
Зачем мы его представим? В продакшене мы такое никогда использвоать не будем. У нас будет достаточно простой объект BankTransaction(decimal amount, BankAccount from, BankAccount to). Целиком всю высосанную из пальца проблему решит.
Проблема композиции и Deadlock: ее нет, это транзакция, она одной операцией выполняется. Мы лочим оба баланса разом.
Проблема гранулярности блокировки: ее нет. У нас нет сложного объекта. Есть объект BankAccountBalance, есть BankAccountHistory, есть BankAccountOwner - буковка S из замечательной аббревиатуры SOLID. И мы всегда знаем, что лочить.
Блокирующий I/O... Кхм, тут только цитировать:
Самоубийство - это писать банковскую проводку в БД вне лока транзакции. Вот тут прям настоящее самоубийство.
Предлагаемое вами решение - эпический фейл.
Вы предлагаете Аккаунт1.СписатьДеньги -> Аккаунт2.ЗачислитьДеньги -> ПисательВБД.ЗаписатьВБД. Теперь, внимание, вопрос: с первого аккаунта деньги списались, а Аккаунт2 зафейлился. Чо делаем? А если оба ОК, но БД упала - чо делаем? Если упростить, то буквально:
Ах, блин, это же в недостатках логики с локами написано! Или ваша модель ортогональна решаемой проблеме (и более того, абсолютнейшим образом не подходит для ее решения)?
Очень странный вывод. Инкапсуляция - это сокрытие, и оно вот прям в вашем примере есть, и даже работает.
Это в каком месте ООП такое заявляет? Может, вы просто неверно поняли то, какие гарантии ООП дает?
У вас странное понятие о протекающих абстракциях.
Это не инкапсуляция.
"Я объект BankAccount, представляющий банковский счет. У меня есть методы Deposit и Withdraw, позволяющие менять баланс счета. Я гарантирую целостность своих данных. Как я это делаю и в каком виде храню баланс - не твое собачье дело" - вот это инкапсуляция.
Вообще по барабану, где оно лежит и как реализовано до тех пор, пока подробности не торчат наружу.
Смысл абстракции именно в том, чтобы ты не знал, что внутри. Это гарантия, что ты можешь безопасно поменять внутреннюю реализацию, не задевая зависящих от нее частей программы.
Вот у тебя вышеуказанный BankAccount. Допустим, ты пишешь реализацию: методы Deposit и Withdraw набирают номер телефона оператора и заставляют нейросеть зачитать голосом "увеличить баланс счета №192839102931012 на 100 рублей". Реализация говно, конечно, но абстракция - идеальна.
Почему идеальна абстракция? Ну, например, ты достаточно быстро понял, что дозвон может занимать до 5 минут, что сильно тормозит поток транзакций. Надо что-то делать. Например, батчевать. Пока номер набирается, копить команды и вываливать на оператора разом то, что накопилось: "счет 1923891243 +100, счет 932181292348 -100". Прелесть в том, что весь твой код, который подсчитывает, на сколько менять баланс, вообще ни в одной строчке не изменился. Все изменения - внутри класса BankAccount.
Окей, на 18-й итерации до тебя дошло, что с оператором что-то не так: то опечатывается, то трубку не берет, то рыдает и в запой уходит. Ты ставишь какую-нибудь постгрю и выкидываешь к чертовой матери всю эту дичь с телефонией. Заменяешь тупым запросом к БД. Изменения - монументальные, а абстракция не изменилась. Чтобы поменять баланс, тебе нужно дергать все те же ручки с теми же параметрами.
Вот это - абстракция, и она не течет. Хочешь пример текущей абстракции - посмотри на слайсы в Go. А вот то, что ты напридумывал, не течет.
Хранилище паролей на всех платформах примерно одинаково надёжно.
Ваш софт, очевидно, менее надёжен на linux. Но это не Linux плохая ось, а вы - плохой разработчик. Linux с TPM работать умеет, в отличие от...
Серьезно? Hwinfo, hdsentinel - жизненно необходимые программы на каждом десктопе?)
Базовая реализация воркерпула над каналом выглядит, все же, вот так:
Ну это слабо похоже на базовую реализацию.
Вы же разрешите покритиковать?
Воркерпул ужасен. Прям пример "как не надо делать".
Зачем воркеру вообще знать, что он в вейтгруппе работает? Воркер - это тупая штука, которая, просто берет по одному значению из канала и что-то с ним делает, пока канал не закроется. Зачем ему при этом знать, что он в вейтгруппе живет? Просто чтобы нельзя было без вейтгруппы его использовать?
Fan-In/Fan-Out - это же вот отсюда скопипащено? https://habr.com/ru/companies/otus/articles/886740/
Ну там плохому учат. Давайте начнем с того, что fan-in и fan-out (как и мультиплексор/демультиплексор) две достаточно разные, не зависящие друг от друга штуки. "По классике" в Go все обычно сводится к сбору N каналов в 1 или распределению данных из одного канала в N других.
В вашем примере просто еще один воркерпул. Вот прям идентичный алгоритм с новыми названиями функций и переменных.
Да и вот это вы зачем сделали,
Зачем буфер у канала? Не разобрались просто, как тут правильно с вейтгруппой работать? Ну вот эталонно-плохой код получился...
Пайплайн получился достаточно опасный. Каждый
пионерGo-разработчик в курсе, что запускать запись в канал до гарантии чтения из него - достаточно печальная идея. Поэтому методы вашего "пайплайна" лучше инвертировать. Да и конструкторы можно будет "зачейнить", и потом быть уверенным, что достаточно закрыть последний вернувшийся канал, и все само схлопнется.В PubSub'е отлично бы смотрелся Fan-Out на выходе, как пример того, что паттерны отлично реализуются поверх других паттернов. Ну и от неприятия defer'ов пора бы уже избавляться - 2025 год на дворе.
Select-с-таймаутом вообще не паттерн. Ну и демонстрировать на этом молодежи работу с таймаутами, утверждая, что это для сценариев "Сетевой клиент, который пытается подключиться к серверу и останавливается, если сервер не отвечает вовремя." - форменная бессовестность. Покажите людям контекст.
Семафор... Ну давайте будем с собой честными, в примере - просто еще один воркерпул.... Вы бы хотя бы up/down вне воркера делали, в этом был бы смысл. А так - оно ж даже количество горутин не ограничивает...
Рейтлимитер... Ну, с тикером работаем странно. Вот здесь как раз select { case <- ticker.C: } отлично зайдет. Закрыли неаккуратно, так память течет. Да и в сценарии, если
fmt.Println("Обработка запроса", req)
завершился позже тикера - будем ждать следующего тика зачем-то... Он тоже плохой.Давайте будем честными и сократим до "new почти никогда не используют. Да есть случаи но это оч редко и очень специфически"
Ну, собственно, в случае со слайсом - тоже не изменится. Я вам больше скажу, слайс в принципе не может измениться, он иммутабельный. Аппенд всегда новый слайс создает.
Ну, собственно, зря.
"Ссылочные типы данных в программировании – это типы, которые хранят не само значение, а адрес (ссылку) на место в памяти, где хранится это значение"
Из перечисленных вами slice, map и channel вышеуказанное верно только для map, которая буквально алиас на *hMap. Слайс и канал - иммутабельные структуры, со всеми вытекающими.
Вот это откровенная неправда.
Возьмем слайс: make - запросто, литералом - да не вопрос, var a []int - вообще по барабану. nil-слайс - валидная структура.
Нил-мапа? Ну, при попытке чтения, например, ничего страшного не случится. А вот при попытке записать - паника будет. Но я вам интереснее скажу: при инициализации литералом попытка записи вызывала панику до одной из последних версий.
Канал... А как вы его литералом инициализировать собираетесь?
Да и вообще, в кучу кони и люди. Литералы ортогональны ссылочности/нессылочности. Они вообще вещь в себе - просто сокращенная запись для "создать переменную, проинициализировать ее начальным значением". Для каких-то типов есть, для каких-то не придуманы.
Ну т.е. вы не стали в принципе вдумываться в то, что написали, и решили, что читателю и так понятно:
а) что фраза "в го есть 3 ссылочных типа: мапа, слайс и канал" означает "в го не используется терминология ссылочных типов"
б) что фраза "слайс должен быть явно инициализирован словом make, иначе вы получите панику" на самом деле означает "слайс можно инициализировать как угодно, паники от него не добьешься"
Ну и примерно вся статья такая....
Берем Донована/Кернигана "Язык программирования Go". Глава первая: Hello World. Глава вторая: ключевые слова, переменные, указатели. Глава третья: типы данных.
"Уровень", необходимый для чтения книг по программированию - умение читать.
Вот это, как раз, просто.
Мы договариваемся о проведении консультации: +1000 "мы обещали" <=> -1000 "недополученная прибыль"
Проводим консультацию:
+1000 "нам должны" <=> -1000 "мы обещали"
Получаем деньги:
+1000 "недополученная прибыль" <=> -1000 "нам должны"
Как только везде нули - операция считается завершенной.
Двойная запись - офигенная тема.
Ее просто гладят, это успокаивает
Нейросеть захватила OTUS???
Ох уж эти эксперты, скажу я вам...
Давайте попробуем понять, что такое "ссылочная структура". Ссылочный тип, вы имели в виду?
Вот есть мапа - она ссылочная (только не структура, она буквально алиас для указателя на hMap). А слайс с каналом тут каким боком?
Зачем вы неправду-то рассказываете?
new(User) создает ровно такую же.
А вот так, внезапно, можно
А почему не вот так?
То же самое, но не обязательно целиком в куче:
Вместо make? Нельзя. Ну, просто потому, что new([]int) вернет указатель на слайс, а make([]int) - собственно слайс.
Можно вот так: `slice := *new([]int)` - подумайте, для чего тут "звездочка"
Ну вот, вырожденный пример с мапой. Мапа паникует не из-за того, что ее через new создавали, а из-за того, что в нее явно запрещена запись до инициализации. `fmt.Println((*m)[1])` - вот эта штука не паникует, нормальная мапа.
А давайте со слайсом провернем то же самое?
И ничего не случилось, никаких паник, все законно.
А вот тут:
А здесь вам new зачем нужен?
&Options{} и new(Options) в поле RetryCount будут содержать совершенно идентичные nil'ы...
Да и вообще паттерн Options не так реализовывается.
Вполне существуют, просто надо понимать, что с ними неинициализированными делать можно...
Достаточно далеко от реальности, например. Потому что делает он следующее:
Вот тут можно ознакомиться: https://go.dev/ref/spec#Slice_types
make([]int, 0, 100) - это 24 байта заголовка + new([100]int) - непрерывный кусок памяти в хипе.
Ну вы хоть проверяйте то, что пишете:
Это где я это должен сразу увидеть???
Откуда "односторонний" канал-то??? `<-chan Type`, `chan<- Type` - односторонние, а вот это - обычный канал.
А откуда "единственный сигнал"? 1 - это размер буфера! Я могу миллион "сигналов" этих ваших напосылать, при наличии читателя. Причем даже без буфера вообще - тоже могу.
Да и откуда "сигналы"-то взялись? Явно же написано chan error, ошибки это, а не сигналы.
Позор, Отус, позорище!
Ну, насколько я в курсе, с t490 по t14 gen2 вся линейка была в одном корпусе с метрически-совместимыми компонентами. Т.е., условно, можно взять 490й и в тупую вкорячить туда материнку от t14gen2 - встанет как родная. Дисплеи взаимозаменяемы, но может понадобиться переходник.
Так что, в целом, чем не конструктор
Мммм, кликбейтный заголовок!
Как минимум, в статье не раскрыта тема того, для чего каждому разработчику Rust их знать. Бедная библиотеками экосистема, что ли? Дык, вроде нет.
Второй нераскрытый вопрос, почему именно Rust требует знания этих алгоритмов. Чем они Rustмэну важнее, чем C++еру, например.
Короче, "Позор! Позор!"
Ну как минимум про SEQUEL (Structured English QUEry Language) и его идейного наследника SQL не слышал только ленивый.
https://ieeexplore.ieee.org/document/6359709 - воспоминания создателя языка. Скуль именно что задумался как язык, доступный "рядовым" пользователям.
Ничем от провода не отличается
Формулировка, конечно, корявая. Но в целом - вполне рабочая схема. Язык заносит какую-то классную фичу, конкуренты ее воспроизводят. А дальше либо языку есть что предложить сверх этой фичи, либо он становится не нужен, потому что все его киллер-фичи уже есть в конкурирующих языках.
В целом достаточно много языков так и вымерло, оставив по себе память в виде фич, осевших в других языках.