Как сделать select динамическим

Ответ

Для динамического 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 сработал")
}

Ключевые моменты:

  1. reflect.SelectCase описывает каждый case.
  2. Dir указывает направление: reflect.SelectRecv (получение), reflect.SelectSend (отправка) или reflect.SelectDefault (default-ветка).
  3. Chan должно быть reflect.ValueOf канала.
  4. Send (для отправки) и Recv (для получения) должны быть reflect.ValueOf значений.
  5. Можно динамически добавлять/удалять case-ы в слайсе cases.
  6. 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 сработал") // Ничего не произошло, идём пить чай
}

Вот тебе ключевые моменты, чтобы не облажаться:

  1. reflect.SelectCase — это как билет в кино. Для каждого case в твоём select ты создаёшь такую структуру.
  2. Dir — это направление. Три варианта: SelectRecv (слушаем канал), SelectSend (пишем в канал) и SelectDefault (наш любимый default).
  3. Chan — это, собственно, сам канал. Но! Его надо обернуть в reflect.ValueOf(), иначе reflection не поймёт, что от него хотят.
  4. Для отправки (SelectSend) надо ещё поле Send заполнить — туда кладёшь reflect.ValueOf(значение_для_отправки).
  5. Вся мощь в том, что ты можешь этот слайс cases перед вызовом reflect.Select как хочешь нахуй менять — добавлять каналы, убирать, менять default. Полная динамика, блядь!
  6. reflect.Select возвращает тебе три штуки: индекс выбранного кейса (chosen), значение (recv, если было получение/отправка) и recvOK — булево, которое говорит, нормально ли мы получили данные или канал уже закрыли.

Но слушай, предупреждаю сразу: reflection — это как ебать с закрытыми глазами. Код становится менее читаемым, а производительность может просесть, как у меня настроение в понедельник утром. Используй эту хуйню только тогда, когда реально нет другого выхода — например, когда пишешь какую-нибудь библиотеку общего назначения, где на этапе компиляции нихуя не ясно, с чем придётся работать. Для всего остального — обычный select, и спасибо на этом.