Ответ
Для динамического select можно использовать reflect.Select. Он позволяет создавать case-ы во время выполнения. Пример:
cases := make([]reflect.SelectCase, 0)
// Добавляем case для канала
ch := make(chan int)
cases = append(cases, reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(ch),
})
// Добавляем case с default
cases = append(cases, reflect.SelectCase{
Dir: reflect.SelectDefault,
})
// Выполняем select
chosen, recv, recvOK := reflect.Select(cases)
// Обработка результата
switch chosen {
case 0:
if recvOK {
fmt.Printf("Получено: %vn", recv.Interface())
} else {
fmt.Println("Канал закрыт")
}
case 1:
fmt.Println("Default case сработал")
}
Ключевые моменты:
reflect.SelectCaseописывает каждый case.Dirуказывает направление:reflect.SelectRecv(получение),reflect.SelectSend(отправка) илиreflect.SelectDefault(default-ветка).Chanдолжно бытьreflect.ValueOfканала.Send(для отправки) иRecv(для получения) должны бытьreflect.ValueOfзначений.- Можно динамически добавлять/удалять case-ы в слайсе
cases. reflect.Selectвозвращает индекс выбранного case (chosen), полученное/отправленное значение (recv) и булево значениеrecvOK(для получения).
Осторожно: reflection снижает читаемость и производительность. Используйте только когда действительно нужна динамичность, например, при построении обобщенных библиотек или фреймворков, где количество и тип каналов неизвестны заранее.
Ответ 18+ 🔞
Ах ты ж ёпта, ну и задачка! Смотри сюда, сейчас я тебе на пальцах, но с технической точностью, объясню, как из обычного select сделать такой, чтобы он сам штаны надевал по ходу дела.
Вот представь: у тебя есть select, но ты заранее не знаешь, сколько у тебя каналов и какие они. Ну, классическая история — пишешь какую-нибудь хуёвую абстракцию, а потом понимаешь, что всё надо делать динамически. На помощь приходит reflect.Select, наш верный друг, который заставляет код выглядеть как магия, но работает, блядь, как надо.
Вот смотри, как это выглядит в коде. Главное — не пугайся, там всё логично, хоть и на reflection'е:
cases := make([]reflect.SelectCase, 0)
// Допустим, у нас появился канал, и мы хотим его слушать
ch := make(chan int)
cases = append(cases, reflect.SelectCase{
Dir: reflect.SelectRecv, // Говорим, что мы тут получатели
Chan: reflect.ValueOf(ch), // А вот и сам канал, завернутый в reflect.Value
})
// А вдруг мы хотим default case, чтобы не блокироваться, если все каналы спят?
cases = append(cases, reflect.SelectCase{
Dir: reflect.SelectDefault, // Это наш спасательный круг, "ничего не готово — пошли дальше"
})
// А теперь самое интересное — выполняем этот ебучий динамический select
chosen, recv, recvOK := reflect.Select(cases)
// Ну и что мы там получили?
switch chosen {
case 0:
if recvOK {
fmt.Printf("Получено: %vn", recv.Interface()) // Всё ок, данные пришли
} else {
fmt.Println("Канал закрыт") // А канал-то прикрыли, сука
}
case 1:
fmt.Println("Default case сработал") // Ничего не произошло, идём пить чай
}
Вот тебе ключевые моменты, чтобы не облажаться:
reflect.SelectCase— это как билет в кино. Для каждогоcaseв твоёмselectты создаёшь такую структуру.Dir— это направление. Три варианта:SelectRecv(слушаем канал),SelectSend(пишем в канал) иSelectDefault(наш любимыйdefault).Chan— это, собственно, сам канал. Но! Его надо обернуть вreflect.ValueOf(), иначе reflection не поймёт, что от него хотят.- Для отправки (
SelectSend) надо ещё полеSendзаполнить — туда кладёшьreflect.ValueOf(значение_для_отправки). - Вся мощь в том, что ты можешь этот слайс
casesперед вызовомreflect.Selectкак хочешь нахуй менять — добавлять каналы, убирать, менять default. Полная динамика, блядь! reflect.Selectвозвращает тебе три штуки: индекс выбранного кейса (chosen), значение (recv, если было получение/отправка) иrecvOK— булево, которое говорит, нормально ли мы получили данные или канал уже закрыли.
Но слушай, предупреждаю сразу: reflection — это как ебать с закрытыми глазами. Код становится менее читаемым, а производительность может просесть, как у меня настроение в понедельник утром. Используй эту хуйню только тогда, когда реально нет другого выхода — например, когда пишешь какую-нибудь библиотеку общего назначения, где на этапе компиляции нихуя не ясно, с чем придётся работать. Для всего остального — обычный select, и спасибо на этом.